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 limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 338    LIMIT_FETCH = "ALL"
 339
 340    # Whether limit and fetch allows expresions or just limits
 341    LIMIT_ONLY_LITERALS = False
 342
 343    # Whether a table is allowed to be renamed with a db
 344    RENAME_TABLE_WITH_DB = True
 345
 346    # The separator for grouping sets and rollups
 347    GROUPINGS_SEP = ","
 348
 349    # The string used for creating an index on a table
 350    INDEX_ON = "ON"
 351
 352    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 353    INOUT_SEPARATOR = " "
 354
 355    # Whether join hints should be generated
 356    JOIN_HINTS = True
 357
 358    # Whether directed joins are supported
 359    DIRECTED_JOINS = False
 360
 361    # Whether table hints should be generated
 362    TABLE_HINTS = True
 363
 364    # Whether query hints should be generated
 365    QUERY_HINTS = True
 366
 367    # What kind of separator to use for query hints
 368    QUERY_HINT_SEP = ", "
 369
 370    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 371    IS_BOOL_ALLOWED = True
 372
 373    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 374    DUPLICATE_KEY_UPDATE_WITH_SET = True
 375
 376    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 377    LIMIT_IS_TOP = False
 378
 379    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 380    RETURNING_END = True
 381
 382    # Whether to generate an unquoted value for EXTRACT's date part argument
 383    EXTRACT_ALLOWS_QUOTES = True
 384
 385    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 386    TZ_TO_WITH_TIME_ZONE = False
 387
 388    # Whether the NVL2 function is supported
 389    NVL2_SUPPORTED = True
 390
 391    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 392    SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE")
 393
 394    # Whether VALUES statements can be used as derived tables.
 395    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 396    # SELECT * VALUES into SELECT UNION
 397    VALUES_AS_TABLE = True
 398
 399    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 400    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 401
 402    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 403    UNNEST_WITH_ORDINALITY = True
 404
 405    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 406    AGGREGATE_FILTER_SUPPORTED = True
 407
 408    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 409    SEMI_ANTI_JOIN_WITH_SIDE = True
 410
 411    # Whether to include the type of a computed column in the CREATE DDL
 412    COMPUTED_COLUMN_WITH_TYPE = True
 413
 414    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 415    SUPPORTS_TABLE_COPY = True
 416
 417    # Whether parentheses are required around the table sample's expression
 418    TABLESAMPLE_REQUIRES_PARENS = True
 419
 420    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 421    TABLESAMPLE_SIZE_IS_ROWS = True
 422
 423    # The keyword(s) to use when generating a sample clause
 424    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 425
 426    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 427    TABLESAMPLE_WITH_METHOD = True
 428
 429    # The keyword to use when specifying the seed of a sample clause
 430    TABLESAMPLE_SEED_KEYWORD = "SEED"
 431
 432    # Whether COLLATE is a function instead of a binary operator
 433    COLLATE_IS_FUNC = False
 434
 435    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 436    DATA_TYPE_SPECIFIERS_ALLOWED = False
 437
 438    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 439    ENSURE_BOOLS = False
 440
 441    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 442    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 443
 444    # Whether CONCAT requires >1 arguments
 445    SUPPORTS_SINGLE_ARG_CONCAT = True
 446
 447    # Whether LAST_DAY function supports a date part argument
 448    LAST_DAY_SUPPORTS_DATE_PART = True
 449
 450    # Whether named columns are allowed in table aliases
 451    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 452
 453    # Whether named columns are allowed in CTE definitions
 454    SUPPORTS_NAMED_CTE_COLUMNS = True
 455
 456    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 457    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 458
 459    # What delimiter to use for separating JSON key/value pairs
 460    JSON_KEY_VALUE_PAIR_SEP = ":"
 461
 462    # INSERT OVERWRITE TABLE x override
 463    INSERT_OVERWRITE = " OVERWRITE TABLE"
 464
 465    # Whether the SELECT .. INTO syntax is used instead of CTAS
 466    SUPPORTS_SELECT_INTO = False
 467
 468    # Whether UNLOGGED tables can be created
 469    SUPPORTS_UNLOGGED_TABLES = False
 470
 471    # Whether the CREATE TABLE LIKE statement is supported
 472    SUPPORTS_CREATE_TABLE_LIKE = True
 473
 474    # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 475    SUPPORTS_MODIFY_COLUMN = False
 476
 477    # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported
 478    SUPPORTS_CHANGE_COLUMN = False
 479
 480    # Whether the LikeProperty needs to be specified inside of the schema clause
 481    LIKE_PROPERTY_INSIDE_SCHEMA = False
 482
 483    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 484    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 485    MULTI_ARG_DISTINCT = True
 486
 487    # Whether the JSON extraction operators expect a value of type JSON
 488    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 489
 490    # Whether bracketed keys like ["foo"] are supported in JSON paths
 491    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 492
 493    # Whether to escape keys using single quotes in JSON paths
 494    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 495
 496    # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be
 497    # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match
 498    # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks
 499    # where a bare colon key is case-insensitive but a bracketed key is case-sensitive.
 500    JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False
 501
 502    # The JSONPathPart expressions supported by this dialect
 503    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 504
 505    # Whether any(f(x) for x in array) can be implemented by this dialect
 506    CAN_IMPLEMENT_ARRAY_ANY = False
 507
 508    # Whether the function TO_NUMBER is supported
 509    SUPPORTS_TO_NUMBER = True
 510
 511    # Whether EXCLUDE in window specification is supported
 512    SUPPORTS_WINDOW_EXCLUDE = False
 513
 514    # Whether or not set op modifiers apply to the outer set op or select.
 515    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 516    # True means limit 1 happens after the set op, False means it it happens on y.
 517    SET_OP_MODIFIERS = True
 518
 519    # Whether parameters from COPY statement are wrapped in parentheses
 520    COPY_PARAMS_ARE_WRAPPED = True
 521
 522    # Whether values of params are set with "=" token or empty space
 523    COPY_PARAMS_EQ_REQUIRED = False
 524
 525    # Whether COPY statement has INTO keyword
 526    COPY_HAS_INTO_KEYWORD = True
 527
 528    # Whether the conditional TRY(expression) function is supported
 529    TRY_SUPPORTED = True
 530
 531    # Whether the UESCAPE syntax in unicode strings is supported
 532    SUPPORTS_UESCAPE = True
 533
 534    # Function used to replace escaped unicode codes in unicode strings
 535    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 536
 537    # The keyword to use when generating a star projection with excluded columns
 538    STAR_EXCEPT = "EXCEPT"
 539
 540    # The HEX function name
 541    HEX_FUNC = "HEX"
 542
 543    # The keywords to use when prefixing & separating WITH based properties
 544    WITH_PROPERTIES_PREFIX = "WITH"
 545
 546    # Whether to quote the generated expression of exp.JsonPath
 547    QUOTE_JSON_PATH = True
 548
 549    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 550    PAD_FILL_PATTERN_IS_REQUIRED = False
 551
 552    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 553    SUPPORTS_EXPLODING_PROJECTIONS = True
 554
 555    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 556    ARRAY_CONCAT_IS_VAR_LEN = True
 557
 558    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 559    SUPPORTS_CONVERT_TIMEZONE = False
 560
 561    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 562    SUPPORTS_MEDIAN = True
 563
 564    # Whether UNIX_SECONDS(timestamp) is supported
 565    SUPPORTS_UNIX_SECONDS = False
 566
 567    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 568    ALTER_SET_WRAPPED = False
 569
 570    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 571    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 572    # TODO: The normalization should be done by default once we've tested it across all dialects.
 573    NORMALIZE_EXTRACT_DATE_PARTS = False
 574
 575    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 576    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 577
 578    # The function name of the exp.ArraySize expression
 579    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 580
 581    # The syntax to use when altering the type of a column
 582    ALTER_SET_TYPE = "SET DATA TYPE"
 583
 584    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 585    # None -> Doesn't support it at all
 586    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 587    # True (Postgres) -> Explicitly requires it
 588    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 589
 590    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 591    SUPPORTS_DECODE_CASE = True
 592
 593    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 594    SUPPORTS_BETWEEN_FLAGS = False
 595
 596    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 597    SUPPORTS_LIKE_QUANTIFIERS = True
 598
 599    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 600    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 601
 602    # Whether to include the VARIABLE keyword for SET assignments
 603    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 604
 605    # The keyword to use for default value assignment in DECLARE statements
 606    DECLARE_DEFAULT_ASSIGNMENT = "="
 607
 608    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 609    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 610    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 611    UPDATE_STATEMENT_SUPPORTS_FROM = True
 612
 613    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 614    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 615
 616    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 617    # - Snowflake: DROP ICEBERG TABLE a.b;
 618    # - DuckDB:    DROP TABLE a.b;
 619    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 620
 621    TYPE_MAPPING: t.ClassVar = {
 622        exp.DType.DATETIME2: "TIMESTAMP",
 623        exp.DType.NCHAR: "CHAR",
 624        exp.DType.NVARCHAR: "VARCHAR",
 625        exp.DType.MEDIUMTEXT: "TEXT",
 626        exp.DType.LONGTEXT: "TEXT",
 627        exp.DType.TINYTEXT: "TEXT",
 628        exp.DType.BLOB: "VARBINARY",
 629        exp.DType.MEDIUMBLOB: "BLOB",
 630        exp.DType.LONGBLOB: "BLOB",
 631        exp.DType.TINYBLOB: "BLOB",
 632        exp.DType.INET: "INET",
 633        exp.DType.ROWVERSION: "VARBINARY",
 634        exp.DType.SMALLDATETIME: "TIMESTAMP",
 635    }
 636
 637    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 638
 639    # mapping of DType to its default parameters, bounds
 640    TYPE_PARAM_SETTINGS: t.ClassVar[
 641        dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]
 642    ] = {}
 643
 644    TIME_PART_SINGULARS: t.ClassVar = {
 645        "MICROSECONDS": "MICROSECOND",
 646        "SECONDS": "SECOND",
 647        "MINUTES": "MINUTE",
 648        "HOURS": "HOUR",
 649        "DAYS": "DAY",
 650        "WEEKS": "WEEK",
 651        "MONTHS": "MONTH",
 652        "QUARTERS": "QUARTER",
 653        "YEARS": "YEAR",
 654    }
 655
 656    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 657        "cluster": lambda self, e: self.sql(e, "cluster"),
 658        "distribute": lambda self, e: self.sql(e, "distribute"),
 659        "sort": lambda self, e: self.sql(e, "sort"),
 660        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 661    }
 662
 663    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 664
 665    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 666
 667    PARAMETER_TOKEN = "@"
 668    NAMED_PLACEHOLDER_TOKEN = ":"
 669
 670    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 671
 672    PROPERTIES_LOCATION: t.ClassVar = {
 673        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 674        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 675        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 676        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 677        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 678        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 679        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 680        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 681        exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA,
 682        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 683        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 684        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 685        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 686        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 687        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 688        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 689        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 690        exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA,
 691        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 692        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 693        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 694        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 695        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 696        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 697        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 698        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 699        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 700        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 701        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 702        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 703        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 704        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 705        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 706        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 707        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 708        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 709        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 710        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 711        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 712        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 713        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 714        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 715        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 716        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 717        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 718        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 719        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 720        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 721        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 722        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 723        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 724        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 725        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 726        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 727        exp.LogProperty: exp.Properties.Location.POST_NAME,
 728        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 729        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 730        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 731        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 732        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 733        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 734        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 735        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 736        exp.Order: exp.Properties.Location.POST_SCHEMA,
 737        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 738        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 739        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 740        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 741        exp.Property: exp.Properties.Location.POST_WITH,
 742        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 743        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 744        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 745        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 746        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 747        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 748        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 749        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 750        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 751        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 752        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 753        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 754        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 755        exp.Set: exp.Properties.Location.POST_SCHEMA,
 756        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 757        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 758        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 759        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 760        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 761        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 762        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 763        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 764        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 765        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 766        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 767        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 768        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 769        exp.Tags: exp.Properties.Location.POST_WITH,
 770        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 771        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 772        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 773        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 774        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 775        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 776        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 777        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 778        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 779        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 780        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 781        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 782        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 783        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 784        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 785        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 786        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 787    }
 788
 789    # Keywords that can't be used as unquoted identifier names
 790    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 791
 792    # Exprs whose comments are separated from them for better formatting
 793    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 794        exp.Command,
 795        exp.Create,
 796        exp.Describe,
 797        exp.Delete,
 798        exp.Drop,
 799        exp.From,
 800        exp.Insert,
 801        exp.Join,
 802        exp.MultitableInserts,
 803        exp.Order,
 804        exp.Group,
 805        exp.Having,
 806        exp.Select,
 807        exp.SetOperation,
 808        exp.Update,
 809        exp.Where,
 810        exp.With,
 811    )
 812
 813    # Exprs that should not have their comments generated in maybe_comment
 814    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 815        exp.Binary,
 816        exp.SetOperation,
 817    )
 818
 819    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 820    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 821        exp.Column,
 822        exp.Literal,
 823        exp.Neg,
 824        exp.Paren,
 825    )
 826
 827    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 828        exp.DType.NVARCHAR,
 829        exp.DType.VARCHAR,
 830        exp.DType.CHAR,
 831        exp.DType.NCHAR,
 832    }
 833
 834    # Exprs that need to have all CTEs under them bubbled up to them
 835    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 836
 837    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 838
 839    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 840
 841    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 842
 843    __slots__ = (
 844        "pretty",
 845        "identify",
 846        "normalize",
 847        "pad",
 848        "_indent",
 849        "normalize_functions",
 850        "unsupported_level",
 851        "max_unsupported",
 852        "leading_comma",
 853        "max_text_width",
 854        "comments",
 855        "dialect",
 856        "unsupported_messages",
 857        "_escaped_quote_end",
 858        "_escaped_byte_quote_end",
 859        "_escaped_identifier_end",
 860        "_next_name",
 861        "_identifier_start",
 862        "_identifier_end",
 863        "_quote_json_path_key_using_brackets",
 864        "_dispatch",
 865    )
 866
 867    def __init__(
 868        self,
 869        pretty: bool | int | None = None,
 870        identify: str | bool = False,
 871        normalize: bool = False,
 872        pad: int = 2,
 873        indent: int = 2,
 874        normalize_functions: str | bool | None = None,
 875        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 876        max_unsupported: int = 3,
 877        leading_comma: bool = False,
 878        max_text_width: int = 80,
 879        comments: bool = True,
 880        dialect: DialectType = None,
 881    ):
 882        import sqlglot
 883        import sqlglot.dialects.dialect
 884
 885        self.pretty = pretty if pretty is not None else sqlglot.pretty
 886        self.identify = identify
 887        self.normalize = normalize
 888        self.pad = pad
 889        self._indent = indent
 890        self.unsupported_level = unsupported_level
 891        self.max_unsupported = max_unsupported
 892        self.leading_comma = leading_comma
 893        self.max_text_width = max_text_width
 894        self.comments = comments
 895        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 896
 897        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 898        self.normalize_functions = (
 899            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 900        )
 901
 902        self.unsupported_messages: list[str] = []
 903        self._escaped_quote_end: str = (
 904            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 905        )
 906        self._escaped_byte_quote_end: str = (
 907            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 908            if self.dialect.BYTE_END
 909            else ""
 910        )
 911        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 912
 913        self._next_name = name_sequence("_t")
 914
 915        self._identifier_start = self.dialect.IDENTIFIER_START
 916        self._identifier_end = self.dialect.IDENTIFIER_END
 917
 918        self._quote_json_path_key_using_brackets = True
 919
 920        cls = type(self)
 921        dispatch = _DISPATCH_CACHE.get(cls)
 922        if dispatch is None:
 923            dispatch = _build_dispatch(cls)
 924            _DISPATCH_CACHE[cls] = dispatch
 925        self._dispatch = dispatch
 926
 927    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 928        """
 929        Generates the SQL string corresponding to the given syntax tree.
 930
 931        Args:
 932            expression: The syntax tree.
 933            copy: Whether to copy the expression. The generator performs mutations so
 934                it is safer to copy.
 935
 936        Returns:
 937            The SQL string corresponding to `expression`.
 938        """
 939        if copy:
 940            expression = expression.copy()
 941
 942        expression = self.preprocess(expression)
 943
 944        self.unsupported_messages = []
 945        sql = self.sql(expression).strip()
 946
 947        if self.pretty:
 948            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 949
 950        if self.unsupported_level == ErrorLevel.IGNORE:
 951            return sql
 952
 953        if self.unsupported_level == ErrorLevel.WARN:
 954            for msg in self.unsupported_messages:
 955                logger.warning(msg)
 956        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 957            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 958
 959        return sql
 960
 961    def preprocess(self, expression: exp.Expr) -> exp.Expr:
 962        """Apply generic preprocessing transformations to a given expression."""
 963        expression = self._move_ctes_to_top_level(expression)
 964
 965        if self.ENSURE_BOOLS:
 966            import sqlglot.transforms
 967
 968            expression = sqlglot.transforms.ensure_bools(expression)
 969
 970        return expression
 971
 972    def _move_ctes_to_top_level(self, expression: E) -> E:
 973        if (
 974            not expression.parent
 975            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 976            and any(node.parent is not expression for node in expression.find_all(exp.With))
 977        ):
 978            import sqlglot.transforms
 979
 980            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
 981        return expression
 982
 983    def unsupported(self, message: str) -> None:
 984        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 985            raise UnsupportedError(message)
 986        self.unsupported_messages.append(message)
 987
 988    def sep(self, sep: str = " ") -> str:
 989        return f"{sep.strip()}\n" if self.pretty else sep
 990
 991    def seg(self, sql: str, sep: str = " ") -> str:
 992        return f"{self.sep(sep)}{sql}"
 993
 994    def sanitize_comment(self, comment: str) -> str:
 995        comment = " " + comment if comment[0].strip() else comment
 996        comment = comment + " " if comment[-1].strip() else comment
 997
 998        # Escape block comment markers to prevent premature closure or unintended nesting.
 999        # This is necessary because single-line comments (--) are converted to block comments
1000        # (/* */) on output, and any */ in the original text would close the comment early.
1001        comment = comment.replace("*/", "* /").replace("/*", "/ *")
1002
1003        return comment
1004
1005    def maybe_comment(
1006        self,
1007        sql: str,
1008        expression: exp.Expr | None = None,
1009        comments: list[str] | None = None,
1010        separated: bool = False,
1011    ) -> str:
1012        comments = (
1013            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1014            if self.comments
1015            else None
1016        )
1017
1018        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1019            return sql
1020
1021        comments_list = [
1022            f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/"
1023            for comment in comments
1024            if comment
1025        ]
1026
1027        if not comments_list:
1028            return sql
1029
1030        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1031            comments_sql = self.sep().join(comments_list)
1032            return (
1033                f"{self.sep()}{comments_sql}{sql}"
1034                if not sql or sql[0].isspace()
1035                else f"{comments_sql}{self.sep()}{sql}"
1036            )
1037
1038        return f"{sql} {' '.join(comments_list)}"
1039
1040    def wrap(self, expression: exp.Expr | str) -> str:
1041        this_sql = (
1042            self.sql(expression)
1043            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1044            else self.sql(expression, "this")
1045        )
1046        if not this_sql:
1047            return "()"
1048
1049        this_sql = self.indent(this_sql, level=1, pad=0)
1050        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1051
1052    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1053        original = self.identify
1054        self.identify = False
1055        result = func(*args, **kwargs)
1056        self.identify = original
1057        return result
1058
1059    def normalize_func(self, name: str) -> str:
1060        if self.normalize_functions == "upper" or self.normalize_functions is True:
1061            return name.upper()
1062        if self.normalize_functions == "lower":
1063            return name.lower()
1064        return name
1065
1066    def indent(
1067        self,
1068        sql: str,
1069        level: int = 0,
1070        pad: int | None = None,
1071        skip_first: bool = False,
1072        skip_last: bool = False,
1073    ) -> str:
1074        if not self.pretty or not sql:
1075            return sql
1076
1077        pad = self.pad if pad is None else pad
1078        lines = sql.split("\n")
1079
1080        return "\n".join(
1081            (
1082                line
1083                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1084                else f"{' ' * (level * self._indent + pad)}{line}"
1085            )
1086            for i, line in enumerate(lines)
1087        )
1088
1089    def sql(
1090        self,
1091        expression: str | exp.Expr | None,
1092        key: str | None = None,
1093        comment: bool = True,
1094    ) -> str:
1095        if not expression:
1096            return ""
1097
1098        if isinstance(expression, str):
1099            return expression
1100
1101        if key:
1102            value = expression.args.get(key)
1103            if value:
1104                return self.sql(value)
1105            return ""
1106
1107        handler = self._dispatch.get(expression.__class__)
1108
1109        if handler:
1110            sql = handler(self, expression)
1111        elif isinstance(expression, exp.Func):
1112            sql = self.function_fallback_sql(expression)
1113        elif isinstance(expression, exp.Property):
1114            sql = self.property_sql(expression)
1115        else:
1116            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1117
1118        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1119
1120    def uncache_sql(self, expression: exp.Uncache) -> str:
1121        table = self.sql(expression, "this")
1122        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1123        return f"UNCACHE TABLE{exists_sql} {table}"
1124
1125    def cache_sql(self, expression: exp.Cache) -> str:
1126        lazy = " LAZY" if expression.args.get("lazy") else ""
1127        table = self.sql(expression, "this")
1128        options = expression.args.get("options")
1129        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1130        sql = self.sql(expression, "expression")
1131        sql = f" AS{self.sep()}{sql}" if sql else ""
1132        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1133        return self.prepend_ctes(expression, sql)
1134
1135    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1136        default = "DEFAULT " if expression.args.get("default") else ""
1137        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1138
1139    def column_parts(self, expression: exp.Column) -> str:
1140        return ".".join(
1141            self.sql(part)
1142            for part in (
1143                expression.args.get("catalog"),
1144                expression.args.get("db"),
1145                expression.args.get("table"),
1146                expression.args.get("this"),
1147            )
1148            if part
1149        )
1150
1151    def column_sql(self, expression: exp.Column) -> str:
1152        join_mark = " (+)" if expression.args.get("join_mark") else ""
1153
1154        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1155            join_mark = ""
1156            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1157
1158        return f"{self.column_parts(expression)}{join_mark}"
1159
1160    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1161        return self.column_sql(expression)
1162
1163    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1164        this = self.sql(expression, "this")
1165        this = f" {this}" if this else ""
1166        position = self.sql(expression, "position")
1167        return f"{position}{this}"
1168
1169    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1170        column = self.sql(expression, "this")
1171        kind = self.sql(expression, "kind")
1172        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1173        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1174        kind = f"{sep}{kind}" if kind else ""
1175        constraints = f" {constraints}" if constraints else ""
1176        position = self.sql(expression, "position")
1177        position = f" {position}" if position else ""
1178
1179        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1180            kind = ""
1181
1182        return f"{exists}{column}{kind}{constraints}{position}"
1183
1184    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1185        this = self.sql(expression, "this")
1186        kind_sql = self.sql(expression, "kind").strip()
1187        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1188
1189    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1190        this = self.sql(expression, "this")
1191        if expression.args.get("not_null"):
1192            persisted = " PERSISTED NOT NULL"
1193        elif expression.args.get("persisted"):
1194            persisted = " PERSISTED"
1195        else:
1196            persisted = ""
1197
1198        return f"AS {this}{persisted}"
1199
1200    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1201        return self.token_sql(TokenType.AUTO_INCREMENT)
1202
1203    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1204        if isinstance(expression.this, list):
1205            this = self.wrap(self.expressions(expression, key="this", flat=True))
1206        else:
1207            this = self.sql(expression, "this")
1208
1209        return f"COMPRESS {this}"
1210
1211    def generatedasidentitycolumnconstraint_sql(
1212        self, expression: exp.GeneratedAsIdentityColumnConstraint
1213    ) -> str:
1214        this = ""
1215        if expression.this is not None:
1216            on_null = " ON NULL" if expression.args.get("on_null") else ""
1217            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1218
1219        start = expression.args.get("start")
1220        start = f"START WITH {start}" if start else ""
1221        increment = expression.args.get("increment")
1222        increment = f" INCREMENT BY {increment}" if increment else ""
1223        minvalue = expression.args.get("minvalue")
1224        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1225        maxvalue = expression.args.get("maxvalue")
1226        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1227        cycle = expression.args.get("cycle")
1228        cycle_sql = ""
1229
1230        if cycle is not None:
1231            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1232            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1233
1234        sequence_opts = ""
1235        if start or increment or cycle_sql:
1236            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1237            sequence_opts = f" ({sequence_opts.strip()})"
1238
1239        expr = self.sql(expression, "expression")
1240        expr = f"({expr})" if expr else "IDENTITY"
1241
1242        return f"GENERATED{this} AS {expr}{sequence_opts}"
1243
1244    def generatedasrowcolumnconstraint_sql(
1245        self, expression: exp.GeneratedAsRowColumnConstraint
1246    ) -> str:
1247        start = "START" if expression.args.get("start") else "END"
1248        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1249        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1250
1251    def periodforsystemtimeconstraint_sql(
1252        self, expression: exp.PeriodForSystemTimeConstraint
1253    ) -> str:
1254        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1255
1256    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1257        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1258
1259    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1260        desc = expression.args.get("desc")
1261        if desc is not None:
1262            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1263        options = self.expressions(expression, key="options", flat=True, sep=" ")
1264        options = f" {options}" if options else ""
1265        return f"PRIMARY KEY{options}"
1266
1267    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1268        this = self.sql(expression, "this")
1269        this = f" {this}" if this else ""
1270        index_type = expression.args.get("index_type")
1271        index_type = f" USING {index_type}" if index_type else ""
1272        on_conflict = self.sql(expression, "on_conflict")
1273        on_conflict = f" {on_conflict}" if on_conflict else ""
1274        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1275        options = self.expressions(expression, key="options", flat=True, sep=" ")
1276        options = f" {options}" if options else ""
1277        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1278
1279    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1280        input_ = expression.args.get("input_")
1281        output = expression.args.get("output")
1282        variadic = expression.args.get("variadic")
1283
1284        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1285        if variadic:
1286            return "VARIADIC"
1287
1288        if input_ and output:
1289            return f"IN{self.INOUT_SEPARATOR}OUT"
1290        if input_:
1291            return "IN"
1292        if output:
1293            return "OUT"
1294
1295        return ""
1296
1297    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1298        return self.sql(expression, "this")
1299
1300    def create_sql(self, expression: exp.Create) -> str:
1301        kind = self.sql(expression, "kind")
1302        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1303
1304        properties = expression.args.get("properties")
1305
1306        if (
1307            kind == "TRIGGER"
1308            and properties
1309            and properties.expressions
1310            and isinstance(properties.expressions[0], exp.TriggerProperties)
1311            and properties.expressions[0].args.get("constraint")
1312        ):
1313            kind = f"CONSTRAINT {kind}"
1314
1315        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1316
1317        this = self.createable_sql(expression, properties_locs)
1318
1319        properties_sql = ""
1320        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1321            exp.Properties.Location.POST_WITH
1322        ):
1323            props_ast = exp.Properties(
1324                expressions=[
1325                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1326                    *properties_locs[exp.Properties.Location.POST_WITH],
1327                ]
1328            )
1329            props_ast.parent = expression
1330            properties_sql = self.sql(props_ast)
1331
1332            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1333                properties_sql = self.sep() + properties_sql
1334            elif not self.pretty:
1335                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1336                properties_sql = f" {properties_sql}"
1337
1338        begin = " BEGIN" if expression.args.get("begin") else ""
1339
1340        expression_sql = self.sql(expression, "expression")
1341        if expression_sql:
1342            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1343
1344            if not isinstance(expression.expression, exp.MacroOverloads) and (
1345                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1346            ):
1347                postalias_props_sql = ""
1348                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1349                    postalias_props_sql = self.properties(
1350                        exp.Properties(
1351                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1352                        ),
1353                        wrapped=False,
1354                    )
1355                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1356                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1357
1358        postindex_props_sql = ""
1359        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1360            postindex_props_sql = self.properties(
1361                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1362                wrapped=False,
1363                prefix=" ",
1364            )
1365
1366        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1367        indexes = f" {indexes}" if indexes else ""
1368        index_sql = indexes + postindex_props_sql
1369
1370        replace = " OR REPLACE" if expression.args.get("replace") else ""
1371        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1372        unique = " UNIQUE" if expression.args.get("unique") else ""
1373
1374        clustered = expression.args.get("clustered")
1375        if clustered is None:
1376            clustered_sql = ""
1377        elif clustered:
1378            clustered_sql = " CLUSTERED COLUMNSTORE"
1379        else:
1380            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1381
1382        postcreate_props_sql = ""
1383        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1384            postcreate_props_sql = self.properties(
1385                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1386                sep=" ",
1387                prefix=" ",
1388                wrapped=False,
1389            )
1390
1391        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1392
1393        postexpression_props_sql = ""
1394        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1395            postexpression_props_sql = self.properties(
1396                exp.Properties(
1397                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1398                ),
1399                sep=" ",
1400                prefix=" ",
1401                wrapped=False,
1402            )
1403
1404        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1405        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1406        no_schema_binding = (
1407            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1408        )
1409
1410        clone = self.sql(expression, "clone")
1411        clone = f" {clone}" if clone else ""
1412
1413        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1414            properties_expression = f"{expression_sql}{properties_sql}"
1415        else:
1416            properties_expression = f"{properties_sql}{expression_sql}"
1417
1418        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1419        return self.prepend_ctes(expression, expression_sql)
1420
1421    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1422        start = self.sql(expression, "start")
1423        start = f"START WITH {start}" if start else ""
1424        increment = self.sql(expression, "increment")
1425        increment = f" INCREMENT BY {increment}" if increment else ""
1426        minvalue = self.sql(expression, "minvalue")
1427        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1428        maxvalue = self.sql(expression, "maxvalue")
1429        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1430        owned = self.sql(expression, "owned")
1431        owned = f" OWNED BY {owned}" if owned else ""
1432
1433        cache = expression.args.get("cache")
1434        if cache is None:
1435            cache_str = ""
1436        elif cache is True:
1437            cache_str = " CACHE"
1438        else:
1439            cache_str = f" CACHE {cache}"
1440
1441        options = self.expressions(expression, key="options", flat=True, sep=" ")
1442        options = f" {options}" if options else ""
1443
1444        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1445
1446    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1447        timing = expression.args.get("timing", "")
1448        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1449        timing_events = f"{timing} {events}".strip() if timing or events else ""
1450
1451        parts = [timing_events, "ON", self.sql(expression, "table")]
1452
1453        if referenced_table := expression.args.get("referenced_table"):
1454            parts.extend(["FROM", self.sql(referenced_table)])
1455
1456        if deferrable := expression.args.get("deferrable"):
1457            parts.append(deferrable)
1458
1459        if initially := expression.args.get("initially"):
1460            parts.append(f"INITIALLY {initially}")
1461
1462        if referencing := expression.args.get("referencing"):
1463            parts.append(self.sql(referencing))
1464
1465        if for_each := expression.args.get("for_each"):
1466            parts.append(f"FOR EACH {for_each}")
1467
1468        if when := expression.args.get("when"):
1469            parts.append(f"WHEN ({self.sql(when)})")
1470
1471        parts.append(self.sql(expression, "execute"))
1472
1473        return self.sep().join(parts)
1474
1475    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1476        parts = []
1477
1478        if old_alias := expression.args.get("old"):
1479            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1480
1481        if new_alias := expression.args.get("new"):
1482            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1483
1484        return f"REFERENCING {' '.join(parts)}"
1485
1486    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1487        columns = expression.args.get("columns")
1488        if columns:
1489            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1490
1491        return self.sql(expression, "this")
1492
1493    def clone_sql(self, expression: exp.Clone) -> str:
1494        this = self.sql(expression, "this")
1495        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1496        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1497        return f"{shallow}{keyword} {this}"
1498
1499    def describe_sql(self, expression: exp.Describe) -> str:
1500        style = expression.args.get("style")
1501        style = f" {style}" if style else ""
1502        partition = self.sql(expression, "partition")
1503        partition = f" {partition}" if partition else ""
1504        format = self.sql(expression, "format")
1505        format = f" {format}" if format else ""
1506        as_json = " AS JSON" if expression.args.get("as_json") else ""
1507
1508        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1509
1510    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1511        tag = self.sql(expression, "tag")
1512        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1513
1514    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1515        with_ = self.sql(expression, "with_")
1516        if with_:
1517            sql = f"{with_}{self.sep()}{sql}"
1518        return sql
1519
1520    def with_sql(self, expression: exp.With) -> str:
1521        sql = self.expressions(expression, flat=True)
1522        recursive = (
1523            "RECURSIVE "
1524            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1525            else ""
1526        )
1527        search = self.sql(expression, "search")
1528        search = f" {search}" if search else ""
1529
1530        return f"WITH {recursive}{sql}{search}"
1531
1532    def cte_sql(self, expression: exp.CTE) -> str:
1533        alias = expression.args.get("alias")
1534        if alias:
1535            alias.add_comments(expression.pop_comments())
1536
1537        alias_sql = self.sql(expression, "alias")
1538
1539        materialized = expression.args.get("materialized")
1540        if materialized is False:
1541            materialized = "NOT MATERIALIZED "
1542        elif materialized:
1543            materialized = "MATERIALIZED "
1544
1545        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1546        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1547
1548        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1549
1550    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1551        alias = self.sql(expression, "this")
1552        columns = self.expressions(expression, key="columns", flat=True)
1553        columns = f"({columns})" if columns else ""
1554
1555        if (
1556            columns
1557            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1558            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1559        ):
1560            columns = ""
1561            self.unsupported("Named columns are not supported in table alias.")
1562
1563        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1564            alias = self._next_name()
1565
1566        return f"{alias}{columns}"
1567
1568    def bitstring_sql(self, expression: exp.BitString) -> str:
1569        this = self.sql(expression, "this")
1570        if self.dialect.BIT_START:
1571            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1572        return f"{int(this, 2)}"
1573
1574    def hexstring_sql(
1575        self, expression: exp.HexString, binary_function_repr: str | None = None
1576    ) -> str:
1577        this = self.sql(expression, "this")
1578        is_integer_type = expression.args.get("is_integer")
1579
1580        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1581            not self.dialect.HEX_START and not binary_function_repr
1582        ):
1583            # Integer representation will be returned if:
1584            # - The read dialect treats the hex value as integer literal but not the write
1585            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1586            return f"{int(this, 16)}"
1587
1588        if not is_integer_type:
1589            # Read dialect treats the hex value as BINARY/BLOB
1590            if binary_function_repr:
1591                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1592                return self.func(binary_function_repr, exp.Literal.string(this))
1593            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1594                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1595                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1596
1597        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1598
1599    def bytestring_sql(self, expression: exp.ByteString) -> str:
1600        this = self.sql(expression, "this")
1601        if self.dialect.BYTE_START:
1602            escaped_byte_string = self.escape_str(
1603                this,
1604                escape_backslash=False,
1605                delimiter=self.dialect.BYTE_END,
1606                escaped_delimiter=self._escaped_byte_quote_end,
1607                is_byte_string=True,
1608            )
1609            is_bytes = expression.args.get("is_bytes", False)
1610            delimited_byte_string = (
1611                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1612            )
1613            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1614                return self.sql(
1615                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1616                )
1617            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1618                return self.sql(
1619                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1620                )
1621
1622            return delimited_byte_string
1623
1624        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1625            return self.sql(exp.Literal.string(this))
1626
1627        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1628        return ""
1629
1630    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1631        this = self.sql(expression, "this")
1632        escape = expression.args.get("escape")
1633
1634        if self.dialect.UNICODE_START:
1635            escape_substitute = r"\\\1"
1636            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1637        else:
1638            escape_substitute = r"\\u\1"
1639            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1640
1641        if escape:
1642            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1643            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1644        else:
1645            escape_pattern = ESCAPED_UNICODE_RE
1646            escape_sql = ""
1647
1648        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1649            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1650
1651        return f"{left_quote}{this}{right_quote}{escape_sql}"
1652
1653    def rawstring_sql(self, expression: exp.RawString) -> str:
1654        string = expression.this
1655        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1656            string = string.replace("\\", "\\\\")
1657
1658        string = self.escape_str(string, escape_backslash=False)
1659        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1660
1661    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1662        this = self.sql(expression, "this")
1663        specifier = self.sql(expression, "expression")
1664        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1665        return f"{this}{specifier}"
1666
1667    def datatype_param_bound_limiter(
1668        self,
1669        expression: exp.DataType,
1670        type_value: exp.DType,
1671        defaults: tuple[int, ...],
1672        bounds: tuple[int | None, ...],
1673    ) -> exp.DataType:
1674        params = expression.expressions
1675
1676        if not params:
1677            if defaults:
1678                expression.set(
1679                    "expressions",
1680                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1681                )
1682            return expression
1683
1684        if not bounds:
1685            return expression
1686
1687        for i, param in enumerate(params):
1688            bound = bounds[i] if i < len(bounds) else None
1689            if bound is None:
1690                continue
1691
1692            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1693            if (
1694                isinstance(param_value, exp.Literal)
1695                and param_value.is_number
1696                and int(param_value.to_py()) > bound
1697            ):
1698                self.unsupported(
1699                    f"{type_value.value} parameter {param_value.name} exceeds "
1700                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1701                )
1702                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1703
1704        return expression
1705
1706    def datatype_sql(self, expression: exp.DataType) -> str:
1707        nested = ""
1708        values = ""
1709
1710        expr_nested = expression.args.get("nested")
1711        type_value = expression.this
1712
1713        if (
1714            not expr_nested
1715            and isinstance(type_value, exp.DType)
1716            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1717        ):
1718            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1719
1720        interior = (
1721            self.expressions(
1722                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1723            )
1724            if expr_nested and self.pretty
1725            else self.expressions(expression, flat=True)
1726        )
1727
1728        if type_value in self.UNSUPPORTED_TYPES:
1729            self.unsupported(
1730                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1731            )
1732
1733        type_sql: t.Any = ""
1734        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1735            type_sql = self.sql(expression, "kind")
1736        elif type_value == exp.DType.CHARACTER_SET:
1737            return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}"
1738        else:
1739            type_sql = (
1740                self.TYPE_MAPPING.get(type_value, type_value.value)
1741                if isinstance(type_value, exp.DType)
1742                else type_value
1743            )
1744
1745        if interior:
1746            if expr_nested:
1747                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1748                if expression.args.get("values") is not None:
1749                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1750                    values = self.expressions(expression, key="values", flat=True)
1751                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1752            elif type_value == exp.DType.INTERVAL:
1753                nested = f" {interior}"
1754            else:
1755                nested = f"({interior})"
1756
1757        type_sql = f"{type_sql}{nested}{values}"
1758        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1759            exp.DType.TIMETZ,
1760            exp.DType.TIMESTAMPTZ,
1761        ):
1762            type_sql = f"{type_sql} WITH TIME ZONE"
1763
1764        collate = self.sql(expression, "collate")
1765        if collate:
1766            type_sql = f"{type_sql} COLLATE {collate}"
1767
1768        return type_sql
1769
1770    def directory_sql(self, expression: exp.Directory) -> str:
1771        local = "LOCAL " if expression.args.get("local") else ""
1772        row_format = self.sql(expression, "row_format")
1773        row_format = f" {row_format}" if row_format else ""
1774        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1775
1776    def delete_sql(self, expression: exp.Delete) -> str:
1777        hint = self.sql(expression, "hint")
1778        this = self.sql(expression, "this")
1779        this = f" FROM {this}" if this else ""
1780        using = self.expressions(expression, key="using")
1781        using = f" USING {using}" if using else ""
1782        cluster = self.sql(expression, "cluster")
1783        cluster = f" {cluster}" if cluster else ""
1784        where = self.sql(expression, "where")
1785        returning = self.sql(expression, "returning")
1786        order = self.sql(expression, "order")
1787        limit = self.sql(expression, "limit")
1788        tables = self.expressions(expression, key="tables")
1789        tables = f" {tables}" if tables else ""
1790        if self.RETURNING_END:
1791            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1792        else:
1793            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1794        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1795
1796    def drop_sql(self, expression: exp.Drop) -> str:
1797        this = self.sql(expression, "this")
1798        expressions = self.expressions(expression, flat=True)
1799        expressions = f" ({expressions})" if expressions else ""
1800        kind = expression.args["kind"]
1801        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1802        iceberg = (
1803            " ICEBERG"
1804            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1805            else ""
1806        )
1807        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1808        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1809        on_cluster = self.sql(expression, "cluster")
1810        on_cluster = f" {on_cluster}" if on_cluster else ""
1811        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1812        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1813        cascade = " CASCADE" if expression.args.get("cascade") else ""
1814        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1815        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1816        purge = " PURGE" if expression.args.get("purge") else ""
1817        sync = " SYNC" if expression.args.get("sync") else ""
1818        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}"
1819
1820    def set_operation(self, expression: exp.SetOperation) -> str:
1821        op_type = type(expression)
1822        op_name = op_type.key.upper()
1823
1824        distinct = expression.args.get("distinct")
1825        if (
1826            distinct is False
1827            and op_type in (exp.Except, exp.Intersect)
1828            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1829        ):
1830            self.unsupported(f"{op_name} ALL is not supported")
1831
1832        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1833
1834        if distinct is None:
1835            distinct = default_distinct
1836            if distinct is None:
1837                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1838
1839        if distinct is default_distinct:
1840            distinct_or_all = ""
1841        else:
1842            distinct_or_all = " DISTINCT" if distinct else " ALL"
1843
1844        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1845        side_kind = f"{side_kind} " if side_kind else ""
1846
1847        by_name = " BY NAME" if expression.args.get("by_name") else ""
1848        on = self.expressions(expression, key="on", flat=True)
1849        on = f" ON ({on})" if on else ""
1850
1851        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1852
1853    def set_operations(self, expression: exp.SetOperation) -> str:
1854        if not self.SET_OP_MODIFIERS:
1855            limit = expression.args.get("limit")
1856            order = expression.args.get("order")
1857
1858            if limit or order:
1859                select = self._move_ctes_to_top_level(
1860                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1861                )
1862
1863                if limit:
1864                    select = select.limit(limit.pop(), copy=False)
1865                if order:
1866                    select = select.order_by(order.pop(), copy=False)
1867                return self.sql(select)
1868
1869        sqls: list[str] = []
1870        stack: list[str | exp.Expr] = [expression]
1871
1872        while stack:
1873            node = stack.pop()
1874
1875            if isinstance(node, exp.SetOperation):
1876                stack.append(node.expression)
1877                stack.append(
1878                    self.maybe_comment(
1879                        self.set_operation(node), comments=node.comments, separated=True
1880                    )
1881                )
1882                stack.append(node.this)
1883            else:
1884                sqls.append(self.sql(node))
1885
1886        this = self.sep().join(sqls)
1887        this = self.query_modifiers(expression, this)
1888        return self.prepend_ctes(expression, this)
1889
1890    def fetch_sql(self, expression: exp.Fetch) -> str:
1891        direction = expression.args.get("direction")
1892        direction = f" {direction}" if direction else ""
1893        count = self.sql(expression, "count")
1894        count = f" {count}" if count else ""
1895        limit_options = self.sql(expression, "limit_options")
1896        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1897        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1898
1899    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1900        percent = " PERCENT" if expression.args.get("percent") else ""
1901        rows = " ROWS" if expression.args.get("rows") else ""
1902        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1903        if not with_ties and rows:
1904            with_ties = " ONLY"
1905        return f"{percent}{rows}{with_ties}"
1906
1907    def filter_sql(self, expression: exp.Filter) -> str:
1908        if self.AGGREGATE_FILTER_SUPPORTED:
1909            this = self.sql(expression, "this")
1910            where = self.sql(expression, "expression").strip()
1911            return f"{this} FILTER({where})"
1912
1913        agg = expression.this
1914        agg_arg = agg.this
1915        cond = expression.expression.this
1916        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1917        return self.sql(agg)
1918
1919    def hint_sql(self, expression: exp.Hint) -> str:
1920        if not self.QUERY_HINTS:
1921            self.unsupported("Hints are not supported")
1922            return ""
1923
1924        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1925
1926    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1927        using = self.sql(expression, "using")
1928        using = f" USING {using}" if using else ""
1929        columns = self.expressions(expression, key="columns", flat=True)
1930        columns = f"({columns})" if columns else ""
1931        partition_by = self.expressions(expression, key="partition_by", flat=True)
1932        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1933        where = self.sql(expression, "where")
1934        include = self.expressions(expression, key="include", flat=True)
1935        if include:
1936            include = f" INCLUDE ({include})"
1937        with_storage = self.expressions(expression, key="with_storage", flat=True)
1938        with_storage = f" WITH ({with_storage})" if with_storage else ""
1939        tablespace = self.sql(expression, "tablespace")
1940        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1941        on = self.sql(expression, "on")
1942        on = f" ON {on}" if on else ""
1943
1944        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1945
1946    def index_sql(self, expression: exp.Index) -> str:
1947        unique = "UNIQUE " if expression.args.get("unique") else ""
1948        primary = "PRIMARY " if expression.args.get("primary") else ""
1949        amp = "AMP " if expression.args.get("amp") else ""
1950        name = self.sql(expression, "this")
1951        name = f"{name} " if name else ""
1952        table = self.sql(expression, "table")
1953        table = f"{self.INDEX_ON} {table}" if table else ""
1954
1955        index = "INDEX " if not table else ""
1956
1957        params = self.sql(expression, "params")
1958        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1959
1960    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
1961        this = expression.this
1962        if this and this.is_string:
1963            resolved = maybe_parse(this.name).sql(self.dialect)
1964            if "expressions" in expression.args:
1965                # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
1966                # We can't safely emit the call to other dialects since name/arg semantics may differ
1967                self.unsupported(
1968                    "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported"
1969                )
1970            return resolved
1971        self.unsupported("IDENTIFIER() with non-literal arguments is not supported")
1972        return self.func("IDENTIFIER", this)
1973
1974    def identifier_sql(self, expression: exp.Identifier) -> str:
1975        text = expression.name
1976        lower = text.lower()
1977        quoted = expression.quoted
1978        text = lower if self.normalize and not quoted else text
1979        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1980        if (
1981            quoted
1982            or self.dialect.can_quote(expression, self.identify)
1983            or lower in self.RESERVED_KEYWORDS
1984            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1985        ):
1986            text = (
1987                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
1988            )
1989        return text
1990
1991    def hex_sql(self, expression: exp.Hex) -> str:
1992        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1993        if self.dialect.HEX_LOWERCASE:
1994            text = self.func("LOWER", text)
1995
1996        return text
1997
1998    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1999        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2000        if not self.dialect.HEX_LOWERCASE:
2001            text = self.func("LOWER", text)
2002        return text
2003
2004    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
2005        input_format = self.sql(expression, "input_format")
2006        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
2007        output_format = self.sql(expression, "output_format")
2008        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
2009        return self.sep().join((input_format, output_format))
2010
2011    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
2012        string = self.sql(exp.Literal.string(expression.name))
2013        return f"{prefix}{string}"
2014
2015    def partition_sql(self, expression: exp.Partition) -> str:
2016        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
2017        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
2018
2019    def properties_sql(self, expression: exp.Properties) -> str:
2020        root_properties = []
2021        with_properties = []
2022
2023        for p in expression.expressions:
2024            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2025            if p_loc == exp.Properties.Location.POST_WITH:
2026                with_properties.append(p)
2027            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2028                root_properties.append(p)
2029
2030        root_props_ast = exp.Properties(expressions=root_properties)
2031        root_props_ast.parent = expression.parent
2032
2033        with_props_ast = exp.Properties(expressions=with_properties)
2034        with_props_ast.parent = expression.parent
2035
2036        root_props = self.root_properties(root_props_ast)
2037        with_props = self.with_properties(with_props_ast)
2038
2039        if root_props and with_props and not self.pretty:
2040            with_props = " " + with_props
2041
2042        return root_props + with_props
2043
2044    def root_properties(self, properties: exp.Properties) -> str:
2045        if properties.expressions:
2046            return self.expressions(properties, indent=False, sep=" ")
2047        return ""
2048
2049    def properties(
2050        self,
2051        properties: exp.Properties,
2052        prefix: str = "",
2053        sep: str = ", ",
2054        suffix: str = "",
2055        wrapped: bool = True,
2056    ) -> str:
2057        if properties.expressions:
2058            expressions = self.expressions(properties, sep=sep, indent=False)
2059            if expressions:
2060                expressions = self.wrap(expressions) if wrapped else expressions
2061                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2062        return ""
2063
2064    def with_properties(self, properties: exp.Properties) -> str:
2065        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
2066
2067    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2068        properties_locs = defaultdict(list)
2069        for p in properties.expressions:
2070            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2071            if p_loc != exp.Properties.Location.UNSUPPORTED:
2072                properties_locs[p_loc].append(p)
2073            else:
2074                self.unsupported(f"Unsupported property {p.key}")
2075
2076        return properties_locs
2077
2078    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2079        if isinstance(expression.this, exp.Dot):
2080            return self.sql(expression, "this")
2081        return f"'{expression.name}'" if string_key else expression.name
2082
2083    def property_sql(self, expression: exp.Property) -> str:
2084        property_cls = expression.__class__
2085        if property_cls == exp.Property:
2086            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2087
2088        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2089        if not property_name:
2090            self.unsupported(f"Unsupported property {expression.key}")
2091
2092        return f"{property_name}={self.sql(expression, 'this')}"
2093
2094    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2095        return f"UUID {self.sql(expression, 'this')}"
2096
2097    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2098        if self.SUPPORTS_CREATE_TABLE_LIKE:
2099            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2100            options = f" {options}" if options else ""
2101
2102            like = f"LIKE {self.sql(expression, 'this')}{options}"
2103            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2104                like = f"({like})"
2105
2106            return like
2107
2108        if expression.expressions:
2109            self.unsupported("Transpilation of LIKE property options is unsupported")
2110
2111        select = exp.select("*").from_(expression.this).limit(0)
2112        return f"AS {self.sql(select)}"
2113
2114    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2115        no = "NO " if expression.args.get("no") else ""
2116        protection = " PROTECTION" if expression.args.get("protection") else ""
2117        return f"{no}FALLBACK{protection}"
2118
2119    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2120        no = "NO " if expression.args.get("no") else ""
2121        local = expression.args.get("local")
2122        local = f"{local} " if local else ""
2123        dual = "DUAL " if expression.args.get("dual") else ""
2124        before = "BEFORE " if expression.args.get("before") else ""
2125        after = "AFTER " if expression.args.get("after") else ""
2126        return f"{no}{local}{dual}{before}{after}JOURNAL"
2127
2128    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2129        freespace = self.sql(expression, "this")
2130        percent = " PERCENT" if expression.args.get("percent") else ""
2131        return f"FREESPACE={freespace}{percent}"
2132
2133    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2134        if expression.args.get("default"):
2135            property = "DEFAULT"
2136        elif expression.args.get("on"):
2137            property = "ON"
2138        else:
2139            property = "OFF"
2140        return f"CHECKSUM={property}"
2141
2142    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2143        if expression.args.get("no"):
2144            return "NO MERGEBLOCKRATIO"
2145        if expression.args.get("default"):
2146            return "DEFAULT MERGEBLOCKRATIO"
2147
2148        percent = " PERCENT" if expression.args.get("percent") else ""
2149        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2150
2151    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2152        expressions = self.expressions(expression, flat=True)
2153        expressions = f"({expressions})" if expressions else ""
2154        return f"USING {self.sql(expression, 'this')}{expressions}"
2155
2156    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2157        default = expression.args.get("default")
2158        minimum = expression.args.get("minimum")
2159        maximum = expression.args.get("maximum")
2160        if default or minimum or maximum:
2161            if default:
2162                prop = "DEFAULT"
2163            elif minimum:
2164                prop = "MINIMUM"
2165            else:
2166                prop = "MAXIMUM"
2167            return f"{prop} DATABLOCKSIZE"
2168        units = expression.args.get("units")
2169        units = f" {units}" if units else ""
2170        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2171
2172    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2173        autotemp = expression.args.get("autotemp")
2174        always = expression.args.get("always")
2175        default = expression.args.get("default")
2176        manual = expression.args.get("manual")
2177        never = expression.args.get("never")
2178
2179        if autotemp is not None:
2180            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2181        elif always:
2182            prop = "ALWAYS"
2183        elif default:
2184            prop = "DEFAULT"
2185        elif manual:
2186            prop = "MANUAL"
2187        elif never:
2188            prop = "NEVER"
2189        return f"BLOCKCOMPRESSION={prop}"
2190
2191    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2192        no = expression.args.get("no")
2193        no = " NO" if no else ""
2194        concurrent = expression.args.get("concurrent")
2195        concurrent = " CONCURRENT" if concurrent else ""
2196        target = self.sql(expression, "target")
2197        target = f" {target}" if target else ""
2198        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2199
2200    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2201        if isinstance(expression.this, list):
2202            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2203        if expression.this:
2204            modulus = self.sql(expression, "this")
2205            remainder = self.sql(expression, "expression")
2206            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2207
2208        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2209        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2210        return f"FROM ({from_expressions}) TO ({to_expressions})"
2211
2212    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2213        this = self.sql(expression, "this")
2214
2215        for_values_or_default = expression.expression
2216        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2217            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2218        else:
2219            for_values_or_default = " DEFAULT"
2220
2221        return f"PARTITION OF {this}{for_values_or_default}"
2222
2223    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2224        kind = expression.args.get("kind")
2225        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2226        for_or_in = expression.args.get("for_or_in")
2227        for_or_in = f" {for_or_in}" if for_or_in else ""
2228        lock_type = expression.args.get("lock_type")
2229        override = " OVERRIDE" if expression.args.get("override") else ""
2230        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2231
2232    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2233        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2234        statistics = expression.args.get("statistics")
2235        statistics_sql = ""
2236        if statistics is not None:
2237            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2238        return f"{data_sql}{statistics_sql}"
2239
2240    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2241        this = self.sql(expression, "this")
2242        this = f"HISTORY_TABLE={this}" if this else ""
2243        data_consistency: str | None = self.sql(expression, "data_consistency")
2244        data_consistency = (
2245            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2246        )
2247        retention_period: str | None = self.sql(expression, "retention_period")
2248        retention_period = (
2249            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2250        )
2251
2252        if this:
2253            on_sql = self.func("ON", this, data_consistency, retention_period)
2254        else:
2255            on_sql = "ON" if expression.args.get("on") else "OFF"
2256
2257        sql = f"SYSTEM_VERSIONING={on_sql}"
2258
2259        return f"WITH({sql})" if expression.args.get("with_") else sql
2260
2261    def insert_sql(self, expression: exp.Insert) -> str:
2262        hint = self.sql(expression, "hint")
2263        overwrite = expression.args.get("overwrite")
2264
2265        if isinstance(expression.this, exp.Directory):
2266            this = " OVERWRITE" if overwrite else " INTO"
2267        else:
2268            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2269
2270        stored = self.sql(expression, "stored")
2271        stored = f" {stored}" if stored else ""
2272        alternative = expression.args.get("alternative")
2273        alternative = f" OR {alternative}" if alternative else ""
2274        ignore = " IGNORE" if expression.args.get("ignore") else ""
2275        is_function = expression.args.get("is_function")
2276        if is_function:
2277            this = f"{this} FUNCTION"
2278        this = f"{this} {self.sql(expression, 'this')}"
2279
2280        exists = " IF EXISTS" if expression.args.get("exists") else ""
2281        where = self.sql(expression, "where")
2282        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2283        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2284        on_conflict = self.sql(expression, "conflict")
2285        on_conflict = f" {on_conflict}" if on_conflict else ""
2286        by_name = " BY NAME" if expression.args.get("by_name") else ""
2287        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2288        returning = self.sql(expression, "returning")
2289
2290        if self.RETURNING_END:
2291            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2292        else:
2293            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2294
2295        partition_by = self.sql(expression, "partition")
2296        partition_by = f" {partition_by}" if partition_by else ""
2297        settings = self.sql(expression, "settings")
2298        settings = f" {settings}" if settings else ""
2299
2300        source = self.sql(expression, "source")
2301        source = f"TABLE {source}" if source else ""
2302
2303        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2304        return self.prepend_ctes(expression, sql)
2305
2306    def introducer_sql(self, expression: exp.Introducer) -> str:
2307        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2308
2309    def kill_sql(self, expression: exp.Kill) -> str:
2310        kind = self.sql(expression, "kind")
2311        kind = f" {kind}" if kind else ""
2312        this = self.sql(expression, "this")
2313        this = f" {this}" if this else ""
2314        return f"KILL{kind}{this}"
2315
2316    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2317        return expression.name
2318
2319    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2320        return expression.name
2321
2322    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2323        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2324
2325        constraint = self.sql(expression, "constraint")
2326        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2327
2328        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2329        if conflict_keys:
2330            conflict_keys = f"({conflict_keys})"
2331
2332        index_predicate = self.sql(expression, "index_predicate")
2333        conflict_keys = f"{conflict_keys}{index_predicate} "
2334
2335        action = self.sql(expression, "action")
2336
2337        expressions = self.expressions(expression, flat=True)
2338        if expressions:
2339            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2340            expressions = f" {set_keyword}{expressions}"
2341
2342        where = self.sql(expression, "where")
2343        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2344
2345    def returning_sql(self, expression: exp.Returning) -> str:
2346        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2347
2348    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2349        fields = self.sql(expression, "fields")
2350        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2351        escaped = self.sql(expression, "escaped")
2352        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2353        items = self.sql(expression, "collection_items")
2354        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2355        keys = self.sql(expression, "map_keys")
2356        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2357        lines = self.sql(expression, "lines")
2358        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2359        null = self.sql(expression, "null")
2360        null = f" NULL DEFINED AS {null}" if null else ""
2361        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2362
2363    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2364        return f"WITH ({self.expressions(expression, flat=True)})"
2365
2366    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2367        this = f"{self.sql(expression, 'this')} INDEX"
2368        target = self.sql(expression, "target")
2369        target = f" FOR {target}" if target else ""
2370        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2371
2372    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2373        this = self.sql(expression, "this")
2374        kind = self.sql(expression, "kind")
2375        expr = self.sql(expression, "expression")
2376        return f"{this} ({kind} => {expr})"
2377
2378    def table_parts(self, expression: exp.Table) -> str:
2379        return ".".join(
2380            self.sql(part)
2381            for part in (
2382                expression.args.get("catalog"),
2383                expression.args.get("db"),
2384                expression.args.get("this"),
2385            )
2386            if part is not None
2387        )
2388
2389    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2390        table = self.table_parts(expression)
2391        only = "ONLY " if expression.args.get("only") else ""
2392        partition = self.sql(expression, "partition")
2393        partition = f" {partition}" if partition else ""
2394        version = self.sql(expression, "version")
2395        version = f" {version}" if version else ""
2396        alias = self.sql(expression, "alias")
2397        alias = f"{sep}{alias}" if alias else ""
2398
2399        sample = self.sql(expression, "sample")
2400        post_alias = ""
2401        pre_alias = ""
2402
2403        if self.dialect.ALIAS_POST_TABLESAMPLE:
2404            pre_alias = sample
2405        else:
2406            post_alias = sample
2407
2408        if self.dialect.ALIAS_POST_VERSION:
2409            pre_alias = f"{pre_alias}{version}"
2410        else:
2411            post_alias = f"{post_alias}{version}"
2412
2413        hints = self.expressions(expression, key="hints", sep=" ")
2414        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2415        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2416        joins = self.indent(
2417            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2418        )
2419        laterals = self.expressions(expression, key="laterals", sep="")
2420
2421        file_format = self.sql(expression, "format")
2422        pattern = self.sql(expression, "pattern")
2423        if file_format:
2424            pattern = f", PATTERN => {pattern}" if pattern else ""
2425            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2426        elif pattern:
2427            file_format = f" (PATTERN => {pattern})"
2428
2429        ordinality = expression.args.get("ordinality") or ""
2430        if ordinality:
2431            ordinality = f" WITH ORDINALITY{alias}"
2432            alias = ""
2433
2434        when = self.sql(expression, "when")
2435        if when:
2436            table = f"{table} {when}"
2437
2438        changes = self.sql(expression, "changes")
2439        changes = f" {changes}" if changes else ""
2440
2441        rows_from = self.expressions(expression, key="rows_from")
2442        if rows_from:
2443            table = f"ROWS FROM {self.wrap(rows_from)}"
2444
2445        indexed = expression.args.get("indexed")
2446        if indexed is not None:
2447            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2448        else:
2449            indexed = ""
2450
2451        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2452
2453    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2454        table = self.func("TABLE", expression.this)
2455        alias = self.sql(expression, "alias")
2456        alias = f" AS {alias}" if alias else ""
2457        sample = self.sql(expression, "sample")
2458        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2459        joins = self.indent(
2460            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2461        )
2462        return f"{table}{alias}{pivots}{sample}{joins}"
2463
2464    def tablesample_sql(
2465        self,
2466        expression: exp.TableSample,
2467        tablesample_keyword: str | None = None,
2468    ) -> str:
2469        method = self.sql(expression, "method")
2470        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2471        numerator = self.sql(expression, "bucket_numerator")
2472        denominator = self.sql(expression, "bucket_denominator")
2473        field = self.sql(expression, "bucket_field")
2474        field = f" ON {field}" if field else ""
2475        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2476        seed = self.sql(expression, "seed")
2477        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2478
2479        size = self.sql(expression, "size")
2480        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2481            size = f"{size} ROWS"
2482
2483        percent = self.sql(expression, "percent")
2484        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2485            percent = f"{percent} PERCENT"
2486
2487        expr = f"{bucket}{percent}{size}"
2488        if self.TABLESAMPLE_REQUIRES_PARENS:
2489            expr = f"({expr})"
2490
2491        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2492
2493    def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None:
2494        # Returns the rewritten field.expressions list with PivotAlias wrappers injected where
2495        # the stored column name differs from the target dialect's natural output.
2496        columns = expression.args.get("columns")
2497        if not columns or len(expression.fields) != 1:
2498            return None
2499
2500        args = expression.args
2501        parser_cls = self.dialect.parser_class
2502
2503        tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS
2504        tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS
2505        tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING
2506
2507        src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings)
2508        src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns)
2509        src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming)
2510
2511        if (
2512            src_identify_pivot_strings == tgt_identify_pivot_strings
2513            and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns
2514            and src_pivot_column_naming == tgt_pivot_column_naming
2515        ):
2516            return None
2517
2518        in_exprs = expression.fields[0].expressions
2519        step = len(columns) // len(in_exprs)
2520
2521        # Derive the per-value suffix from the first stored column vs the first IN-list value.
2522        # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases.
2523        first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name
2524        first_stored = columns[0].name
2525
2526        # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed)
2527        if not first_stored.startswith(first_base):
2528            return None
2529
2530        suffix = first_stored[len(first_base) :]
2531
2532        # Whether the target dialect would append an agg-name suffix for this pivot.
2533        # Spark single-agg uniquely drops the agg alias entirely.
2534        target_has_suffix = (
2535            len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple"
2536        ) and any(a.alias for a in expression.expressions)
2537        source_has_suffix = suffix != ""
2538
2539        new_exprs: list[exp.Expression] = []
2540        modified = False
2541        for val_idx, e in enumerate(in_exprs):
2542            if isinstance(e, exp.PivotAlias):
2543                new_exprs.append(e)
2544                continue
2545
2546            i = val_idx * step
2547            stored_full = columns[i].name
2548            stored_value = stored_full[: -len(suffix)] if suffix else stored_full
2549            target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name
2550
2551            # Source had a suffix, but target won't apply one
2552            if source_has_suffix and not target_has_suffix:
2553                new_exprs.append(
2554                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True))
2555                )
2556                modified = True
2557            # Value-part mismatch (e.g. Snowflake's literal-style values vs others).
2558            elif stored_value != target_value:
2559                new_exprs.append(
2560                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True))
2561                )
2562                modified = True
2563            else:
2564                new_exprs.append(e)
2565
2566        return new_exprs if modified else None
2567
2568    def pivot_sql(self, expression: exp.Pivot) -> str:
2569        expressions = self.expressions(expression, flat=True)
2570        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2571
2572        group = self.sql(expression, "group")
2573
2574        if expression.this:
2575            this = self.sql(expression, "this")
2576            if not expressions:
2577                sql = f"UNPIVOT {this}"
2578            else:
2579                on = f"{self.seg('ON')} {expressions}"
2580                into = self.sql(expression, "into")
2581                into = f"{self.seg('INTO')} {into}" if into else ""
2582                using = self.expressions(expression, key="using", flat=True)
2583                using = f"{self.seg('USING')} {using}" if using else ""
2584                sql = f"{direction} {this}{on}{into}{using}{group}"
2585            return self.prepend_ctes(expression, sql)
2586
2587        if not expression.unpivot:
2588            # Wrap IN-list values with explicit aliases where the target dialect would differ
2589            new_field_exprs = self._pivot_in_value_aliases(expression)
2590            if new_field_exprs is not None:
2591                expression.fields[0].set("expressions", new_field_exprs)
2592
2593        alias = self.sql(expression, "alias")
2594        alias = f" AS {alias}" if alias else ""
2595
2596        fields = self.expressions(
2597            expression,
2598            "fields",
2599            sep=" ",
2600            dynamic=True,
2601            new_line=True,
2602            skip_first=True,
2603            skip_last=True,
2604        )
2605
2606        include_nulls = expression.args.get("include_nulls")
2607        if include_nulls is not None:
2608            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2609        else:
2610            nulls = ""
2611
2612        default_on_null = self.sql(expression, "default_on_null")
2613        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2614        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2615        return self.prepend_ctes(expression, sql)
2616
2617    def version_sql(self, expression: exp.Version) -> str:
2618        this = f"FOR {expression.name}"
2619        kind = expression.text("kind")
2620        expr = self.sql(expression, "expression")
2621        return f"{this} {kind} {expr}"
2622
2623    def tuple_sql(self, expression: exp.Tuple) -> str:
2624        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2625
2626    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2627        """
2628        Returns (join_sql, from_sql) for UPDATE statements.
2629        - join_sql: placed after UPDATE table, before SET
2630        - from_sql: placed after SET clause (standard position)
2631        Dialects like MySQL need to convert FROM to JOIN syntax.
2632        """
2633        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2634            return ("", self.sql(expression, "from_"))
2635
2636        # Qualify unqualified columns in SET clause with the target table
2637        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2638        target_table = expression.this
2639        if isinstance(target_table, exp.Table):
2640            target_name = exp.to_identifier(target_table.alias_or_name)
2641            for eq in expression.expressions:
2642                col = eq.this
2643                if isinstance(col, exp.Column) and not col.table:
2644                    col.set("table", target_name)
2645
2646        table = from_expr.this
2647        if nested_joins := table.args.get("joins", []):
2648            table.set("joins", None)
2649
2650        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2651        for nested in nested_joins:
2652            if not nested.args.get("on") and not nested.args.get("using"):
2653                nested.set("on", exp.true())
2654            join_sql += self.sql(nested)
2655
2656        return (join_sql, "")
2657
2658    def update_sql(self, expression: exp.Update) -> str:
2659        hint = self.sql(expression, "hint")
2660        this = self.sql(expression, "this")
2661        join_sql, from_sql = self._update_from_joins_sql(expression)
2662        set_sql = self.expressions(expression, flat=True)
2663        where_sql = self.sql(expression, "where")
2664        returning = self.sql(expression, "returning")
2665        order = self.sql(expression, "order")
2666        limit = self.sql(expression, "limit")
2667        if self.RETURNING_END:
2668            expression_sql = f"{from_sql}{where_sql}{returning}"
2669        else:
2670            expression_sql = f"{returning}{from_sql}{where_sql}"
2671        options = self.expressions(expression, key="options")
2672        options = f" OPTION({options})" if options else ""
2673        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2674        return self.prepend_ctes(expression, sql)
2675
2676    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2677        values_as_table = values_as_table and self.VALUES_AS_TABLE
2678
2679        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2680        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2681            args = self.expressions(expression)
2682            alias = self.sql(expression, "alias")
2683            values = f"VALUES{self.seg('')}{args}"
2684            values = (
2685                f"({values})"
2686                if self.WRAP_DERIVED_VALUES
2687                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2688                else values
2689            )
2690            values = self.query_modifiers(expression, values)
2691            return f"{values} AS {alias}" if alias else values
2692
2693        # Converts `VALUES...` expression into a series of select unions.
2694        alias_node = expression.args.get("alias")
2695        column_names = alias_node and alias_node.columns
2696
2697        selects: list[exp.Query] = []
2698
2699        for i, tup in enumerate(expression.expressions):
2700            row = tup.expressions
2701
2702            if i == 0 and column_names:
2703                row = [
2704                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2705                ]
2706
2707            selects.append(exp.Select(expressions=row))
2708
2709        if self.pretty:
2710            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2711            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2712            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2713            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2714            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2715
2716        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2717        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2718        return f"({unions}){alias}"
2719
2720    def var_sql(self, expression: exp.Var) -> str:
2721        return self.sql(expression, "this")
2722
2723    @unsupported_args("expressions")
2724    def into_sql(self, expression: exp.Into) -> str:
2725        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2726        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2727        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2728
2729    def from_sql(self, expression: exp.From) -> str:
2730        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2731
2732    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2733        grouping_sets = self.expressions(expression, indent=False)
2734        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2735
2736    def rollup_sql(self, expression: exp.Rollup) -> str:
2737        expressions = self.expressions(expression, indent=False)
2738        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2739
2740    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2741        this = self.sql(expression, "this")
2742
2743        columns = self.expressions(expression, flat=True)
2744
2745        from_sql = self.sql(expression, "from_index")
2746        from_sql = f" FROM {from_sql}" if from_sql else ""
2747
2748        properties = expression.args.get("properties")
2749        properties_sql = (
2750            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2751        )
2752
2753        return f"{this}({columns}){from_sql}{properties_sql}"
2754
2755    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2756        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2757
2758    def cube_sql(self, expression: exp.Cube) -> str:
2759        expressions = self.expressions(expression, indent=False)
2760        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2761
2762    def group_sql(self, expression: exp.Group) -> str:
2763        group_by_all = expression.args.get("all")
2764        if group_by_all is True:
2765            modifier = " ALL"
2766        elif group_by_all is False:
2767            modifier = " DISTINCT"
2768        else:
2769            modifier = ""
2770
2771        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2772
2773        grouping_sets = self.expressions(expression, key="grouping_sets")
2774        cube = self.expressions(expression, key="cube")
2775        rollup = self.expressions(expression, key="rollup")
2776
2777        groupings = csv(
2778            self.seg(grouping_sets) if grouping_sets else "",
2779            self.seg(cube) if cube else "",
2780            self.seg(rollup) if rollup else "",
2781            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2782            sep=self.GROUPINGS_SEP,
2783        )
2784
2785        if (
2786            expression.expressions
2787            and groupings
2788            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2789        ):
2790            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2791
2792        return f"{group_by}{groupings}"
2793
2794    def having_sql(self, expression: exp.Having) -> str:
2795        this = self.indent(self.sql(expression, "this"))
2796        return f"{self.seg('HAVING')}{self.sep()}{this}"
2797
2798    def connect_sql(self, expression: exp.Connect) -> str:
2799        start = self.sql(expression, "start")
2800        start = self.seg(f"START WITH {start}") if start else ""
2801        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2802        connect = self.sql(expression, "connect")
2803        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2804        return start + connect
2805
2806    def prior_sql(self, expression: exp.Prior) -> str:
2807        return f"PRIOR {self.sql(expression, 'this')}"
2808
2809    def join_sql(self, expression: exp.Join) -> str:
2810        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2811            side = None
2812        else:
2813            side = expression.side
2814
2815        op_sql = " ".join(
2816            op
2817            for op in (
2818                expression.method,
2819                "GLOBAL" if expression.args.get("global_") else None,
2820                side,
2821                expression.kind,
2822                expression.hint if self.JOIN_HINTS else None,
2823                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2824            )
2825            if op
2826        )
2827        match_cond = self.sql(expression, "match_condition")
2828        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2829        on_sql = self.sql(expression, "on")
2830        using = expression.args.get("using")
2831
2832        if not on_sql and using:
2833            on_sql = csv(*(self.sql(column) for column in using))
2834
2835        this = expression.this
2836        this_sql = self.sql(this)
2837
2838        exprs = self.expressions(expression)
2839        if exprs:
2840            this_sql = f"{this_sql},{self.seg(exprs)}"
2841
2842        if on_sql:
2843            on_sql = self.indent(on_sql, skip_first=True)
2844            space = self.seg(" " * self.pad) if self.pretty else " "
2845            if using:
2846                on_sql = f"{space}USING ({on_sql})"
2847            else:
2848                on_sql = f"{space}ON {on_sql}"
2849        elif not op_sql:
2850            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2851                return f" {this_sql}"
2852
2853            return f", {this_sql}"
2854
2855        if op_sql != "STRAIGHT_JOIN":
2856            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2857
2858        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2859        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2860
2861    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2862        args = self.expressions(expression, flat=True)
2863        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2864        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2865
2866    def lateral_op(self, expression: exp.Lateral) -> str:
2867        cross_apply = expression.args.get("cross_apply")
2868
2869        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2870        if cross_apply is True:
2871            op = "INNER JOIN "
2872        elif cross_apply is False:
2873            op = "LEFT JOIN "
2874        else:
2875            op = ""
2876
2877        return f"{op}LATERAL"
2878
2879    def lateral_sql(self, expression: exp.Lateral) -> str:
2880        this = self.sql(expression, "this")
2881
2882        if expression.args.get("view"):
2883            alias = expression.args["alias"]
2884            columns = self.expressions(alias, key="columns", flat=True)
2885            table = f" {alias.name}" if alias.name else ""
2886            columns = f" AS {columns}" if columns else ""
2887            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2888            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2889
2890        alias = self.sql(expression, "alias")
2891        alias = f" AS {alias}" if alias else ""
2892
2893        ordinality = expression.args.get("ordinality") or ""
2894        if ordinality:
2895            ordinality = f" WITH ORDINALITY{alias}"
2896            alias = ""
2897
2898        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2899
2900    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2901        this = self.sql(expression, "this")
2902
2903        args = [
2904            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2905            for e in (expression.args.get(k) for k in ("offset", "expression"))
2906            if e
2907        ]
2908
2909        args_sql = ", ".join(self.sql(e) for e in args)
2910        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2911        expressions = self.expressions(expression, flat=True)
2912        limit_options = self.sql(expression, "limit_options")
2913        expressions = f" BY {expressions}" if expressions else ""
2914
2915        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2916
2917    def offset_sql(self, expression: exp.Offset) -> str:
2918        this = self.sql(expression, "this")
2919        value = expression.expression
2920        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2921        expressions = self.expressions(expression, flat=True)
2922        expressions = f" BY {expressions}" if expressions else ""
2923        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2924
2925    def setitem_sql(self, expression: exp.SetItem) -> str:
2926        kind = self.sql(expression, "kind")
2927        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2928            kind = ""
2929        else:
2930            kind = f"{kind} " if kind else ""
2931        this = self.sql(expression, "this")
2932        expressions = self.expressions(expression)
2933        collate = self.sql(expression, "collate")
2934        collate = f" COLLATE {collate}" if collate else ""
2935        global_ = "GLOBAL " if expression.args.get("global_") else ""
2936        return f"{global_}{kind}{this}{expressions}{collate}"
2937
2938    def set_sql(self, expression: exp.Set) -> str:
2939        expressions = f" {self.expressions(expression, flat=True)}"
2940        tag = " TAG" if expression.args.get("tag") else ""
2941        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2942
2943    def queryband_sql(self, expression: exp.QueryBand) -> str:
2944        this = self.sql(expression, "this")
2945        update = " UPDATE" if expression.args.get("update") else ""
2946        scope = self.sql(expression, "scope")
2947        scope = f" FOR {scope}" if scope else ""
2948
2949        return f"QUERY_BAND = {this}{update}{scope}"
2950
2951    def pragma_sql(self, expression: exp.Pragma) -> str:
2952        return f"PRAGMA {self.sql(expression, 'this')}"
2953
2954    def lock_sql(self, expression: exp.Lock) -> str:
2955        if not self.LOCKING_READS_SUPPORTED:
2956            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2957            return ""
2958
2959        update = expression.args["update"]
2960        key = expression.args.get("key")
2961        if update:
2962            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2963        else:
2964            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2965        expressions = self.expressions(expression, flat=True)
2966        expressions = f" OF {expressions}" if expressions else ""
2967        wait = expression.args.get("wait")
2968
2969        if wait is not None:
2970            if isinstance(wait, exp.Literal):
2971                wait = f" WAIT {self.sql(wait)}"
2972            else:
2973                wait = " NOWAIT" if wait else " SKIP LOCKED"
2974
2975        return f"{lock_type}{expressions}{wait or ''}"
2976
2977    def literal_sql(self, expression: exp.Literal) -> str:
2978        text = expression.this or ""
2979        if expression.is_string:
2980            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2981        return text
2982
2983    def escape_str(
2984        self,
2985        text: str,
2986        escape_backslash: bool = True,
2987        delimiter: str | None = None,
2988        escaped_delimiter: str | None = None,
2989        is_byte_string: bool = False,
2990    ) -> str:
2991        if is_byte_string:
2992            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
2993        else:
2994            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
2995
2996        if supports_escape_sequences:
2997            text = "".join(
2998                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
2999                for ch in text
3000            )
3001
3002        delimiter = delimiter or self.dialect.QUOTE_END
3003        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
3004
3005        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3006
3007    def loaddata_sql(self, expression: exp.LoadData) -> str:
3008        is_overwrite = expression.args.get("overwrite")
3009        overwrite = " OVERWRITE" if is_overwrite else ""
3010        this = self.sql(expression, "this")
3011
3012        files = expression.args.get("files")
3013        if files:
3014            files_sql = self.expressions(files, flat=True)
3015            files_sql = f"FILES{self.wrap(files_sql)}"
3016            if is_overwrite:
3017                this = f" {this}"
3018            elif expression.args.get("temp"):
3019                this = f" INTO TEMP TABLE {this}"
3020            else:
3021                this = f" INTO TABLE {this}"
3022            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
3023
3024        local = " LOCAL" if expression.args.get("local") else ""
3025        inpath = f" INPATH {self.sql(expression, 'inpath')}"
3026        this = f" INTO TABLE {this}"
3027        partition = self.sql(expression, "partition")
3028        partition = f" {partition}" if partition else ""
3029        input_format = self.sql(expression, "input_format")
3030        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3031        serde = self.sql(expression, "serde")
3032        serde = f" SERDE {serde}" if serde else ""
3033        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3034
3035    def null_sql(self, *_) -> str:
3036        return "NULL"
3037
3038    def boolean_sql(self, expression: exp.Boolean) -> str:
3039        return "TRUE" if expression.this else "FALSE"
3040
3041    def booland_sql(self, expression: exp.Booland) -> str:
3042        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
3043
3044    def boolor_sql(self, expression: exp.Boolor) -> str:
3045        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
3046
3047    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3048        this = self.sql(expression, "this")
3049        this = f"{this} " if this else this
3050        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3051        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3052
3053    def withfill_sql(self, expression: exp.WithFill) -> str:
3054        from_sql = self.sql(expression, "from_")
3055        from_sql = f" FROM {from_sql}" if from_sql else ""
3056        to_sql = self.sql(expression, "to")
3057        to_sql = f" TO {to_sql}" if to_sql else ""
3058        step_sql = self.sql(expression, "step")
3059        step_sql = f" STEP {step_sql}" if step_sql else ""
3060        interpolated_values = [
3061            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3062            if isinstance(e, exp.Alias)
3063            else self.sql(e, "this")
3064            for e in expression.args.get("interpolate") or []
3065        ]
3066        interpolate = (
3067            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3068        )
3069        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3070
3071    def cluster_sql(self, expression: exp.Cluster) -> str:
3072        return self.op_expressions("CLUSTER BY", expression)
3073
3074    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3075        if expression.this:
3076            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3077            return ""
3078        expressions = self.expressions(expression, flat=True)
3079        return f"CLUSTER BY ({expressions})"
3080
3081    def distribute_sql(self, expression: exp.Distribute) -> str:
3082        return self.op_expressions("DISTRIBUTE BY", expression)
3083
3084    def sort_sql(self, expression: exp.Sort) -> str:
3085        return self.op_expressions("SORT BY", expression)
3086
3087    def _resolve_ordered_for_null_ordering_simulation(
3088        self, expression: exp.Ordered
3089    ) -> exp.Expr | None:
3090        """Resolve a bare ORDER BY name against the enclosing SELECT projection.
3091
3092        Returns the underlying expression of the uniquely-matching projection
3093        (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE
3094        simulation, since the CASE is evaluated in FROM-clause scope rather
3095        than alias scope (MySQL error 1052). Returns None if no safe
3096        substitution applies, leaving the original behaviour unchanged.
3097        """
3098        this = expression.this
3099        if not (isinstance(this, exp.Column) and not this.table):
3100            return None
3101
3102        ancestor = expression.find_ancestor(exp.Select, exp.Window)
3103        if not isinstance(ancestor, exp.Select):
3104            return None
3105
3106        column_name = this.name
3107        matched: list[exp.Expr] = [
3108            p.this if isinstance(p, exp.Alias) else p
3109            for p in ancestor.selects
3110            if p.output_name == column_name
3111        ]
3112        match = matched[0] if len(matched) == 1 else None
3113
3114        # Skip the substitution when it would be identical to the existing
3115        # reference (e.g. ``SELECT col FROM t ORDER BY col``).
3116        if isinstance(match, exp.Column) and not match.table and match.name == column_name:
3117            return None
3118
3119        return match
3120
3121    def ordered_sql(self, expression: exp.Ordered) -> str:
3122        desc = expression.args.get("desc")
3123        asc = not desc
3124
3125        nulls_first = expression.args.get("nulls_first")
3126        nulls_last = not nulls_first
3127        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3128        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3129        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3130
3131        this = self.sql(expression, "this")
3132
3133        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3134        nulls_sort_change = ""
3135        if nulls_first and (
3136            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3137        ):
3138            nulls_sort_change = " NULLS FIRST"
3139        elif (
3140            nulls_last
3141            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3142            and not nulls_are_last
3143        ):
3144            nulls_sort_change = " NULLS LAST"
3145
3146        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3147        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3148            window = expression.find_ancestor(exp.Window, exp.Select)
3149
3150            if isinstance(window, exp.Window):
3151                window_this = window.this
3152                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3153                    window_this = window_this.this
3154                spec = window.args.get("spec")
3155            else:
3156                window_this = None
3157                spec = None
3158
3159            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3160            # without a spec or with a ROWS spec, but not with RANGE
3161            if not (
3162                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3163                and (not spec or spec.text("kind").upper() == "ROWS")
3164            ):
3165                if window_this and spec:
3166                    self.unsupported(
3167                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3168                    )
3169                    nulls_sort_change = ""
3170                elif self.NULL_ORDERING_SUPPORTED is False and (
3171                    (asc and nulls_sort_change == " NULLS LAST")
3172                    or (desc and nulls_sort_change == " NULLS FIRST")
3173                ):
3174                    # BigQuery does not allow these ordering/nulls combinations when used under
3175                    # an aggregation func or under a window containing one
3176                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3177
3178                    if isinstance(ancestor, exp.Window):
3179                        ancestor = ancestor.this
3180                    if isinstance(ancestor, exp.AggFunc):
3181                        self.unsupported(
3182                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3183                        )
3184                        nulls_sort_change = ""
3185                elif self.NULL_ORDERING_SUPPORTED is None:
3186                    if expression.this.is_int:
3187                        self.unsupported(
3188                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3189                        )
3190                    elif not isinstance(expression.this, exp.Rand):
3191                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3192                        target = self.sql(resolved) if resolved is not None else this
3193                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3194                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3195                    nulls_sort_change = ""
3196
3197        with_fill = self.sql(expression, "with_fill")
3198        with_fill = f" {with_fill}" if with_fill else ""
3199
3200        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
3201
3202    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3203        window_frame = self.sql(expression, "window_frame")
3204        window_frame = f"{window_frame} " if window_frame else ""
3205
3206        this = self.sql(expression, "this")
3207
3208        return f"{window_frame}{this}"
3209
3210    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3211        partition = self.partition_by_sql(expression)
3212        order = self.sql(expression, "order")
3213        measures = self.expressions(expression, key="measures")
3214        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3215        rows = self.sql(expression, "rows")
3216        rows = self.seg(rows) if rows else ""
3217        after = self.sql(expression, "after")
3218        after = self.seg(after) if after else ""
3219        pattern = self.sql(expression, "pattern")
3220        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3221        definition_sqls = [
3222            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3223            for definition in expression.args.get("define", [])
3224        ]
3225        definitions = self.expressions(sqls=definition_sqls)
3226        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3227        body = "".join(
3228            (
3229                partition,
3230                order,
3231                measures,
3232                rows,
3233                after,
3234                pattern,
3235                define,
3236            )
3237        )
3238        alias = self.sql(expression, "alias")
3239        alias = f" {alias}" if alias else ""
3240        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3241
3242    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3243        limit = expression.args.get("limit")
3244
3245        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3246            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
3247        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
3248            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3249
3250        return csv(
3251            *sqls,
3252            *[self.sql(join) for join in expression.args.get("joins") or []],
3253            self.sql(expression, "match"),
3254            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3255            self.sql(expression, "prewhere"),
3256            self.sql(expression, "where"),
3257            self.sql(expression, "connect"),
3258            self.sql(expression, "group"),
3259            self.sql(expression, "having"),
3260            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3261            self.sql(expression, "order"),
3262            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3263            *self.after_limit_modifiers(expression),
3264            self.options_modifier(expression),
3265            self.sql(expression, "for_"),
3266            sep="",
3267        )
3268
3269    def options_modifier(self, expression: exp.Expr) -> str:
3270        options = self.expressions(expression, key="options")
3271        return f" {options}" if options else ""
3272
3273    def forclause_sql(self, expression: exp.ForClause) -> str:
3274        kind = expression.args["kind"]
3275        if kind == "BROWSE":
3276            return f"{self.sep()}FOR BROWSE"
3277        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3278        # the target dialect doesn't support QueryOption, so we drop the clause.
3279        options = self.expressions(expression, key="expressions")
3280        if not options:
3281            return ""
3282        return f"{self.sep()}FOR {kind}{self.seg(options)}"
3283
3284    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3285        self.unsupported("Unsupported query option.")
3286        return ""
3287
3288    def offset_limit_modifiers(
3289        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3290    ) -> list[str]:
3291        return [
3292            self.sql(expression, "offset") if fetch else self.sql(limit),
3293            self.sql(limit) if fetch else self.sql(expression, "offset"),
3294        ]
3295
3296    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3297        locks = self.expressions(expression, key="locks", sep=" ")
3298        locks = f" {locks}" if locks else ""
3299        return [locks, self.sql(expression, "sample")]
3300
3301    def select_sql(self, expression: exp.Select) -> str:
3302        into = expression.args.get("into")
3303        if not self.SUPPORTS_SELECT_INTO and into:
3304            into.pop()
3305
3306        hint = self.sql(expression, "hint")
3307        distinct = self.sql(expression, "distinct")
3308        distinct = f" {distinct}" if distinct else ""
3309        kind = self.sql(expression, "kind")
3310
3311        limit = expression.args.get("limit")
3312        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3313            top = self.limit_sql(limit, top=True)
3314            limit.pop()
3315        else:
3316            top = ""
3317
3318        expressions = self.expressions(expression)
3319
3320        if kind:
3321            if kind in self.SELECT_KINDS:
3322                kind = f" AS {kind}"
3323            else:
3324                if kind == "STRUCT":
3325                    expressions = self.expressions(
3326                        sqls=[
3327                            self.sql(
3328                                exp.Struct(
3329                                    expressions=[
3330                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3331                                        if isinstance(e, exp.Alias)
3332                                        else e
3333                                        for e in expression.expressions
3334                                    ]
3335                                )
3336                            )
3337                        ]
3338                    )
3339                kind = ""
3340
3341        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3342        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3343
3344        exclude = expression.args.get("exclude")
3345
3346        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3347            exclude_sql = self.expressions(sqls=exclude, flat=True)
3348            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3349
3350        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3351        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3352        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3353        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3354        sql = self.query_modifiers(
3355            expression,
3356            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3357            self.sql(expression, "into", comment=False),
3358            self.sql(expression, "from_", comment=False),
3359        )
3360
3361        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3362        if expression.args.get("with_"):
3363            sql = self.maybe_comment(sql, expression)
3364            expression.pop_comments()
3365
3366        sql = self.prepend_ctes(expression, sql)
3367
3368        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3369            expression.set("exclude", None)
3370            subquery = expression.subquery(copy=False)
3371            star = exp.Star(except_=exclude)
3372            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3373
3374        if not self.SUPPORTS_SELECT_INTO and into:
3375            if into.args.get("temporary"):
3376                table_kind = " TEMPORARY"
3377            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3378                table_kind = " UNLOGGED"
3379            else:
3380                table_kind = ""
3381            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3382
3383        return sql
3384
3385    def schema_sql(self, expression: exp.Schema) -> str:
3386        this = self.sql(expression, "this")
3387        sql = self.schema_columns_sql(expression)
3388        return f"{this} {sql}" if this and sql else this or sql
3389
3390    def schema_columns_sql(self, expression: exp.Expr) -> str:
3391        if expression.expressions:
3392            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3393        return ""
3394
3395    def star_sql(self, expression: exp.Star) -> str:
3396        except_ = self.expressions(expression, key="except_", flat=True)
3397        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3398        replace = self.expressions(expression, key="replace", flat=True)
3399        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3400        rename = self.expressions(expression, key="rename", flat=True)
3401        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3402        ilike = self.sql(expression, "ilike")
3403        ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else ""
3404        return f"*{ilike}{except_}{replace}{rename}"
3405
3406    def parameter_sql(self, expression: exp.Parameter) -> str:
3407        this = self.sql(expression, "this")
3408        return f"{self.PARAMETER_TOKEN}{this}"
3409
3410    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3411        this = self.sql(expression, "this")
3412        kind = expression.text("kind")
3413        if kind:
3414            kind = f"{kind}."
3415        return f"@@{kind}{this}"
3416
3417    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3418        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3419
3420    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3421        alias = self.sql(expression, "alias")
3422        alias = f"{sep}{alias}" if alias else ""
3423        sample = self.sql(expression, "sample")
3424        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3425            alias = f"{sample}{alias}"
3426
3427            # Set to None so it's not generated again by self.query_modifiers()
3428            expression.set("sample", None)
3429
3430        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3431        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3432        return self.prepend_ctes(expression, sql)
3433
3434    def qualify_sql(self, expression: exp.Qualify) -> str:
3435        this = self.indent(self.sql(expression, "this"))
3436        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3437
3438    def unnest_sql(self, expression: exp.Unnest) -> str:
3439        args = self.expressions(expression, flat=True)
3440
3441        alias = expression.args.get("alias")
3442        offset = expression.args.get("offset")
3443
3444        if self.UNNEST_WITH_ORDINALITY:
3445            if alias and isinstance(offset, exp.Expr):
3446                alias.append("columns", offset)
3447                expression.set("offset", None)
3448
3449        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3450            columns = alias.columns
3451            alias = self.sql(columns[0]) if columns else ""
3452        else:
3453            alias = self.sql(alias)
3454
3455        alias = f" AS {alias}" if alias else alias
3456        if self.UNNEST_WITH_ORDINALITY:
3457            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3458        else:
3459            if isinstance(offset, exp.Expr):
3460                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3461            elif offset:
3462                suffix = f"{alias} WITH OFFSET"
3463            else:
3464                suffix = alias
3465
3466        return f"UNNEST({args}){suffix}"
3467
3468    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3469        return ""
3470
3471    def where_sql(self, expression: exp.Where) -> str:
3472        this = self.indent(self.sql(expression, "this"))
3473        return f"{self.seg('WHERE')}{self.sep()}{this}"
3474
3475    def window_sql(self, expression: exp.Window) -> str:
3476        this = self.sql(expression, "this")
3477        partition = self.partition_by_sql(expression)
3478        order = expression.args.get("order")
3479        order = self.order_sql(order, flat=True) if order else ""
3480        spec = self.sql(expression, "spec")
3481        alias = self.sql(expression, "alias")
3482        over = self.sql(expression, "over") or "OVER"
3483
3484        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3485
3486        first = expression.args.get("first")
3487        if first is None:
3488            first = ""
3489        else:
3490            first = "FIRST" if first else "LAST"
3491
3492        if not partition and not order and not spec and alias:
3493            return f"{this} {alias}"
3494
3495        args = self.format_args(
3496            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3497        )
3498        return f"{this} ({args})"
3499
3500    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3501        partition = self.expressions(expression, key="partition_by", flat=True)
3502        return f"PARTITION BY {partition}" if partition else ""
3503
3504    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3505        kind = self.sql(expression, "kind")
3506        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3507        end = (
3508            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3509            or "CURRENT ROW"
3510        )
3511
3512        window_spec = f"{kind} BETWEEN {start} AND {end}"
3513
3514        exclude = self.sql(expression, "exclude")
3515        if exclude:
3516            if self.SUPPORTS_WINDOW_EXCLUDE:
3517                window_spec += f" EXCLUDE {exclude}"
3518            else:
3519                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3520
3521        return window_spec
3522
3523    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3524        this = self.sql(expression, "this")
3525        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3526        return f"{this} WITHIN GROUP ({expression_sql})"
3527
3528    def between_sql(self, expression: exp.Between) -> str:
3529        this = self.sql(expression, "this")
3530        low = self.sql(expression, "low")
3531        high = self.sql(expression, "high")
3532        symmetric = expression.args.get("symmetric")
3533
3534        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3535            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3536
3537        flag = (
3538            " SYMMETRIC"
3539            if symmetric
3540            else " ASYMMETRIC"
3541            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3542            else ""  # silently drop ASYMMETRIC – semantics identical
3543        )
3544        return f"{this} BETWEEN{flag} {low} AND {high}"
3545
3546    def bracket_offset_expressions(
3547        self, expression: exp.Bracket, index_offset: int | None = None
3548    ) -> list[exp.Expr]:
3549        if expression.args.get("json_access"):
3550            return expression.expressions
3551
3552        return apply_index_offset(
3553            expression.this,
3554            expression.expressions,
3555            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3556            dialect=self.dialect,
3557        )
3558
3559    def bracket_sql(self, expression: exp.Bracket) -> str:
3560        expressions = self.bracket_offset_expressions(expression)
3561        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3562        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3563
3564    def all_sql(self, expression: exp.All) -> str:
3565        this = self.sql(expression, "this")
3566        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3567            this = self.wrap(this)
3568        return f"ALL {this}"
3569
3570    def any_sql(self, expression: exp.Any) -> str:
3571        this = self.sql(expression, "this")
3572        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3573            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3574                this = self.wrap(this)
3575            return f"ANY{this}"
3576        return f"ANY {this}"
3577
3578    def exists_sql(self, expression: exp.Exists) -> str:
3579        return f"EXISTS{self.wrap(expression)}"
3580
3581    def case_sql(self, expression: exp.Case) -> str:
3582        this = self.sql(expression, "this")
3583        statements = [f"CASE {this}" if this else "CASE"]
3584
3585        for e in expression.args["ifs"]:
3586            statements.append(f"WHEN {self.sql(e, 'this')}")
3587            statements.append(f"THEN {self.sql(e, 'true')}")
3588
3589        default = self.sql(expression, "default")
3590
3591        if default:
3592            statements.append(f"ELSE {default}")
3593
3594        statements.append("END")
3595
3596        if self.pretty and self.too_wide(statements):
3597            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3598
3599        return " ".join(statements)
3600
3601    def constraint_sql(self, expression: exp.Constraint) -> str:
3602        this = self.sql(expression, "this")
3603        expressions = self.expressions(expression, flat=True)
3604        return f"CONSTRAINT {this} {expressions}"
3605
3606    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3607        order = expression.args.get("order")
3608        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3609        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3610
3611    def extract_sql(self, expression: exp.Extract) -> str:
3612        import sqlglot.dialects.dialect
3613
3614        this = (
3615            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3616            if self.NORMALIZE_EXTRACT_DATE_PARTS
3617            else expression.this
3618        )
3619        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3620        expression_sql = self.sql(expression, "expression")
3621
3622        return f"EXTRACT({this_sql} FROM {expression_sql})"
3623
3624    def trim_sql(self, expression: exp.Trim) -> str:
3625        trim_type = self.sql(expression, "position")
3626
3627        if trim_type == "LEADING":
3628            func_name = "LTRIM"
3629        elif trim_type == "TRAILING":
3630            func_name = "RTRIM"
3631        else:
3632            func_name = "TRIM"
3633
3634        return self.func(func_name, expression.this, expression.expression)
3635
3636    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3637        args = expression.expressions
3638        if isinstance(expression, exp.ConcatWs):
3639            args = args[1:]  # Skip the delimiter
3640
3641        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3642            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3643
3644        concat_coalesce = (
3645            self.dialect.CONCAT_WS_COALESCE
3646            if isinstance(expression, exp.ConcatWs)
3647            else self.dialect.CONCAT_COALESCE
3648        )
3649
3650        if not concat_coalesce and expression.args.get("coalesce"):
3651
3652            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3653                if not e.type:
3654                    import sqlglot.optimizer.annotate_types
3655
3656                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3657
3658                if e.is_string or e.is_type(exp.DType.ARRAY):
3659                    return e
3660
3661                return exp.func("coalesce", e, exp.Literal.string(""))
3662
3663            args = [_wrap_with_coalesce(e) for e in args]
3664
3665        return args
3666
3667    def concat_sql(self, expression: exp.Concat) -> str:
3668        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3669            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3670            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3671            # instead of coalescing them to empty string.
3672            import sqlglot.dialects.dialect
3673
3674            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3675
3676        expressions = self.convert_concat_args(expression)
3677
3678        # Some dialects don't allow a single-argument CONCAT call
3679        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3680            return self.sql(expressions[0])
3681
3682        return self.func("CONCAT", *expressions)
3683
3684    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3685        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3686            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3687            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3688            all_args = expression.expressions
3689            expression.set("coalesce", True)
3690            return self.sql(
3691                exp.case()
3692                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3693                .else_(expression)
3694            )
3695
3696        return self.func(
3697            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3698        )
3699
3700    def check_sql(self, expression: exp.Check) -> str:
3701        this = self.sql(expression, key="this")
3702        return f"CHECK ({this})"
3703
3704    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3705        expressions = self.expressions(expression, flat=True)
3706        expressions = f" ({expressions})" if expressions else ""
3707        reference = self.sql(expression, "reference")
3708        reference = f" {reference}" if reference else ""
3709        delete = self.sql(expression, "delete")
3710        delete = f" ON DELETE {delete}" if delete else ""
3711        update = self.sql(expression, "update")
3712        update = f" ON UPDATE {update}" if update else ""
3713        options = self.expressions(expression, key="options", flat=True, sep=" ")
3714        options = f" {options}" if options else ""
3715        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3716
3717    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3718        this = self.sql(expression, "this")
3719        this = f" {this}" if this else ""
3720        expressions = self.expressions(expression, flat=True)
3721        include = self.sql(expression, "include")
3722        options = self.expressions(expression, key="options", flat=True, sep=" ")
3723        options = f" {options}" if options else ""
3724        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3725
3726    def if_sql(self, expression: exp.If) -> str:
3727        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3728
3729    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3730        if self.MATCH_AGAINST_TABLE_PREFIX:
3731            expressions = []
3732            for expr in expression.expressions:
3733                if isinstance(expr, exp.Table):
3734                    expressions.append(f"TABLE {self.sql(expr)}")
3735                else:
3736                    expressions.append(expr)
3737        else:
3738            expressions = expression.expressions
3739
3740        modifier = expression.args.get("modifier")
3741        modifier = f" {modifier}" if modifier else ""
3742        return (
3743            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3744        )
3745
3746    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3747        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3748
3749    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3750        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3751
3752        if self.QUOTE_JSON_PATH:
3753            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3754
3755        return path
3756
3757    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3758        if isinstance(expression, exp.JSONPathPart):
3759            transform = self.TRANSFORMS.get(expression.__class__)
3760            if not callable(transform):
3761                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3762                return ""
3763
3764            return transform(self, expression)
3765
3766        if isinstance(expression, int):
3767            return str(expression)
3768
3769        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3770            escaped = expression.replace("'", "\\'")
3771            escaped = f"\\'{expression}\\'"
3772        else:
3773            escaped = expression.replace('"', '\\"')
3774            escaped = f'"{escaped}"'
3775
3776        return escaped
3777
3778    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3779        return f"{self.sql(expression, 'this')} FORMAT JSON"
3780
3781    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3782        # Output the Teradata column FORMAT override.
3783        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3784        this = self.sql(expression, "this")
3785        fmt = self.sql(expression, "format")
3786        return f"{this} (FORMAT {fmt})"
3787
3788    def _jsonobject_sql(
3789        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
3790    ) -> str:
3791        null_handling = expression.args.get("null_handling")
3792        null_handling = f" {null_handling}" if null_handling else ""
3793
3794        unique_keys = expression.args.get("unique_keys")
3795        if unique_keys is not None:
3796            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3797        else:
3798            unique_keys = ""
3799
3800        return_type = self.sql(expression, "return_type")
3801        return_type = f" RETURNING {return_type}" if return_type else ""
3802        encoding = self.sql(expression, "encoding")
3803        encoding = f" ENCODING {encoding}" if encoding else ""
3804
3805        if not name:
3806            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
3807
3808        return self.func(
3809            name,
3810            *expression.expressions,
3811            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3812        )
3813
3814    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3815        null_handling = expression.args.get("null_handling")
3816        null_handling = f" {null_handling}" if null_handling else ""
3817        return_type = self.sql(expression, "return_type")
3818        return_type = f" RETURNING {return_type}" if return_type else ""
3819        strict = " STRICT" if expression.args.get("strict") else ""
3820        return self.func(
3821            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3822        )
3823
3824    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3825        this = self.sql(expression, "this")
3826        order = self.sql(expression, "order")
3827        null_handling = expression.args.get("null_handling")
3828        null_handling = f" {null_handling}" if null_handling else ""
3829        return_type = self.sql(expression, "return_type")
3830        return_type = f" RETURNING {return_type}" if return_type else ""
3831        strict = " STRICT" if expression.args.get("strict") else ""
3832        return self.func(
3833            "JSON_ARRAYAGG",
3834            this,
3835            suffix=f"{order}{null_handling}{return_type}{strict})",
3836        )
3837
3838    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3839        path = self.sql(expression, "path")
3840        path = f" PATH {path}" if path else ""
3841        nested_schema = self.sql(expression, "nested_schema")
3842
3843        if nested_schema:
3844            return f"NESTED{path} {nested_schema}"
3845
3846        this = self.sql(expression, "this")
3847        kind = self.sql(expression, "kind")
3848        kind = f" {kind}" if kind else ""
3849        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3850
3851        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3852        return f"{this}{kind}{format_json}{path}{ordinality}"
3853
3854    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3855        return self.func("COLUMNS", *expression.expressions)
3856
3857    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3858        this = self.sql(expression, "this")
3859        path = self.sql(expression, "path")
3860        path = f", {path}" if path else ""
3861        error_handling = expression.args.get("error_handling")
3862        error_handling = f" {error_handling}" if error_handling else ""
3863        empty_handling = expression.args.get("empty_handling")
3864        empty_handling = f" {empty_handling}" if empty_handling else ""
3865        schema = self.sql(expression, "schema")
3866        return self.func(
3867            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3868        )
3869
3870    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3871        this = self.sql(expression, "this")
3872        kind = self.sql(expression, "kind")
3873        path = self.sql(expression, "path")
3874        path = f" {path}" if path else ""
3875        as_json = " AS JSON" if expression.args.get("as_json") else ""
3876        return f"{this} {kind}{path}{as_json}"
3877
3878    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3879        this = self.sql(expression, "this")
3880        path = self.sql(expression, "path")
3881        path = f", {path}" if path else ""
3882        expressions = self.expressions(expression)
3883        with_ = (
3884            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3885            if expressions
3886            else ""
3887        )
3888        return f"OPENJSON({this}{path}){with_}"
3889
3890    def in_sql(self, expression: exp.In) -> str:
3891        query = expression.args.get("query")
3892        unnest = expression.args.get("unnest")
3893        field = expression.args.get("field")
3894        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3895
3896        if query:
3897            in_sql = self.sql(query)
3898        elif unnest:
3899            in_sql = self.in_unnest_op(unnest)
3900        elif field:
3901            in_sql = self.sql(field)
3902        else:
3903            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3904
3905        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3906
3907    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3908        return f"(SELECT {self.sql(unnest)})"
3909
3910    def interval_sql(self, expression: exp.Interval) -> str:
3911        unit_expression = expression.args.get("unit")
3912        unit = self.sql(unit_expression) if unit_expression else ""
3913        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3914            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3915        unit = f" {unit}" if unit else ""
3916
3917        if self.SINGLE_STRING_INTERVAL:
3918            this = expression.this.name if expression.this else ""
3919            if this:
3920                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3921                    return f"INTERVAL '{this}'{unit}"
3922                return f"INTERVAL '{this}{unit}'"
3923            return f"INTERVAL{unit}"
3924
3925        this = self.sql(expression, "this")
3926        if this:
3927            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3928            this = f" {this}" if unwrapped else f" ({this})"
3929
3930        return f"INTERVAL{this}{unit}"
3931
3932    def return_sql(self, expression: exp.Return) -> str:
3933        return f"RETURN {self.sql(expression, 'this')}"
3934
3935    def reference_sql(self, expression: exp.Reference) -> str:
3936        this = self.sql(expression, "this")
3937        expressions = self.expressions(expression, flat=True)
3938        expressions = f"({expressions})" if expressions else ""
3939        options = self.expressions(expression, key="options", flat=True, sep=" ")
3940        options = f" {options}" if options else ""
3941        return f"REFERENCES {this}{expressions}{options}"
3942
3943    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3944        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3945        parent = expression.parent
3946        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3947
3948        return self.func(
3949            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3950        )
3951
3952    def paren_sql(self, expression: exp.Paren) -> str:
3953        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3954        return f"({sql}{self.seg(')', sep='')}"
3955
3956    def neg_sql(self, expression: exp.Neg) -> str:
3957        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3958        this_sql = self.sql(expression, "this")
3959        sep = " " if this_sql[0] == "-" else ""
3960        return f"-{sep}{this_sql}"
3961
3962    def not_sql(self, expression: exp.Not) -> str:
3963        return f"NOT {self.sql(expression, 'this')}"
3964
3965    def alias_sql(self, expression: exp.Alias) -> str:
3966        alias = self.sql(expression, "alias")
3967        alias = f" AS {alias}" if alias else ""
3968        return f"{self.sql(expression, 'this')}{alias}"
3969
3970    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3971        alias = expression.args["alias"]
3972
3973        parent = expression.parent
3974        pivot = parent and parent.parent
3975
3976        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3977            identifier_alias = isinstance(alias, exp.Identifier)
3978            literal_alias = isinstance(alias, exp.Literal)
3979
3980            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3981                alias.replace(exp.Literal.string(alias.output_name))
3982            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3983                alias.replace(exp.to_identifier(alias.output_name))
3984
3985        return self.alias_sql(expression)
3986
3987    def aliases_sql(self, expression: exp.Aliases) -> str:
3988        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3989
3990    def atindex_sql(self, expression: exp.AtIndex) -> str:
3991        this = self.sql(expression, "this")
3992        index = self.sql(expression, "expression")
3993        return f"{this} AT {index}"
3994
3995    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3996        this = self.sql(expression, "this")
3997        zone = self.sql(expression, "zone")
3998        return f"{this} AT TIME ZONE {zone}"
3999
4000    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
4001        this = self.sql(expression, "this")
4002        zone = self.sql(expression, "zone")
4003        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
4004
4005    def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str:
4006        return self.sql(exp.cast(expression.this, exp.DType.DATE))
4007
4008    def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:
4009        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4010
4011    def add_sql(self, expression: exp.Add) -> str:
4012        return self.binary(expression, "+")
4013
4014    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
4015        return self.connector_sql(expression, "AND", stack)
4016
4017    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
4018        return self.connector_sql(expression, "OR", stack)
4019
4020    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
4021        return self.connector_sql(expression, "XOR", stack)
4022
4023    def connector_sql(
4024        self,
4025        expression: exp.Connector,
4026        op: str,
4027        stack: list[str | exp.Expr] | None = None,
4028    ) -> str:
4029        if stack is not None:
4030            if expression.expressions:
4031                stack.append(self.expressions(expression, sep=f" {op} "))
4032            else:
4033                stack.append(expression.right)
4034                if expression.comments and self.comments:
4035                    op = self.maybe_comment(op, comments=expression.comments)
4036                stack.extend((op, expression.left))
4037            return op
4038
4039        stack = [expression]
4040        sqls: list[str] = []
4041        ops = set()
4042
4043        while stack:
4044            node = stack.pop()
4045            if isinstance(node, exp.Connector):
4046                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4047            else:
4048                sql = self.sql(node)
4049                if sqls and sqls[-1] in ops:
4050                    sqls[-1] += f" {sql}"
4051                else:
4052                    sqls.append(sql)
4053
4054        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4055        return sep.join(sqls)
4056
4057    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4058        return self.binary(expression, "&")
4059
4060    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4061        return self.binary(expression, "<<")
4062
4063    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4064        return f"~{self.sql(expression, 'this')}"
4065
4066    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4067        return self.binary(expression, "|")
4068
4069    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4070        return self.binary(expression, ">>")
4071
4072    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4073        return self.binary(expression, "^")
4074
4075    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4076        format_sql = self.sql(expression, "format")
4077        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4078        to_sql = self.sql(expression, "to")
4079        to_sql = f" {to_sql}" if to_sql else ""
4080        action = self.sql(expression, "action")
4081        action = f" {action}" if action else ""
4082        default = self.sql(expression, "default")
4083        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4084        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4085
4086    # Base implementation that excludes safe, zone, and target_type metadata args
4087    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4088        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
4089
4090    def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str:
4091        return self.func(
4092            "PARSE_DATETIME",
4093            expression.this,
4094            expression.args.get("format"),
4095            expression.args.get("zone"),
4096        )
4097
4098    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4099        zone = self.sql(expression, "this")
4100        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
4101
4102    def collate_sql(self, expression: exp.Collate) -> str:
4103        if self.COLLATE_IS_FUNC:
4104            return self.function_fallback_sql(expression)
4105        return self.binary(expression, "COLLATE")
4106
4107    def command_sql(self, expression: exp.Command) -> str:
4108        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
4109
4110    def comment_sql(self, expression: exp.Comment) -> str:
4111        this = self.sql(expression, "this")
4112        kind = expression.args["kind"]
4113        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4114        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4115        expression_sql = self.sql(expression, "expression")
4116        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4117
4118    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4119        this = self.sql(expression, "this")
4120        delete = " DELETE" if expression.args.get("delete") else ""
4121        recompress = self.sql(expression, "recompress")
4122        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4123        to_disk = self.sql(expression, "to_disk")
4124        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4125        to_volume = self.sql(expression, "to_volume")
4126        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4127        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4128
4129    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4130        where = self.sql(expression, "where")
4131        group = self.sql(expression, "group")
4132        aggregates = self.expressions(expression, key="aggregates")
4133        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4134
4135        if not (where or group or aggregates) and len(expression.expressions) == 1:
4136            return f"TTL {self.expressions(expression, flat=True)}"
4137
4138        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4139
4140    def transaction_sql(self, expression: exp.Transaction) -> str:
4141        modes = self.expressions(expression, key="modes")
4142        modes = f" {modes}" if modes else ""
4143        return f"BEGIN{modes}"
4144
4145    def commit_sql(self, expression: exp.Commit) -> str:
4146        chain = expression.args.get("chain")
4147        if chain is not None:
4148            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4149
4150        return f"COMMIT{chain or ''}"
4151
4152    def rollback_sql(self, expression: exp.Rollback) -> str:
4153        savepoint = expression.args.get("savepoint")
4154        savepoint = f" TO {savepoint}" if savepoint else ""
4155        return f"ROLLBACK{savepoint}"
4156
4157    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4158        this = self.sql(expression, "this")
4159
4160        dtype = self.sql(expression, "dtype")
4161        if dtype:
4162            collate = self.sql(expression, "collate")
4163            collate = f" COLLATE {collate}" if collate else ""
4164            using = self.sql(expression, "using")
4165            using = f" USING {using}" if using else ""
4166            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4167            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
4168
4169        default = self.sql(expression, "default")
4170        if default:
4171            return f"ALTER COLUMN {this} SET DEFAULT {default}"
4172
4173        comment = self.sql(expression, "comment")
4174        if comment:
4175            return f"ALTER COLUMN {this} COMMENT {comment}"
4176
4177        visible = expression.args.get("visible")
4178        if visible:
4179            return f"ALTER COLUMN {this} SET {visible}"
4180
4181        allow_null = expression.args.get("allow_null")
4182        drop = expression.args.get("drop")
4183
4184        if not drop and not allow_null:
4185            self.unsupported("Unsupported ALTER COLUMN syntax")
4186
4187        if allow_null is not None:
4188            keyword = "DROP" if drop else "SET"
4189            return f"ALTER COLUMN {this} {keyword} NOT NULL"
4190
4191        return f"ALTER COLUMN {this} DROP DEFAULT"
4192
4193    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4194        this = self.sql(expression, "this")
4195        rename_from = self.sql(expression, "rename_from")
4196        if rename_from:
4197            if not self.SUPPORTS_CHANGE_COLUMN:
4198                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4199            return f"CHANGE COLUMN {rename_from} {this}"
4200        if not self.SUPPORTS_MODIFY_COLUMN:
4201            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4202        return f"MODIFY COLUMN {this}"
4203
4204    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4205        this = self.sql(expression, "this")
4206
4207        visible = expression.args.get("visible")
4208        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4209
4210        return f"ALTER INDEX {this} {visible_sql}"
4211
4212    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4213        this = self.sql(expression, "this")
4214        if not isinstance(expression.this, exp.Var):
4215            this = f"KEY DISTKEY {this}"
4216        return f"ALTER DISTSTYLE {this}"
4217
4218    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4219        compound = " COMPOUND" if expression.args.get("compound") else ""
4220        this = self.sql(expression, "this")
4221        expressions = self.expressions(expression, flat=True)
4222        expressions = f"({expressions})" if expressions else ""
4223        return f"ALTER{compound} SORTKEY {this or expressions}"
4224
4225    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4226        if not self.RENAME_TABLE_WITH_DB:
4227            # Remove db from tables
4228            expression = expression.transform(
4229                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4230            ).assert_is(exp.AlterRename)
4231        this = self.sql(expression, "this")
4232        to_kw = " TO" if include_to else ""
4233        return f"RENAME{to_kw} {this}"
4234
4235    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4236        exists = " IF EXISTS" if expression.args.get("exists") else ""
4237        old_column = self.sql(expression, "this")
4238        new_column = self.sql(expression, "to")
4239        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
4240
4241    def alterset_sql(self, expression: exp.AlterSet) -> str:
4242        exprs = self.expressions(expression, flat=True)
4243        if self.ALTER_SET_WRAPPED:
4244            exprs = f"({exprs})"
4245
4246        return f"SET {exprs}"
4247
4248    def alter_sql(self, expression: exp.Alter) -> str:
4249        actions = expression.args["actions"]
4250
4251        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
4252            actions[0], exp.ColumnDef
4253        ):
4254            actions_sql = self.expressions(expression, key="actions", flat=True)
4255            actions_sql = f"ADD {actions_sql}"
4256        else:
4257            actions_list = []
4258            for action in actions:
4259                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4260                    action_sql = self.add_column_sql(action)
4261                else:
4262                    action_sql = self.sql(action)
4263                    if isinstance(action, exp.Query):
4264                        action_sql = f"AS {action_sql}"
4265
4266                actions_list.append(action_sql)
4267
4268            actions_sql = self.format_args(*actions_list).lstrip("\n")
4269
4270        iceberg = (
4271            "ICEBERG "
4272            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4273            else ""
4274        )
4275        exists = " IF EXISTS" if expression.args.get("exists") else ""
4276        on_cluster = self.sql(expression, "cluster")
4277        on_cluster = f" {on_cluster}" if on_cluster else ""
4278        only = " ONLY" if expression.args.get("only") else ""
4279        options = self.expressions(expression, key="options")
4280        options = f", {options}" if options else ""
4281        kind = self.sql(expression, "kind")
4282        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4283        check = " WITH CHECK" if expression.args.get("check") else ""
4284        cascade = (
4285            " CASCADE"
4286            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4287            else ""
4288        )
4289        this = self.sql(expression, "this")
4290        this = f" {this}" if this else ""
4291
4292        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4293
4294    def altersession_sql(self, expression: exp.AlterSession) -> str:
4295        items_sql = self.expressions(expression, flat=True)
4296        keyword = "UNSET" if expression.args.get("unset") else "SET"
4297        return f"{keyword} {items_sql}"
4298
4299    def add_column_sql(self, expression: exp.Expr) -> str:
4300        sql = self.sql(expression)
4301        if isinstance(expression, exp.Schema):
4302            column_text = " COLUMNS"
4303        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4304            column_text = " COLUMN"
4305        else:
4306            column_text = ""
4307
4308        return f"ADD{column_text} {sql}"
4309
4310    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4311        expressions = self.expressions(expression)
4312        exists = " IF EXISTS " if expression.args.get("exists") else " "
4313        return f"DROP{exists}{expressions}"
4314
4315    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4316        return "DROP PRIMARY KEY"
4317
4318    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4319        return f"ADD {self.expressions(expression, indent=False)}"
4320
4321    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4322        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4323        location = self.sql(expression, "location")
4324        location = f" {location}" if location else ""
4325        return f"ADD {exists}{self.sql(expression.this)}{location}"
4326
4327    def distinct_sql(self, expression: exp.Distinct) -> str:
4328        this = self.expressions(expression, flat=True)
4329
4330        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4331            case = exp.case()
4332            for arg in expression.expressions:
4333                case = case.when(arg.is_(exp.null()), exp.null())
4334            this = self.sql(case.else_(f"({this})"))
4335
4336        this = f" {this}" if this else ""
4337
4338        on = self.sql(expression, "on")
4339        on = f" ON {on}" if on else ""
4340        return f"DISTINCT{this}{on}"
4341
4342    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4343        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4344
4345    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4346        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4347
4348    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4349        this_sql = self.sql(expression, "this")
4350        expression_sql = self.sql(expression, "expression")
4351        kind = "MAX" if expression.args.get("max") else "MIN"
4352        return f"{this_sql} HAVING {kind} {expression_sql}"
4353
4354    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4355        return self.sql(
4356            exp.Cast(
4357                this=exp.Div(this=expression.this, expression=expression.expression),
4358                to=exp.DataType(this=exp.DType.INT),
4359            )
4360        )
4361
4362    def dpipe_sql(self, expression: exp.DPipe) -> str:
4363        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4364            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4365        return self.binary(expression, "||")
4366
4367    def div_sql(self, expression: exp.Div) -> str:
4368        l, r = expression.left, expression.right
4369
4370        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4371            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4372
4373        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4374            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4375                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4376
4377        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4378            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4379                return self.sql(
4380                    exp.cast(
4381                        l / r,
4382                        to=exp.DType.BIGINT,
4383                    )
4384                )
4385
4386        return self.binary(expression, "/")
4387
4388    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4389        n = exp._wrap(expression.this, exp.Binary)
4390        d = exp._wrap(expression.expression, exp.Binary)
4391        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4392
4393    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4394        return self.binary(expression, "OVERLAPS")
4395
4396    def distance_sql(self, expression: exp.Distance) -> str:
4397        return self.binary(expression, "<->")
4398
4399    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4400        return self.binary(expression, "<<->>")
4401
4402    def dot_sql(self, expression: exp.Dot) -> str:
4403        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4404
4405    def eq_sql(self, expression: exp.EQ) -> str:
4406        return self.binary(expression, "=")
4407
4408    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4409        return self.binary(expression, ":=")
4410
4411    def escape_sql(self, expression: exp.Escape) -> str:
4412        this = expression.this
4413        if (
4414            isinstance(this, (exp.Like, exp.ILike))
4415            and isinstance(this.expression, (exp.All, exp.Any))
4416            and not self.SUPPORTS_LIKE_QUANTIFIERS
4417        ):
4418            return self._like_sql(this, escape=expression)
4419        return self.binary(expression, "ESCAPE")
4420
4421    def glob_sql(self, expression: exp.Glob) -> str:
4422        return self.binary(expression, "GLOB")
4423
4424    def gt_sql(self, expression: exp.GT) -> str:
4425        return self.binary(expression, ">")
4426
4427    def gte_sql(self, expression: exp.GTE) -> str:
4428        return self.binary(expression, ">=")
4429
4430    def is_sql(self, expression: exp.Is) -> str:
4431        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4432            return self.sql(
4433                expression.this if expression.expression.this else exp.not_(expression.this)
4434            )
4435        return self.binary(expression, "IS")
4436
4437    def _like_sql(
4438        self,
4439        expression: exp.Like | exp.ILike,
4440        escape: exp.Escape | None = None,
4441    ) -> str:
4442        this = expression.this
4443        rhs = expression.expression
4444
4445        if isinstance(expression, exp.Like):
4446            exp_class: type[exp.Like | exp.ILike] = exp.Like
4447            op = "LIKE"
4448        else:
4449            exp_class = exp.ILike
4450            op = "ILIKE"
4451
4452        if expression.args.get("negate"):
4453            op = f"NOT {op}"
4454
4455        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4456            exprs = rhs.this.unnest()
4457
4458            if isinstance(exprs, exp.Tuple):
4459                exprs = exprs.expressions
4460            else:
4461                exprs = [exprs]
4462
4463            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4464
4465            def _make_like(expr: exp.Expression) -> exp.Expression:
4466                like: exp.Expression = exp_class(
4467                    this=this, expression=expr, negate=expression.args.get("negate")
4468                )
4469                if escape:
4470                    like = exp.Escape(this=like, expression=escape.expression.copy())
4471                return like
4472
4473            like_expr: exp.Expr = _make_like(exprs[0])
4474            for expr in exprs[1:]:
4475                like_expr = connective(like_expr, _make_like(expr), copy=False)
4476
4477            parent = escape.parent if escape else expression.parent
4478            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4479                parent, exp.Condition
4480            ):
4481                like_expr = exp.paren(like_expr, copy=False)
4482
4483            return self.sql(like_expr)
4484
4485        return self.binary(expression, op)
4486
4487    def like_sql(self, expression: exp.Like) -> str:
4488        return self._like_sql(expression)
4489
4490    def ilike_sql(self, expression: exp.ILike) -> str:
4491        return self._like_sql(expression)
4492
4493    def match_sql(self, expression: exp.Match) -> str:
4494        return self.binary(expression, "MATCH")
4495
4496    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4497        return self.binary(expression, "SIMILAR TO")
4498
4499    def lt_sql(self, expression: exp.LT) -> str:
4500        return self.binary(expression, "<")
4501
4502    def lte_sql(self, expression: exp.LTE) -> str:
4503        return self.binary(expression, "<=")
4504
4505    def mod_sql(self, expression: exp.Mod) -> str:
4506        return self.binary(expression, "%")
4507
4508    def mul_sql(self, expression: exp.Mul) -> str:
4509        return self.binary(expression, "*")
4510
4511    def neq_sql(self, expression: exp.NEQ) -> str:
4512        return self.binary(expression, "<>")
4513
4514    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4515        return self.binary(expression, "IS NOT DISTINCT FROM")
4516
4517    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4518        return self.binary(expression, "IS DISTINCT FROM")
4519
4520    def sub_sql(self, expression: exp.Sub) -> str:
4521        return self.binary(expression, "-")
4522
4523    def trycast_sql(self, expression: exp.TryCast) -> str:
4524        return self.cast_sql(expression, safe_prefix="TRY_")
4525
4526    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4527        return self.cast_sql(expression)
4528
4529    def try_sql(self, expression: exp.Try) -> str:
4530        if not self.TRY_SUPPORTED:
4531            self.unsupported("Unsupported TRY function")
4532            return self.sql(expression, "this")
4533
4534        return self.func("TRY", expression.this)
4535
4536    def log_sql(self, expression: exp.Log) -> str:
4537        this = expression.this
4538        expr = expression.expression
4539
4540        if self.dialect.LOG_BASE_FIRST is False:
4541            this, expr = expr, this
4542        elif self.dialect.LOG_BASE_FIRST is None and expr:
4543            if this.name in ("2", "10"):
4544                return self.func(f"LOG{this.name}", expr)
4545
4546            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4547
4548        return self.func("LOG", this, expr)
4549
4550    def use_sql(self, expression: exp.Use) -> str:
4551        kind = self.sql(expression, "kind")
4552        kind = f" {kind}" if kind else ""
4553        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4554        this = f" {this}" if this else ""
4555        return f"USE{kind}{this}"
4556
4557    def binary(self, expression: exp.Binary, op: str) -> str:
4558        sqls: list[str] = []
4559        stack: list[None | str | exp.Expr] = [expression]
4560        binary_type = type(expression)
4561
4562        while stack:
4563            node = stack.pop()
4564
4565            if type(node) is binary_type:
4566                op_func = node.args.get("operator")
4567                if op_func:
4568                    op = f"OPERATOR({self.sql(op_func)})"
4569
4570                stack.append(node.args.get("expression"))
4571                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4572                stack.append(node.args.get("this"))
4573            else:
4574                sqls.append(self.sql(node))
4575
4576        return "".join(sqls)
4577
4578    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4579        to_clause = self.sql(expression, "to")
4580        if to_clause:
4581            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4582
4583        return self.function_fallback_sql(expression)
4584
4585    def function_fallback_sql(self, expression: exp.Func) -> str:
4586        args = []
4587
4588        for key in expression.arg_types:
4589            arg_value = expression.args.get(key)
4590
4591            if isinstance(arg_value, list):
4592                for value in arg_value:
4593                    args.append(value)
4594            elif arg_value is not None:
4595                args.append(arg_value)
4596
4597        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4598            name = expression.meta_get("name") or expression.sql_name()
4599        else:
4600            name = expression.sql_name()
4601
4602        return self.func(name, *args)
4603
4604    def func(
4605        self,
4606        name: str,
4607        *args: t.Any,
4608        prefix: str = "(",
4609        suffix: str = ")",
4610        normalize: bool = True,
4611    ) -> str:
4612        name = self.normalize_func(name) if normalize else name
4613        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4614
4615    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4616        arg_sqls = tuple(
4617            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4618        )
4619        if self.pretty and self.too_wide(arg_sqls):
4620            return self.indent(
4621                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4622            )
4623        return sep.join(arg_sqls)
4624
4625    def too_wide(self, args: t.Iterable) -> bool:
4626        return sum(len(arg) for arg in args) > self.max_text_width
4627
4628    def format_time(
4629        self,
4630        expression: exp.Expr,
4631        inverse_time_mapping: dict[str, str] | None = None,
4632        inverse_time_trie: dict | None = None,
4633    ) -> str | None:
4634        return format_time(
4635            self.sql(expression, "format"),
4636            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4637            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4638        )
4639
4640    def expressions(
4641        self,
4642        expression: exp.Expr | None = None,
4643        key: str | None = None,
4644        sqls: t.Collection[str | exp.Expr] | None = None,
4645        flat: bool = False,
4646        indent: bool = True,
4647        skip_first: bool = False,
4648        skip_last: bool = False,
4649        sep: str = ", ",
4650        prefix: str = "",
4651        dynamic: bool = False,
4652        new_line: bool = False,
4653    ) -> str:
4654        expressions = expression.args.get(key or "expressions") if expression else sqls
4655
4656        if not expressions:
4657            return ""
4658
4659        if flat:
4660            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4661
4662        num_sqls = len(expressions)
4663        result_sqls = []
4664
4665        for i, e in enumerate(expressions):
4666            sql = self.sql(e, comment=False)
4667            if not sql:
4668                continue
4669
4670            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4671
4672            if self.pretty:
4673                if self.leading_comma:
4674                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4675                else:
4676                    result_sqls.append(
4677                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4678                    )
4679            else:
4680                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4681
4682        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4683            if new_line:
4684                result_sqls.insert(0, "")
4685                result_sqls.append("")
4686            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4687        else:
4688            result_sql = "".join(result_sqls)
4689
4690        return (
4691            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4692            if indent
4693            else result_sql
4694        )
4695
4696    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4697        flat = flat or isinstance(expression.parent, exp.Properties)
4698        expressions_sql = self.expressions(expression, flat=flat)
4699        if flat:
4700            return f"{op} {expressions_sql}"
4701        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4702
4703    def naked_property(self, expression: exp.Property) -> str:
4704        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4705        if not property_name:
4706            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4707        return f"{property_name} {self.sql(expression, 'this')}"
4708
4709    def tag_sql(self, expression: exp.Tag) -> str:
4710        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4711
4712    def token_sql(self, token_type: TokenType) -> str:
4713        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4714
4715    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4716        this = self.sql(expression, "this")
4717        expressions = self.no_identify(self.expressions, expression)
4718        expressions = (
4719            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4720        )
4721        return f"{this}{expressions}" if expressions.strip() != "" else this
4722
4723    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
4724        return self.expressions(expression, flat=True)
4725
4726    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
4727        params = self.no_identify(self.expressions, expression, flat=True)
4728        body = self.sql(expression, "this")
4729        prefix = "TABLE " if expression.args.get("is_table") else ""
4730        return f"({params}) AS {prefix}{body}"
4731
4732    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4733        this = self.sql(expression, "this")
4734        expressions = self.expressions(expression, flat=True)
4735        return f"{this}({expressions})"
4736
4737    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4738        return self.binary(expression, "=>")
4739
4740    def when_sql(self, expression: exp.When) -> str:
4741        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4742        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4743        condition = self.sql(expression, "condition")
4744        condition = f" AND {condition}" if condition else ""
4745
4746        then_expression = expression.args.get("then")
4747        if isinstance(then_expression, exp.Insert):
4748            this = self.sql(then_expression, "this")
4749            this = f"INSERT {this}" if this else "INSERT"
4750            then = self.sql(then_expression, "expression")
4751            then = f"{this} VALUES {then}" if then else this
4752        elif isinstance(then_expression, exp.Update):
4753            if isinstance(then_expression.args.get("expressions"), exp.Star):
4754                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4755            else:
4756                expressions_sql = self.expressions(then_expression)
4757                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4758        else:
4759            then = self.sql(then_expression)
4760
4761        if isinstance(then_expression, (exp.Insert, exp.Update)):
4762            where = self.sql(then_expression, "where")
4763            if where and not self.SUPPORTS_MERGE_WHERE:
4764                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4765                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4766                where = ""
4767            then = f"{then}{where}"
4768        return f"WHEN {matched}{source}{condition} THEN {then}"
4769
4770    def whens_sql(self, expression: exp.Whens) -> str:
4771        return self.expressions(expression, sep=" ", indent=False)
4772
4773    def merge_sql(self, expression: exp.Merge) -> str:
4774        table = expression.this
4775        table_alias = ""
4776
4777        hints = table.args.get("hints")
4778        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4779            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4780            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4781
4782        this = self.sql(table)
4783        using = f"USING {self.sql(expression, 'using')}"
4784        whens = self.sql(expression, "whens")
4785
4786        on = self.sql(expression, "on")
4787        on = f"ON {on}" if on else ""
4788
4789        if not on:
4790            on = self.expressions(expression, key="using_cond")
4791            on = f"USING ({on})" if on else ""
4792
4793        returning = self.sql(expression, "returning")
4794        if returning:
4795            whens = f"{whens}{returning}"
4796
4797        sep = self.sep()
4798
4799        return self.prepend_ctes(
4800            expression,
4801            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4802        )
4803
4804    @unsupported_args("format")
4805    def tochar_sql(self, expression: exp.ToChar) -> str:
4806        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
4807
4808    @unsupported_args("default")
4809    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4810        if not self.SUPPORTS_TO_NUMBER:
4811            self.unsupported("Unsupported TO_NUMBER function")
4812            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4813
4814        fmt = expression.args.get("format")
4815        if not fmt:
4816            self.unsupported("Conversion format is required for TO_NUMBER")
4817            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4818
4819        return self.func("TO_NUMBER", expression.this, fmt)
4820
4821    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4822        this = self.sql(expression, "this")
4823        kind = self.sql(expression, "kind")
4824        settings_sql = self.expressions(expression, key="settings", sep=" ")
4825        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4826        return f"{this}({kind}{args})"
4827
4828    def dictrange_sql(self, expression: exp.DictRange) -> str:
4829        this = self.sql(expression, "this")
4830        max = self.sql(expression, "max")
4831        min = self.sql(expression, "min")
4832        return f"{this}(MIN {min} MAX {max})"
4833
4834    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4835        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4836
4837    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4838        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4839
4840    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4841    def uniquekeyproperty_sql(
4842        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4843    ) -> str:
4844        return f"{prefix} ({self.expressions(expression, flat=True)})"
4845
4846    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4847    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4848        expressions = self.expressions(expression, flat=True)
4849        expressions = f" {self.wrap(expressions)}" if expressions else ""
4850        buckets = self.sql(expression, "buckets")
4851        kind = self.sql(expression, "kind")
4852        buckets = f" BUCKETS {buckets}" if buckets else ""
4853        order = self.sql(expression, "order")
4854        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4855
4856    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4857        return ""
4858
4859    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4860        expressions = self.expressions(expression, key="expressions", flat=True)
4861        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4862        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4863        buckets = self.sql(expression, "buckets")
4864        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4865
4866    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4867        this = self.sql(expression, "this")
4868        having = self.sql(expression, "having")
4869
4870        if having:
4871            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4872
4873        return self.func("ANY_VALUE", this)
4874
4875    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4876        transform = self.func("TRANSFORM", *expression.expressions)
4877        row_format_before = self.sql(expression, "row_format_before")
4878        row_format_before = f" {row_format_before}" if row_format_before else ""
4879        record_writer = self.sql(expression, "record_writer")
4880        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4881        using = f" USING {self.sql(expression, 'command_script')}"
4882        schema = self.sql(expression, "schema")
4883        schema = f" AS {schema}" if schema else ""
4884        row_format_after = self.sql(expression, "row_format_after")
4885        row_format_after = f" {row_format_after}" if row_format_after else ""
4886        record_reader = self.sql(expression, "record_reader")
4887        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4888        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4889
4890    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4891        key_block_size = self.sql(expression, "key_block_size")
4892        if key_block_size:
4893            return f"KEY_BLOCK_SIZE = {key_block_size}"
4894
4895        using = self.sql(expression, "using")
4896        if using:
4897            return f"USING {using}"
4898
4899        parser = self.sql(expression, "parser")
4900        if parser:
4901            return f"WITH PARSER {parser}"
4902
4903        comment = self.sql(expression, "comment")
4904        if comment:
4905            return f"COMMENT {comment}"
4906
4907        visible = expression.args.get("visible")
4908        if visible is not None:
4909            return "VISIBLE" if visible else "INVISIBLE"
4910
4911        engine_attr = self.sql(expression, "engine_attr")
4912        if engine_attr:
4913            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4914
4915        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4916        if secondary_engine_attr:
4917            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4918
4919        self.unsupported("Unsupported index constraint option.")
4920        return ""
4921
4922    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4923        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4924        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4925
4926    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4927        kind = self.sql(expression, "kind")
4928        kind = f"{kind} INDEX" if kind else "INDEX"
4929        this = self.sql(expression, "this")
4930        this = f" {this}" if this else ""
4931        index_type = self.sql(expression, "index_type")
4932        index_type = f" USING {index_type}" if index_type else ""
4933        expressions = self.expressions(expression, flat=True)
4934        expressions = f" ({expressions})" if expressions else ""
4935        options = self.expressions(expression, key="options", sep=" ")
4936        options = f" {options}" if options else ""
4937        return f"{kind}{this}{index_type}{expressions}{options}"
4938
4939    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4940        if self.NVL2_SUPPORTED:
4941            return self.function_fallback_sql(expression)
4942
4943        case = exp.Case().when(
4944            expression.this.is_(exp.null()).not_(copy=False),
4945            expression.args["true"],
4946            copy=False,
4947        )
4948        else_cond = expression.args.get("false")
4949        if else_cond:
4950            case.else_(else_cond, copy=False)
4951
4952        return self.sql(case)
4953
4954    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4955        this = self.sql(expression, "this")
4956        expr = self.sql(expression, "expression")
4957        position = self.sql(expression, "position")
4958        position = f", {position}" if position else ""
4959        iterator = self.sql(expression, "iterator")
4960        condition = self.sql(expression, "condition")
4961        condition = f" IF {condition}" if condition else ""
4962        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
4963
4964    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4965        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4966
4967    def opclass_sql(self, expression: exp.Opclass) -> str:
4968        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4969
4970    def _ml_sql(self, expression: exp.Func, name: str) -> str:
4971        model = self.sql(expression, "this")
4972        model = f"MODEL {model}"
4973        expr = expression.expression
4974        if expr:
4975            expr_sql = self.sql(expression, "expression")
4976            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
4977        else:
4978            expr_sql = None
4979
4980        parameters = self.sql(expression, "params_struct") or None
4981
4982        return self.func(name, model, expr_sql, parameters)
4983
4984    def predict_sql(self, expression: exp.Predict) -> str:
4985        return self._ml_sql(expression, "PREDICT")
4986
4987    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4988        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4989        return self._ml_sql(expression, name)
4990
4991    def generatetext_sql(self, expression: exp.GenerateText) -> str:
4992        return self._ml_sql(expression, "GENERATE_TEXT")
4993
4994    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
4995        return self._ml_sql(expression, "GENERATE_TABLE")
4996
4997    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
4998        return self._ml_sql(expression, "GENERATE_BOOL")
4999
5000    def generateint_sql(self, expression: exp.GenerateInt) -> str:
5001        return self._ml_sql(expression, "GENERATE_INT")
5002
5003    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
5004        return self._ml_sql(expression, "GENERATE_DOUBLE")
5005
5006    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
5007        return self._ml_sql(expression, "TRANSLATE")
5008
5009    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
5010        return self._ml_sql(expression, "FORECAST")
5011
5012    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
5013        this_sql = self.sql(expression, "this")
5014        if isinstance(expression.this, exp.Table):
5015            this_sql = f"TABLE {this_sql}"
5016
5017        return self.func(
5018            "FORECAST",
5019            this_sql,
5020            expression.args.get("data_col"),
5021            expression.args.get("timestamp_col"),
5022            expression.args.get("model"),
5023            expression.args.get("id_cols"),
5024            expression.args.get("horizon"),
5025            expression.args.get("forecast_end_timestamp"),
5026            expression.args.get("confidence_level"),
5027            expression.args.get("output_historical_time_series"),
5028            expression.args.get("context_window"),
5029        )
5030
5031    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
5032        this_sql = self.sql(expression, "this")
5033        if isinstance(expression.this, exp.Table):
5034            this_sql = f"TABLE {this_sql}"
5035
5036        return self.func(
5037            "FEATURES_AT_TIME",
5038            this_sql,
5039            expression.args.get("time"),
5040            expression.args.get("num_rows"),
5041            expression.args.get("ignore_feature_nulls"),
5042        )
5043
5044    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5045        this_sql = self.sql(expression, "this")
5046        if isinstance(expression.this, exp.Table):
5047            this_sql = f"TABLE {this_sql}"
5048
5049        query_table = self.sql(expression, "query_table")
5050        if isinstance(expression.args["query_table"], exp.Table):
5051            query_table = f"TABLE {query_table}"
5052
5053        return self.func(
5054            "VECTOR_SEARCH",
5055            this_sql,
5056            expression.args.get("column_to_search"),
5057            query_table,
5058            expression.args.get("query_column_to_search"),
5059            expression.args.get("top_k"),
5060            expression.args.get("distance_type"),
5061            expression.args.get("options"),
5062        )
5063
5064    def forin_sql(self, expression: exp.ForIn) -> str:
5065        this = self.sql(expression, "this")
5066        expression_sql = self.sql(expression, "expression")
5067        return f"FOR {this} DO {expression_sql}"
5068
5069    def refresh_sql(self, expression: exp.Refresh) -> str:
5070        this = self.sql(expression, "this")
5071        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5072        return f"REFRESH {kind}{this}"
5073
5074    def toarray_sql(self, expression: exp.ToArray) -> str:
5075        arg = expression.this
5076        if not arg.type:
5077            import sqlglot.optimizer.annotate_types
5078
5079            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5080
5081        if arg.is_type(exp.DType.ARRAY):
5082            return self.sql(arg)
5083
5084        cond_for_null = arg.is_(exp.null())
5085        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5086
5087    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5088        this = expression.this
5089        time_format = self.format_time(expression)
5090
5091        if time_format:
5092            return self.sql(
5093                exp.cast(
5094                    exp.StrToTime(this=this, format=expression.args["format"]),
5095                    exp.DType.TIME,
5096                )
5097            )
5098
5099        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5100            return self.sql(this)
5101
5102        return self.sql(exp.cast(this, exp.DType.TIME))
5103
5104    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5105        this = expression.this
5106        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5107            return self.sql(this)
5108
5109        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5110
5111    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5112        this = expression.this
5113        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5114            return self.sql(this)
5115
5116        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5117
5118    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5119        this = expression.this
5120        time_format = self.format_time(expression)
5121        safe = expression.args.get("safe")
5122        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5123            return self.sql(
5124                exp.cast(
5125                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5126                    exp.DType.DATE,
5127                )
5128            )
5129
5130        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5131            return self.sql(this)
5132
5133        if safe:
5134            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5135
5136        return self.sql(exp.cast(this, exp.DType.DATE))
5137
5138    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5139        return self.sql(
5140            exp.func(
5141                "DATEDIFF",
5142                expression.this,
5143                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5144                "day",
5145            )
5146        )
5147
5148    def lastday_sql(self, expression: exp.LastDay) -> str:
5149        if self.LAST_DAY_SUPPORTS_DATE_PART:
5150            return self.function_fallback_sql(expression)
5151
5152        unit = expression.text("unit")
5153        if unit and unit != "MONTH":
5154            self.unsupported("Date parts are not supported in LAST_DAY.")
5155
5156        return self.func("LAST_DAY", expression.this)
5157
5158    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5159        import sqlglot.dialects.dialect
5160
5161        return self.func(
5162            "DATE_ADD",
5163            expression.this,
5164            expression.expression,
5165            sqlglot.dialects.dialect.unit_to_str(expression),
5166        )
5167
5168    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5169        if self.CAN_IMPLEMENT_ARRAY_ANY:
5170            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5171            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5172            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5173            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5174
5175        import sqlglot.dialects.dialect
5176
5177        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5178        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5179            self.unsupported("ARRAY_ANY is unsupported")
5180
5181        return self.function_fallback_sql(expression)
5182
5183    def struct_sql(self, expression: exp.Struct) -> str:
5184        expression.set(
5185            "expressions",
5186            [
5187                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5188                if isinstance(e, exp.PropertyEQ)
5189                else e
5190                for e in expression.expressions
5191            ],
5192        )
5193
5194        return self.function_fallback_sql(expression)
5195
5196    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5197        low = self.sql(expression, "this")
5198        high = self.sql(expression, "expression")
5199
5200        return f"{low} TO {high}"
5201
5202    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5203        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5204        tables = f" {self.expressions(expression)}"
5205
5206        exists = " IF EXISTS" if expression.args.get("exists") else ""
5207
5208        on_cluster = self.sql(expression, "cluster")
5209        on_cluster = f" {on_cluster}" if on_cluster else ""
5210
5211        identity = self.sql(expression, "identity")
5212        identity = f" {identity} IDENTITY" if identity else ""
5213
5214        option = self.sql(expression, "option")
5215        option = f" {option}" if option else ""
5216
5217        partition = self.sql(expression, "partition")
5218        partition = f" {partition}" if partition else ""
5219
5220        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5221
5222    # This transpiles T-SQL's CONVERT function
5223    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
5224    def convert_sql(self, expression: exp.Convert) -> str:
5225        to = expression.this
5226        value = expression.expression
5227        style = expression.args.get("style")
5228        safe = expression.args.get("safe")
5229        strict = expression.args.get("strict")
5230
5231        if not to or not value:
5232            return ""
5233
5234        # Retrieve length of datatype and override to default if not specified
5235        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5236            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5237
5238        transformed: exp.Expr | None = None
5239        cast = exp.Cast if strict else exp.TryCast
5240
5241        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5242        if isinstance(style, exp.Literal) and style.is_int:
5243            import sqlglot.dialects.tsql
5244
5245            style_value = style.name
5246            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5247            if not converted_style:
5248                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5249
5250            fmt = exp.Literal.string(converted_style)
5251
5252            if to.this == exp.DType.DATE:
5253                transformed = exp.StrToDate(this=value, format=fmt)
5254            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5255                transformed = exp.StrToTime(this=value, format=fmt)
5256            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5257                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5258            elif to.this == exp.DType.TEXT:
5259                transformed = exp.TimeToStr(this=value, format=fmt)
5260
5261        if not transformed:
5262            transformed = cast(this=value, to=to, safe=safe)
5263
5264        return self.sql(transformed)
5265
5266    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
5267        this = expression.this
5268        if isinstance(this, exp.JSONPathWildcard):
5269            this = self.json_path_part(this)
5270            return f".{this}" if this else ""
5271
5272        quoted = expression.args.get("quoted")
5273        if not (
5274            quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
5275        ) and self.SAFE_JSON_PATH_KEY_RE.match(this):
5276            return f".{this}"
5277
5278        this = self.json_path_part(this)
5279
5280        if quoted and self.QUOTE_JSON_PATH:
5281            # The whole path is rendered as a single quoted string literal, so the bracketed key
5282            # (which may itself contain backslash-escaped quotes, e.g. ["x \"y\"z"]) must be
5283            # escaped again for the outer string literal (-> ["x \\"y\\"z"]).
5284            this = self.escape_str(this)
5285
5286        return (
5287            f"[{this}]"
5288            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5289            else f".{this}"
5290        )
5291
5292    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5293        this = self.json_path_part(expression.this)
5294        return f"[{this}]" if this else ""
5295
5296    def _simplify_unless_literal(self, expression: E) -> E:
5297        if not isinstance(expression, exp.Literal):
5298            import sqlglot.optimizer.simplify
5299
5300            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5301
5302        return expression
5303
5304    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5305        this = expression.this
5306        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5307            self.unsupported(
5308                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5309            )
5310            return self.sql(this)
5311
5312        if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5313            if self.IGNORE_NULLS_BEFORE_ORDER:
5314                # The first modifier here will be the one closest to the AggFunc's arg
5315                mods = sorted(
5316                    expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
5317                    key=lambda x: (
5318                        0
5319                        if isinstance(x, exp.HavingMax)
5320                        else (1 if isinstance(x, exp.Order) else 2)
5321                    ),
5322                )
5323
5324                if mods:
5325                    mod = mods[0]
5326                    this = expression.__class__(this=mod.this.copy())
5327                    this.meta["inline"] = True
5328                    mod.this.replace(this)
5329                    return self.sql(expression.this)
5330
5331            agg_func = expression.find(exp.AggFunc)
5332
5333            if agg_func:
5334                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5335                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5336
5337        return f"{self.sql(expression, 'this')} {text}"
5338
5339    def _replace_line_breaks(self, string: str) -> str:
5340        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5341        if self.pretty:
5342            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5343        return string
5344
5345    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5346        option = self.sql(expression, "this")
5347
5348        if expression.expressions:
5349            upper = option.upper()
5350
5351            # Snowflake FILE_FORMAT options are separated by whitespace
5352            sep = " " if upper == "FILE_FORMAT" else ", "
5353
5354            # Databricks copy/format options do not set their list of values with EQ
5355            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5356            values = self.expressions(expression, flat=True, sep=sep)
5357            return f"{option}{op}({values})"
5358
5359        value = self.sql(expression, "expression")
5360
5361        if not value:
5362            return option
5363
5364        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5365
5366        return f"{option}{op}{value}"
5367
5368    def credentials_sql(self, expression: exp.Credentials) -> str:
5369        cred_expr = expression.args.get("credentials")
5370        if isinstance(cred_expr, exp.Literal):
5371            # Redshift case: CREDENTIALS <string>
5372            credentials = self.sql(expression, "credentials")
5373            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5374        else:
5375            # Snowflake case: CREDENTIALS = (...)
5376            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5377            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5378
5379        storage = self.sql(expression, "storage")
5380        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5381
5382        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5383        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5384
5385        iam_role = self.sql(expression, "iam_role")
5386        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5387
5388        region = self.sql(expression, "region")
5389        region = f" REGION {region}" if region else ""
5390
5391        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5392
5393    def copy_sql(self, expression: exp.Copy) -> str:
5394        this = self.sql(expression, "this")
5395        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5396
5397        credentials = self.sql(expression, "credentials")
5398        credentials = self.seg(credentials) if credentials else ""
5399        files = self.expressions(expression, key="files", flat=True)
5400        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5401
5402        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5403        params = self.expressions(
5404            expression,
5405            key="params",
5406            sep=sep,
5407            new_line=True,
5408            skip_last=True,
5409            skip_first=True,
5410            indent=self.COPY_PARAMS_ARE_WRAPPED,
5411        )
5412
5413        if params:
5414            if self.COPY_PARAMS_ARE_WRAPPED:
5415                params = f" WITH ({params})"
5416            elif not self.pretty and (files or credentials):
5417                params = f" {params}"
5418
5419        return f"COPY{this}{kind} {files}{credentials}{params}"
5420
5421    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5422        return ""
5423
5424    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5425        on_sql = "ON" if expression.args.get("on") else "OFF"
5426        filter_col: str | None = self.sql(expression, "filter_column")
5427        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5428        retention_period: str | None = self.sql(expression, "retention_period")
5429        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5430
5431        if filter_col or retention_period:
5432            on_sql = self.func("ON", filter_col, retention_period)
5433
5434        return f"DATA_DELETION={on_sql}"
5435
5436    def maskingpolicycolumnconstraint_sql(
5437        self, expression: exp.MaskingPolicyColumnConstraint
5438    ) -> str:
5439        this = self.sql(expression, "this")
5440        expressions = self.expressions(expression, flat=True)
5441        expressions = f" USING ({expressions})" if expressions else ""
5442        return f"MASKING POLICY {this}{expressions}"
5443
5444    def gapfill_sql(self, expression: exp.GapFill) -> str:
5445        this = self.sql(expression, "this")
5446        this = f"TABLE {this}"
5447        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5448
5449    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5450        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5451
5452    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5453        this = self.sql(expression, "this")
5454        expr = expression.expression
5455
5456        if isinstance(expr, exp.Func):
5457            # T-SQL's CLR functions are case sensitive
5458            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5459        else:
5460            expr = self.sql(expression, "expression")
5461
5462        return self.scope_resolution(expr, this)
5463
5464    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5465        if self.PARSE_JSON_NAME is None:
5466            return self.sql(expression.this)
5467
5468        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5469
5470    def rand_sql(self, expression: exp.Rand) -> str:
5471        lower = self.sql(expression, "lower")
5472        upper = self.sql(expression, "upper")
5473
5474        if lower and upper:
5475            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5476        return self.func("RAND", expression.this)
5477
5478    def changes_sql(self, expression: exp.Changes) -> str:
5479        information = self.sql(expression, "information")
5480        information = f"INFORMATION => {information}"
5481        at_before = self.sql(expression, "at_before")
5482        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5483        end = self.sql(expression, "end")
5484        end = f"{self.seg('')}{end}" if end else ""
5485
5486        return f"CHANGES ({information}){at_before}{end}"
5487
5488    def pad_sql(self, expression: exp.Pad) -> str:
5489        prefix = "L" if expression.args.get("is_left") else "R"
5490
5491        fill_pattern = self.sql(expression, "fill_pattern") or None
5492        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5493            fill_pattern = "' '"
5494
5495        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5496
5497    def summarize_sql(self, expression: exp.Summarize) -> str:
5498        table = " TABLE" if expression.args.get("table") else ""
5499        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5500
5501    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5502        generate_series = exp.GenerateSeries(**expression.args)
5503
5504        parent = expression.parent
5505        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5506            parent = parent.parent
5507
5508        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5509            return self.sql(exp.Unnest(expressions=[generate_series]))
5510
5511        if isinstance(parent, exp.Select):
5512            self.unsupported("GenerateSeries projection unnesting is not supported.")
5513
5514        return self.sql(generate_series)
5515
5516    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5517        if self.SUPPORTS_CONVERT_TIMEZONE:
5518            return self.function_fallback_sql(expression)
5519
5520        source_tz = expression.args.get("source_tz")
5521        target_tz = expression.args.get("target_tz")
5522        timestamp = expression.args.get("timestamp")
5523
5524        if source_tz and timestamp:
5525            timestamp = exp.AtTimeZone(
5526                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5527            )
5528
5529        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5530
5531        return self.sql(expr)
5532
5533    def json_sql(self, expression: exp.JSON) -> str:
5534        this = self.sql(expression, "this")
5535        this = f" {this}" if this else ""
5536
5537        _with = expression.args.get("with_")
5538
5539        if _with is None:
5540            with_sql = ""
5541        elif not _with:
5542            with_sql = " WITHOUT"
5543        else:
5544            with_sql = " WITH"
5545
5546        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5547
5548        return f"JSON{this}{with_sql}{unique_sql}"
5549
5550    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5551        path = self.sql(expression, "path")
5552        returning = self.sql(expression, "returning")
5553        returning = f" RETURNING {returning}" if returning else ""
5554
5555        on_condition = self.sql(expression, "on_condition")
5556        on_condition = f" {on_condition}" if on_condition else ""
5557
5558        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5559
5560    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5561        regexp = " REGEXP" if expression.args.get("regexp") else ""
5562        return f"SKIP{regexp} {self.sql(expression.expression)}"
5563
5564    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5565        else_ = "ELSE " if expression.args.get("else_") else ""
5566        condition = self.sql(expression, "expression")
5567        condition = f"WHEN {condition} THEN " if condition else else_
5568        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5569        return f"{condition}{insert}"
5570
5571    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5572        kind = self.sql(expression, "kind")
5573        expressions = self.seg(self.expressions(expression, sep=" "))
5574        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
5575        return res
5576
5577    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5578        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5579        empty = expression.args.get("empty")
5580        empty = (
5581            f"DEFAULT {empty} ON EMPTY"
5582            if isinstance(empty, exp.Expr)
5583            else self.sql(expression, "empty")
5584        )
5585
5586        error = expression.args.get("error")
5587        error = (
5588            f"DEFAULT {error} ON ERROR"
5589            if isinstance(error, exp.Expr)
5590            else self.sql(expression, "error")
5591        )
5592
5593        if error and empty:
5594            error = (
5595                f"{empty} {error}"
5596                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5597                else f"{error} {empty}"
5598            )
5599            empty = ""
5600
5601        null = self.sql(expression, "null")
5602
5603        return f"{empty}{error}{null}"
5604
5605    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5606        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5607        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5608
5609    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5610        this = self.sql(expression, "this")
5611        path = self.sql(expression, "path")
5612
5613        passing = self.expressions(expression, "passing")
5614        passing = f" PASSING {passing}" if passing else ""
5615
5616        on_condition = self.sql(expression, "on_condition")
5617        on_condition = f" {on_condition}" if on_condition else ""
5618
5619        path = f"{path}{passing}{on_condition}"
5620
5621        return self.func("JSON_EXISTS", this, path)
5622
5623    def _add_arrayagg_null_filter(
5624        self,
5625        array_agg_sql: str,
5626        array_agg_expr: exp.ArrayAgg,
5627        column_expr: exp.Expr,
5628    ) -> str:
5629        """
5630        Add NULL filter to ARRAY_AGG if dialect requires it.
5631
5632        Args:
5633            array_agg_sql: The generated ARRAY_AGG SQL string
5634            array_agg_expr: The ArrayAgg expression node
5635            column_expr: The column/expression to filter (before ORDER BY wrapping)
5636
5637        Returns:
5638            SQL string with FILTER clause added if needed
5639        """
5640        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5641        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5642        if not (
5643            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5644        ):
5645            return array_agg_sql
5646
5647        parent = array_agg_expr.parent
5648        if isinstance(parent, exp.Filter):
5649            parent_cond = parent.expression.this
5650            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5651        elif column_expr.find(exp.Column):
5652            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5653            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5654            this_sql = (
5655                self.expressions(column_expr)
5656                if isinstance(column_expr, exp.Distinct)
5657                else self.sql(column_expr)
5658            )
5659            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5660
5661        return array_agg_sql
5662
5663    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5664        array_agg = self.function_fallback_sql(expression)
5665        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
5666
5667    def slice_sql(self, expression: exp.Slice) -> str:
5668        step = self.sql(expression, "step")
5669        end = self.sql(expression.expression)
5670        begin = self.sql(expression.this)
5671
5672        sql = f"{end}:{step}" if step else end
5673        return f"{begin}:{sql}" if sql else f"{begin}:"
5674
5675    def apply_sql(self, expression: exp.Apply) -> str:
5676        this = self.sql(expression, "this")
5677        expr = self.sql(expression, "expression")
5678
5679        return f"{this} APPLY({expr})"
5680
5681    def _grant_or_revoke_sql(
5682        self,
5683        expression: exp.Grant | exp.Revoke,
5684        keyword: str,
5685        preposition: str,
5686        grant_option_prefix: str = "",
5687        grant_option_suffix: str = "",
5688    ) -> str:
5689        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5690
5691        kind = self.sql(expression, "kind")
5692        kind = f" {kind}" if kind else ""
5693
5694        securable = self.sql(expression, "securable")
5695        securable = f" {securable}" if securable else ""
5696
5697        principals = self.expressions(expression, key="principals", flat=True)
5698
5699        if not expression.args.get("grant_option"):
5700            grant_option_prefix = grant_option_suffix = ""
5701
5702        # cascade for revoke only
5703        cascade = self.sql(expression, "cascade")
5704        cascade = f" {cascade}" if cascade else ""
5705
5706        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5707
5708    def grant_sql(self, expression: exp.Grant) -> str:
5709        return self._grant_or_revoke_sql(
5710            expression,
5711            keyword="GRANT",
5712            preposition="TO",
5713            grant_option_suffix=" WITH GRANT OPTION",
5714        )
5715
5716    def revoke_sql(self, expression: exp.Revoke) -> str:
5717        return self._grant_or_revoke_sql(
5718            expression,
5719            keyword="REVOKE",
5720            preposition="FROM",
5721            grant_option_prefix="GRANT OPTION FOR ",
5722        )
5723
5724    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5725        this = self.sql(expression, "this")
5726        columns = self.expressions(expression, flat=True)
5727        columns = f"({columns})" if columns else ""
5728
5729        return f"{this}{columns}"
5730
5731    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5732        this = self.sql(expression, "this")
5733
5734        kind = self.sql(expression, "kind")
5735        kind = f"{kind} " if kind else ""
5736
5737        return f"{kind}{this}"
5738
5739    def columns_sql(self, expression: exp.Columns) -> str:
5740        func = self.function_fallback_sql(expression)
5741        if expression.args.get("unpack"):
5742            func = f"*{func}"
5743
5744        return func
5745
5746    def overlay_sql(self, expression: exp.Overlay) -> str:
5747        this = self.sql(expression, "this")
5748        expr = self.sql(expression, "expression")
5749        from_sql = self.sql(expression, "from_")
5750        for_sql = self.sql(expression, "for_")
5751        for_sql = f" FOR {for_sql}" if for_sql else ""
5752
5753        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
5754
5755    @unsupported_args("format")
5756    def todouble_sql(self, expression: exp.ToDouble) -> str:
5757        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5758        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
5759
5760    def string_sql(self, expression: exp.String) -> str:
5761        this = expression.this
5762        zone = expression.args.get("zone")
5763
5764        if zone:
5765            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5766            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5767            # set for source_tz to transpile the time conversion before the STRING cast
5768            this = exp.ConvertTimezone(
5769                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5770            )
5771
5772        return self.sql(exp.cast(this, exp.DType.VARCHAR))
5773
5774    def median_sql(self, expression: exp.Median) -> str:
5775        if not self.SUPPORTS_MEDIAN:
5776            return self.sql(
5777                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5778            )
5779
5780        return self.function_fallback_sql(expression)
5781
5782    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5783        filler = self.sql(expression, "this")
5784        filler = f" {filler}" if filler else ""
5785        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5786        return f"TRUNCATE{filler} {with_count}"
5787
5788    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5789        if self.SUPPORTS_UNIX_SECONDS:
5790            return self.function_fallback_sql(expression)
5791
5792        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
5793
5794        return self.sql(
5795            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5796        )
5797
5798    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5799        dim = expression.expression
5800
5801        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5802        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5803            if not (dim.is_int and dim.name == "1"):
5804                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5805            dim = None
5806
5807        # If dimension is required but not specified, default initialize it
5808        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5809            dim = exp.Literal.number(1)
5810
5811        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5812
5813    def attach_sql(self, expression: exp.Attach) -> str:
5814        this = self.sql(expression, "this")
5815        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5816        expressions = self.expressions(expression)
5817        expressions = f" ({expressions})" if expressions else ""
5818
5819        return f"ATTACH{exists_sql} {this}{expressions}"
5820
5821    def detach_sql(self, expression: exp.Detach) -> str:
5822        kind = self.sql(expression, "kind")
5823        kind = f" {kind}" if kind else ""
5824        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
5825        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5826        exists = " IF EXISTS" if expression.args.get("exists") else ""
5827        if exists:
5828            kind = kind or " DATABASE"
5829
5830        this = self.sql(expression, "this")
5831        this = f" {this}" if this else ""
5832        cluster = self.sql(expression, "cluster")
5833        cluster = f" {cluster}" if cluster else ""
5834        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
5835        sync = " SYNC" if expression.args.get("sync") else ""
5836        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
5837
5838    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5839        this = self.sql(expression, "this")
5840        value = self.sql(expression, "expression")
5841        value = f" {value}" if value else ""
5842        return f"{this}{value}"
5843
5844    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5845        return (
5846            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5847        )
5848
5849    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5850        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5851        encode = f"{encode} {self.sql(expression, 'this')}"
5852
5853        properties = expression.args.get("properties")
5854        if properties:
5855            encode = f"{encode} {self.properties(properties)}"
5856
5857        return encode
5858
5859    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5860        this = self.sql(expression, "this")
5861        include = f"INCLUDE {this}"
5862
5863        column_def = self.sql(expression, "column_def")
5864        if column_def:
5865            include = f"{include} {column_def}"
5866
5867        alias = self.sql(expression, "alias")
5868        if alias:
5869            include = f"{include} AS {alias}"
5870
5871        return include
5872
5873    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5874        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
5875        name = f"{prefix} {self.sql(expression, 'this')}"
5876        return self.func("XMLELEMENT", name, *expression.expressions)
5877
5878    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5879        this = self.sql(expression, "this")
5880        expr = self.sql(expression, "expression")
5881        expr = f"({expr})" if expr else ""
5882        return f"{this}{expr}"
5883
5884    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5885        partitions = self.expressions(expression, "partition_expressions")
5886        create = self.expressions(expression, "create_expressions")
5887        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
5888
5889    def partitionbyrangepropertydynamic_sql(
5890        self, expression: exp.PartitionByRangePropertyDynamic
5891    ) -> str:
5892        start = self.sql(expression, "start")
5893        end = self.sql(expression, "end")
5894
5895        every = expression.args["every"]
5896        if isinstance(every, exp.Interval) and every.this.is_string:
5897            every.this.replace(exp.Literal.number(every.name))
5898
5899        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5900
5901    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5902        name = self.sql(expression, "this")
5903        values = self.expressions(expression, flat=True)
5904
5905        return f"NAME {name} VALUE {values}"
5906
5907    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5908        kind = self.sql(expression, "kind")
5909        sample = self.sql(expression, "sample")
5910        return f"SAMPLE {sample} {kind}"
5911
5912    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5913        kind = self.sql(expression, "kind")
5914        option = self.sql(expression, "option")
5915        option = f" {option}" if option else ""
5916        this = self.sql(expression, "this")
5917        this = f" {this}" if this else ""
5918        columns = self.expressions(expression)
5919        columns = f" {columns}" if columns else ""
5920        return f"{kind}{option} STATISTICS{this}{columns}"
5921
5922    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5923        this = self.sql(expression, "this")
5924        columns = self.expressions(expression)
5925        inner_expression = self.sql(expression, "expression")
5926        inner_expression = f" {inner_expression}" if inner_expression else ""
5927        update_options = self.sql(expression, "update_options")
5928        update_options = f" {update_options} UPDATE" if update_options else ""
5929        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
5930
5931    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5932        kind = self.sql(expression, "kind")
5933        kind = f" {kind}" if kind else ""
5934        return f"DELETE{kind} STATISTICS"
5935
5936    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5937        inner_expression = self.sql(expression, "expression")
5938        return f"LIST CHAINED ROWS{inner_expression}"
5939
5940    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5941        kind = self.sql(expression, "kind")
5942        this = self.sql(expression, "this")
5943        this = f" {this}" if this else ""
5944        inner_expression = self.sql(expression, "expression")
5945        return f"VALIDATE {kind}{this}{inner_expression}"
5946
5947    def analyze_sql(self, expression: exp.Analyze) -> str:
5948        options = self.expressions(expression, key="options", sep=" ")
5949        options = f" {options}" if options else ""
5950        kind = self.sql(expression, "kind")
5951        kind = f" {kind}" if kind else ""
5952        this = self.sql(expression, "this")
5953        this = f" {this}" if this else ""
5954        mode = self.sql(expression, "mode")
5955        mode = f" {mode}" if mode else ""
5956        properties = self.sql(expression, "properties")
5957        properties = f" {properties}" if properties else ""
5958        partition = self.sql(expression, "partition")
5959        partition = f" {partition}" if partition else ""
5960        inner_expression = self.sql(expression, "expression")
5961        inner_expression = f" {inner_expression}" if inner_expression else ""
5962        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5963
5964    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5965        this = self.sql(expression, "this")
5966        namespaces = self.expressions(expression, key="namespaces")
5967        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5968        passing = self.expressions(expression, key="passing")
5969        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5970        columns = self.expressions(expression, key="columns")
5971        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5972        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5973        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5974
5975    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5976        this = self.sql(expression, "this")
5977        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
5978
5979    def export_sql(self, expression: exp.Export) -> str:
5980        this = self.sql(expression, "this")
5981        connection = self.sql(expression, "connection")
5982        connection = f"WITH CONNECTION {connection} " if connection else ""
5983        options = self.sql(expression, "options")
5984        return f"EXPORT DATA {connection}{options} AS {this}"
5985
5986    def declare_sql(self, expression: exp.Declare) -> str:
5987        replace = "OR REPLACE " if expression.args.get("replace") else ""
5988        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
5989
5990    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5991        variables = self.expressions(expression, "this")
5992        default = self.sql(expression, "default")
5993        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
5994
5995        kind = self.sql(expression, "kind")
5996        if isinstance(expression.args.get("kind"), exp.Schema):
5997            kind = f"TABLE {kind}"
5998
5999        kind = f" {kind}" if kind else ""
6000
6001        return f"{variables}{kind}{default}"
6002
6003    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
6004        kind = self.sql(expression, "kind")
6005        this = self.sql(expression, "this")
6006        set = self.sql(expression, "expression")
6007        using = self.sql(expression, "using")
6008        using = f" USING {using}" if using else ""
6009
6010        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
6011
6012        return f"{kind_sql} {this} SET {set}{using}"
6013
6014    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
6015        params = self.expressions(expression, key="params", flat=True)
6016        return self.func(expression.name, *expression.expressions) + f"({params})"
6017
6018    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
6019        return self.func(expression.name, *expression.expressions)
6020
6021    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
6022        return self.anonymousaggfunc_sql(expression)
6023
6024    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
6025        return self.parameterizedagg_sql(expression)
6026
6027    def show_sql(self, expression: exp.Show) -> str:
6028        self.unsupported("Unsupported SHOW statement")
6029        return ""
6030
6031    def install_sql(self, expression: exp.Install) -> str:
6032        self.unsupported("Unsupported INSTALL statement")
6033        return ""
6034
6035    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
6036        # Snowflake GET/PUT statements:
6037        #   PUT <file> <internalStage> <properties>
6038        #   GET <internalStage> <file> <properties>
6039        props = expression.args.get("properties")
6040        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
6041        this = self.sql(expression, "this")
6042        target = self.sql(expression, "target")
6043
6044        if isinstance(expression, exp.Put):
6045            return f"PUT {this} {target}{props_sql}"
6046        else:
6047            return f"GET {target} {this}{props_sql}"
6048
6049    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
6050        this = self.sql(expression, "this")
6051        expr = self.sql(expression, "expression")
6052        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6053        return f"TRANSLATE({this} USING {expr}{with_error})"
6054
6055    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6056        if self.SUPPORTS_DECODE_CASE:
6057            return self.func("DECODE", *expression.expressions)
6058
6059        decode_expr, *expressions = expression.expressions
6060
6061        ifs = []
6062        for search, result in zip(expressions[::2], expressions[1::2]):
6063            if isinstance(search, exp.Literal):
6064                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6065            elif isinstance(search, exp.Null):
6066                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6067            else:
6068                if isinstance(search, exp.Binary):
6069                    search = exp.paren(search)
6070
6071                cond = exp.or_(
6072                    decode_expr.eq(search),
6073                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6074                    copy=False,
6075                )
6076                ifs.append(exp.If(this=cond, true=result))
6077
6078        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6079        return self.sql(case)
6080
6081    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6082        this = self.sql(expression, "this")
6083        this = self.seg(this, sep="")
6084        dimensions = self.expressions(
6085            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6086        )
6087        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6088        metrics = self.expressions(
6089            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6090        )
6091        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6092        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6093        facts = self.seg(f"FACTS {facts}") if facts else ""
6094        where = self.sql(expression, "where")
6095        where = self.seg(f"WHERE {where}") if where else ""
6096        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6097        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6098
6099    def getextract_sql(self, expression: exp.GetExtract) -> str:
6100        this = expression.this
6101        expr = expression.expression
6102
6103        if not this.type or not expression.type:
6104            import sqlglot.optimizer.annotate_types
6105
6106            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6107
6108        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6109            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6110
6111        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
6112
6113    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6114        return self.sql(
6115            exp.DateAdd(
6116                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6117                expression=expression.this,
6118                unit=exp.var("DAY"),
6119            )
6120        )
6121
6122    def space_sql(self: Generator, expression: exp.Space) -> str:
6123        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
6124
6125    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6126        return f"BUILD {self.sql(expression, 'this')}"
6127
6128    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6129        method = self.sql(expression, "method")
6130        kind = expression.args.get("kind")
6131        if not kind:
6132            return f"REFRESH {method}"
6133
6134        every = self.sql(expression, "every")
6135        unit = self.sql(expression, "unit")
6136        every = f" EVERY {every} {unit}" if every else ""
6137        starts = self.sql(expression, "starts")
6138        starts = f" STARTS {starts}" if starts else ""
6139
6140        return f"REFRESH {method} ON {kind}{every}{starts}"
6141
6142    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6143        self.unsupported("The model!attribute syntax is not supported")
6144        return ""
6145
6146    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6147        return self.func("DIRECTORY", expression.this)
6148
6149    def uuid_sql(self, expression: exp.Uuid) -> str:
6150        is_string = expression.args.get("is_string", False)
6151        uuid_func_sql = self.func("UUID")
6152
6153        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6154            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6155
6156        return uuid_func_sql
6157
6158    def initcap_sql(self, expression: exp.Initcap) -> str:
6159        delimiters = expression.expression
6160
6161        if delimiters:
6162            # do not generate delimiters arg if we are round-tripping from default delimiters
6163            if (
6164                delimiters.is_string
6165                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6166            ):
6167                delimiters = None
6168            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6169                self.unsupported("INITCAP does not support custom delimiters")
6170                delimiters = None
6171
6172        return self.func("INITCAP", expression.this, delimiters)
6173
6174    def localtime_sql(self, expression: exp.Localtime) -> str:
6175        this = expression.this
6176        return self.func("LOCALTIME", this) if this else "LOCALTIME"
6177
6178    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6179        this = expression.this
6180        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
6181
6182    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6183        this = expression.this.name.upper()
6184        if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
6185            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
6186            return "WEEK"
6187
6188        return self.func("WEEK", expression.this)
6189
6190    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6191        this = self.expressions(expression)
6192        charset = self.sql(expression, "charset")
6193        using = f" USING {charset}" if charset else ""
6194        return self.func(name, this + using)
6195
6196    def block_sql(self, expression: exp.Block) -> str:
6197        expressions = self.expressions(expression, sep="; ", flat=True)
6198        return f"{expressions}" if expressions else ""
6199
6200    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6201        self.unsupported("Unsupported Stored Procedure syntax")
6202        return ""
6203
6204    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6205        self.unsupported("Unsupported If block syntax")
6206        return ""
6207
6208    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6209        self.unsupported("Unsupported While block syntax")
6210        return ""
6211
6212    def execute_sql(self, expression: exp.Execute) -> str:
6213        self.unsupported("Unsupported Execute syntax")
6214        return ""
6215
6216    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6217        self.unsupported("Unsupported Execute syntax")
6218        return ""
6219
6220    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6221        props = self.expressions(expression, sep=" ")
6222        return f"MODIFY {props}"
6223
6224    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6225        kind = expression.args.get("kind")
6226        return f"USING {kind} {self.sql(expression, 'this')}"
6227
6228    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6229        this = self.sql(expression, "this")
6230        to = self.sql(expression, "to")
6231        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 limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 339    LIMIT_FETCH = "ALL"
 340
 341    # Whether limit and fetch allows expresions or just limits
 342    LIMIT_ONLY_LITERALS = False
 343
 344    # Whether a table is allowed to be renamed with a db
 345    RENAME_TABLE_WITH_DB = True
 346
 347    # The separator for grouping sets and rollups
 348    GROUPINGS_SEP = ","
 349
 350    # The string used for creating an index on a table
 351    INDEX_ON = "ON"
 352
 353    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 354    INOUT_SEPARATOR = " "
 355
 356    # Whether join hints should be generated
 357    JOIN_HINTS = True
 358
 359    # Whether directed joins are supported
 360    DIRECTED_JOINS = False
 361
 362    # Whether table hints should be generated
 363    TABLE_HINTS = True
 364
 365    # Whether query hints should be generated
 366    QUERY_HINTS = True
 367
 368    # What kind of separator to use for query hints
 369    QUERY_HINT_SEP = ", "
 370
 371    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 372    IS_BOOL_ALLOWED = True
 373
 374    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 375    DUPLICATE_KEY_UPDATE_WITH_SET = True
 376
 377    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 378    LIMIT_IS_TOP = False
 379
 380    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 381    RETURNING_END = True
 382
 383    # Whether to generate an unquoted value for EXTRACT's date part argument
 384    EXTRACT_ALLOWS_QUOTES = True
 385
 386    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 387    TZ_TO_WITH_TIME_ZONE = False
 388
 389    # Whether the NVL2 function is supported
 390    NVL2_SUPPORTED = True
 391
 392    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 393    SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE")
 394
 395    # Whether VALUES statements can be used as derived tables.
 396    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 397    # SELECT * VALUES into SELECT UNION
 398    VALUES_AS_TABLE = True
 399
 400    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 401    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 402
 403    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 404    UNNEST_WITH_ORDINALITY = True
 405
 406    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 407    AGGREGATE_FILTER_SUPPORTED = 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 COLLATE is a function instead of a binary operator
 434    COLLATE_IS_FUNC = False
 435
 436    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 437    DATA_TYPE_SPECIFIERS_ALLOWED = False
 438
 439    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 440    ENSURE_BOOLS = False
 441
 442    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 443    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 444
 445    # Whether CONCAT requires >1 arguments
 446    SUPPORTS_SINGLE_ARG_CONCAT = True
 447
 448    # Whether LAST_DAY function supports a date part argument
 449    LAST_DAY_SUPPORTS_DATE_PART = True
 450
 451    # Whether named columns are allowed in table aliases
 452    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 453
 454    # Whether named columns are allowed in CTE definitions
 455    SUPPORTS_NAMED_CTE_COLUMNS = True
 456
 457    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 458    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 459
 460    # What delimiter to use for separating JSON key/value pairs
 461    JSON_KEY_VALUE_PAIR_SEP = ":"
 462
 463    # INSERT OVERWRITE TABLE x override
 464    INSERT_OVERWRITE = " OVERWRITE TABLE"
 465
 466    # Whether the SELECT .. INTO syntax is used instead of CTAS
 467    SUPPORTS_SELECT_INTO = False
 468
 469    # Whether UNLOGGED tables can be created
 470    SUPPORTS_UNLOGGED_TABLES = False
 471
 472    # Whether the CREATE TABLE LIKE statement is supported
 473    SUPPORTS_CREATE_TABLE_LIKE = True
 474
 475    # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 476    SUPPORTS_MODIFY_COLUMN = False
 477
 478    # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported
 479    SUPPORTS_CHANGE_COLUMN = False
 480
 481    # Whether the LikeProperty needs to be specified inside of the schema clause
 482    LIKE_PROPERTY_INSIDE_SCHEMA = False
 483
 484    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 485    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 486    MULTI_ARG_DISTINCT = True
 487
 488    # Whether the JSON extraction operators expect a value of type JSON
 489    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 490
 491    # Whether bracketed keys like ["foo"] are supported in JSON paths
 492    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 493
 494    # Whether to escape keys using single quotes in JSON paths
 495    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 496
 497    # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be
 498    # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match
 499    # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks
 500    # where a bare colon key is case-insensitive but a bracketed key is case-sensitive.
 501    JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False
 502
 503    # The JSONPathPart expressions supported by this dialect
 504    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 505
 506    # Whether any(f(x) for x in array) can be implemented by this dialect
 507    CAN_IMPLEMENT_ARRAY_ANY = False
 508
 509    # Whether the function TO_NUMBER is supported
 510    SUPPORTS_TO_NUMBER = True
 511
 512    # Whether EXCLUDE in window specification is supported
 513    SUPPORTS_WINDOW_EXCLUDE = False
 514
 515    # Whether or not set op modifiers apply to the outer set op or select.
 516    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 517    # True means limit 1 happens after the set op, False means it it happens on y.
 518    SET_OP_MODIFIERS = True
 519
 520    # Whether parameters from COPY statement are wrapped in parentheses
 521    COPY_PARAMS_ARE_WRAPPED = True
 522
 523    # Whether values of params are set with "=" token or empty space
 524    COPY_PARAMS_EQ_REQUIRED = False
 525
 526    # Whether COPY statement has INTO keyword
 527    COPY_HAS_INTO_KEYWORD = True
 528
 529    # Whether the conditional TRY(expression) function is supported
 530    TRY_SUPPORTED = True
 531
 532    # Whether the UESCAPE syntax in unicode strings is supported
 533    SUPPORTS_UESCAPE = True
 534
 535    # Function used to replace escaped unicode codes in unicode strings
 536    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 537
 538    # The keyword to use when generating a star projection with excluded columns
 539    STAR_EXCEPT = "EXCEPT"
 540
 541    # The HEX function name
 542    HEX_FUNC = "HEX"
 543
 544    # The keywords to use when prefixing & separating WITH based properties
 545    WITH_PROPERTIES_PREFIX = "WITH"
 546
 547    # Whether to quote the generated expression of exp.JsonPath
 548    QUOTE_JSON_PATH = True
 549
 550    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 551    PAD_FILL_PATTERN_IS_REQUIRED = False
 552
 553    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 554    SUPPORTS_EXPLODING_PROJECTIONS = True
 555
 556    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 557    ARRAY_CONCAT_IS_VAR_LEN = True
 558
 559    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 560    SUPPORTS_CONVERT_TIMEZONE = False
 561
 562    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 563    SUPPORTS_MEDIAN = True
 564
 565    # Whether UNIX_SECONDS(timestamp) is supported
 566    SUPPORTS_UNIX_SECONDS = False
 567
 568    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 569    ALTER_SET_WRAPPED = False
 570
 571    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 572    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 573    # TODO: The normalization should be done by default once we've tested it across all dialects.
 574    NORMALIZE_EXTRACT_DATE_PARTS = False
 575
 576    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 577    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 578
 579    # The function name of the exp.ArraySize expression
 580    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 581
 582    # The syntax to use when altering the type of a column
 583    ALTER_SET_TYPE = "SET DATA TYPE"
 584
 585    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 586    # None -> Doesn't support it at all
 587    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 588    # True (Postgres) -> Explicitly requires it
 589    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 590
 591    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 592    SUPPORTS_DECODE_CASE = True
 593
 594    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 595    SUPPORTS_BETWEEN_FLAGS = False
 596
 597    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 598    SUPPORTS_LIKE_QUANTIFIERS = True
 599
 600    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 601    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 602
 603    # Whether to include the VARIABLE keyword for SET assignments
 604    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 605
 606    # The keyword to use for default value assignment in DECLARE statements
 607    DECLARE_DEFAULT_ASSIGNMENT = "="
 608
 609    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 610    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 611    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 612    UPDATE_STATEMENT_SUPPORTS_FROM = True
 613
 614    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 615    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 616
 617    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 618    # - Snowflake: DROP ICEBERG TABLE a.b;
 619    # - DuckDB:    DROP TABLE a.b;
 620    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 621
 622    TYPE_MAPPING: t.ClassVar = {
 623        exp.DType.DATETIME2: "TIMESTAMP",
 624        exp.DType.NCHAR: "CHAR",
 625        exp.DType.NVARCHAR: "VARCHAR",
 626        exp.DType.MEDIUMTEXT: "TEXT",
 627        exp.DType.LONGTEXT: "TEXT",
 628        exp.DType.TINYTEXT: "TEXT",
 629        exp.DType.BLOB: "VARBINARY",
 630        exp.DType.MEDIUMBLOB: "BLOB",
 631        exp.DType.LONGBLOB: "BLOB",
 632        exp.DType.TINYBLOB: "BLOB",
 633        exp.DType.INET: "INET",
 634        exp.DType.ROWVERSION: "VARBINARY",
 635        exp.DType.SMALLDATETIME: "TIMESTAMP",
 636    }
 637
 638    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 639
 640    # mapping of DType to its default parameters, bounds
 641    TYPE_PARAM_SETTINGS: t.ClassVar[
 642        dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]
 643    ] = {}
 644
 645    TIME_PART_SINGULARS: t.ClassVar = {
 646        "MICROSECONDS": "MICROSECOND",
 647        "SECONDS": "SECOND",
 648        "MINUTES": "MINUTE",
 649        "HOURS": "HOUR",
 650        "DAYS": "DAY",
 651        "WEEKS": "WEEK",
 652        "MONTHS": "MONTH",
 653        "QUARTERS": "QUARTER",
 654        "YEARS": "YEAR",
 655    }
 656
 657    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 658        "cluster": lambda self, e: self.sql(e, "cluster"),
 659        "distribute": lambda self, e: self.sql(e, "distribute"),
 660        "sort": lambda self, e: self.sql(e, "sort"),
 661        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 662    }
 663
 664    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 665
 666    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 667
 668    PARAMETER_TOKEN = "@"
 669    NAMED_PLACEHOLDER_TOKEN = ":"
 670
 671    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 672
 673    PROPERTIES_LOCATION: t.ClassVar = {
 674        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 675        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 676        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 677        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 678        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 679        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 680        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 681        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 682        exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA,
 683        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 684        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 685        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 686        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 687        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 688        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 689        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 690        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 691        exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA,
 692        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 693        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 694        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 695        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 696        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 697        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 698        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 699        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 700        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 701        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 702        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 703        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 704        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 705        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 706        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 707        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 708        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 709        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 710        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 711        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 712        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 713        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 714        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 715        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 716        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 717        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 718        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 719        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 720        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 721        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 722        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 723        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 724        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 725        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 726        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 727        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 728        exp.LogProperty: exp.Properties.Location.POST_NAME,
 729        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 730        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 731        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 732        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 733        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 734        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 735        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 736        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 737        exp.Order: exp.Properties.Location.POST_SCHEMA,
 738        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 739        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 740        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 741        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 742        exp.Property: exp.Properties.Location.POST_WITH,
 743        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 744        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 745        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 746        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 747        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 748        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 749        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 750        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 751        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 752        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 753        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 754        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 755        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 756        exp.Set: exp.Properties.Location.POST_SCHEMA,
 757        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 758        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 759        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 760        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 761        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 762        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 763        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 764        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 765        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 766        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 767        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 768        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 769        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 770        exp.Tags: exp.Properties.Location.POST_WITH,
 771        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 772        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 773        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 774        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 775        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 776        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 777        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 778        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 779        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 780        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 781        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 782        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 783        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 784        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 785        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 786        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 787        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 788    }
 789
 790    # Keywords that can't be used as unquoted identifier names
 791    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 792
 793    # Exprs whose comments are separated from them for better formatting
 794    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 795        exp.Command,
 796        exp.Create,
 797        exp.Describe,
 798        exp.Delete,
 799        exp.Drop,
 800        exp.From,
 801        exp.Insert,
 802        exp.Join,
 803        exp.MultitableInserts,
 804        exp.Order,
 805        exp.Group,
 806        exp.Having,
 807        exp.Select,
 808        exp.SetOperation,
 809        exp.Update,
 810        exp.Where,
 811        exp.With,
 812    )
 813
 814    # Exprs that should not have their comments generated in maybe_comment
 815    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 816        exp.Binary,
 817        exp.SetOperation,
 818    )
 819
 820    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 821    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 822        exp.Column,
 823        exp.Literal,
 824        exp.Neg,
 825        exp.Paren,
 826    )
 827
 828    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 829        exp.DType.NVARCHAR,
 830        exp.DType.VARCHAR,
 831        exp.DType.CHAR,
 832        exp.DType.NCHAR,
 833    }
 834
 835    # Exprs that need to have all CTEs under them bubbled up to them
 836    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 837
 838    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 839
 840    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 841
 842    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 843
 844    __slots__ = (
 845        "pretty",
 846        "identify",
 847        "normalize",
 848        "pad",
 849        "_indent",
 850        "normalize_functions",
 851        "unsupported_level",
 852        "max_unsupported",
 853        "leading_comma",
 854        "max_text_width",
 855        "comments",
 856        "dialect",
 857        "unsupported_messages",
 858        "_escaped_quote_end",
 859        "_escaped_byte_quote_end",
 860        "_escaped_identifier_end",
 861        "_next_name",
 862        "_identifier_start",
 863        "_identifier_end",
 864        "_quote_json_path_key_using_brackets",
 865        "_dispatch",
 866    )
 867
 868    def __init__(
 869        self,
 870        pretty: bool | int | None = None,
 871        identify: str | bool = False,
 872        normalize: bool = False,
 873        pad: int = 2,
 874        indent: int = 2,
 875        normalize_functions: str | bool | None = None,
 876        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 877        max_unsupported: int = 3,
 878        leading_comma: bool = False,
 879        max_text_width: int = 80,
 880        comments: bool = True,
 881        dialect: DialectType = None,
 882    ):
 883        import sqlglot
 884        import sqlglot.dialects.dialect
 885
 886        self.pretty = pretty if pretty is not None else sqlglot.pretty
 887        self.identify = identify
 888        self.normalize = normalize
 889        self.pad = pad
 890        self._indent = indent
 891        self.unsupported_level = unsupported_level
 892        self.max_unsupported = max_unsupported
 893        self.leading_comma = leading_comma
 894        self.max_text_width = max_text_width
 895        self.comments = comments
 896        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 897
 898        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 899        self.normalize_functions = (
 900            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 901        )
 902
 903        self.unsupported_messages: list[str] = []
 904        self._escaped_quote_end: str = (
 905            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 906        )
 907        self._escaped_byte_quote_end: str = (
 908            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 909            if self.dialect.BYTE_END
 910            else ""
 911        )
 912        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 913
 914        self._next_name = name_sequence("_t")
 915
 916        self._identifier_start = self.dialect.IDENTIFIER_START
 917        self._identifier_end = self.dialect.IDENTIFIER_END
 918
 919        self._quote_json_path_key_using_brackets = True
 920
 921        cls = type(self)
 922        dispatch = _DISPATCH_CACHE.get(cls)
 923        if dispatch is None:
 924            dispatch = _build_dispatch(cls)
 925            _DISPATCH_CACHE[cls] = dispatch
 926        self._dispatch = dispatch
 927
 928    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 929        """
 930        Generates the SQL string corresponding to the given syntax tree.
 931
 932        Args:
 933            expression: The syntax tree.
 934            copy: Whether to copy the expression. The generator performs mutations so
 935                it is safer to copy.
 936
 937        Returns:
 938            The SQL string corresponding to `expression`.
 939        """
 940        if copy:
 941            expression = expression.copy()
 942
 943        expression = self.preprocess(expression)
 944
 945        self.unsupported_messages = []
 946        sql = self.sql(expression).strip()
 947
 948        if self.pretty:
 949            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 950
 951        if self.unsupported_level == ErrorLevel.IGNORE:
 952            return sql
 953
 954        if self.unsupported_level == ErrorLevel.WARN:
 955            for msg in self.unsupported_messages:
 956                logger.warning(msg)
 957        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 958            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 959
 960        return sql
 961
 962    def preprocess(self, expression: exp.Expr) -> exp.Expr:
 963        """Apply generic preprocessing transformations to a given expression."""
 964        expression = self._move_ctes_to_top_level(expression)
 965
 966        if self.ENSURE_BOOLS:
 967            import sqlglot.transforms
 968
 969            expression = sqlglot.transforms.ensure_bools(expression)
 970
 971        return expression
 972
 973    def _move_ctes_to_top_level(self, expression: E) -> E:
 974        if (
 975            not expression.parent
 976            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 977            and any(node.parent is not expression for node in expression.find_all(exp.With))
 978        ):
 979            import sqlglot.transforms
 980
 981            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
 982        return expression
 983
 984    def unsupported(self, message: str) -> None:
 985        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 986            raise UnsupportedError(message)
 987        self.unsupported_messages.append(message)
 988
 989    def sep(self, sep: str = " ") -> str:
 990        return f"{sep.strip()}\n" if self.pretty else sep
 991
 992    def seg(self, sql: str, sep: str = " ") -> str:
 993        return f"{self.sep(sep)}{sql}"
 994
 995    def sanitize_comment(self, comment: str) -> str:
 996        comment = " " + comment if comment[0].strip() else comment
 997        comment = comment + " " if comment[-1].strip() else comment
 998
 999        # Escape block comment markers to prevent premature closure or unintended nesting.
1000        # This is necessary because single-line comments (--) are converted to block comments
1001        # (/* */) on output, and any */ in the original text would close the comment early.
1002        comment = comment.replace("*/", "* /").replace("/*", "/ *")
1003
1004        return comment
1005
1006    def maybe_comment(
1007        self,
1008        sql: str,
1009        expression: exp.Expr | None = None,
1010        comments: list[str] | None = None,
1011        separated: bool = False,
1012    ) -> str:
1013        comments = (
1014            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1015            if self.comments
1016            else None
1017        )
1018
1019        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1020            return sql
1021
1022        comments_list = [
1023            f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/"
1024            for comment in comments
1025            if comment
1026        ]
1027
1028        if not comments_list:
1029            return sql
1030
1031        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1032            comments_sql = self.sep().join(comments_list)
1033            return (
1034                f"{self.sep()}{comments_sql}{sql}"
1035                if not sql or sql[0].isspace()
1036                else f"{comments_sql}{self.sep()}{sql}"
1037            )
1038
1039        return f"{sql} {' '.join(comments_list)}"
1040
1041    def wrap(self, expression: exp.Expr | str) -> str:
1042        this_sql = (
1043            self.sql(expression)
1044            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1045            else self.sql(expression, "this")
1046        )
1047        if not this_sql:
1048            return "()"
1049
1050        this_sql = self.indent(this_sql, level=1, pad=0)
1051        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1052
1053    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1054        original = self.identify
1055        self.identify = False
1056        result = func(*args, **kwargs)
1057        self.identify = original
1058        return result
1059
1060    def normalize_func(self, name: str) -> str:
1061        if self.normalize_functions == "upper" or self.normalize_functions is True:
1062            return name.upper()
1063        if self.normalize_functions == "lower":
1064            return name.lower()
1065        return name
1066
1067    def indent(
1068        self,
1069        sql: str,
1070        level: int = 0,
1071        pad: int | None = None,
1072        skip_first: bool = False,
1073        skip_last: bool = False,
1074    ) -> str:
1075        if not self.pretty or not sql:
1076            return sql
1077
1078        pad = self.pad if pad is None else pad
1079        lines = sql.split("\n")
1080
1081        return "\n".join(
1082            (
1083                line
1084                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1085                else f"{' ' * (level * self._indent + pad)}{line}"
1086            )
1087            for i, line in enumerate(lines)
1088        )
1089
1090    def sql(
1091        self,
1092        expression: str | exp.Expr | None,
1093        key: str | None = None,
1094        comment: bool = True,
1095    ) -> str:
1096        if not expression:
1097            return ""
1098
1099        if isinstance(expression, str):
1100            return expression
1101
1102        if key:
1103            value = expression.args.get(key)
1104            if value:
1105                return self.sql(value)
1106            return ""
1107
1108        handler = self._dispatch.get(expression.__class__)
1109
1110        if handler:
1111            sql = handler(self, expression)
1112        elif isinstance(expression, exp.Func):
1113            sql = self.function_fallback_sql(expression)
1114        elif isinstance(expression, exp.Property):
1115            sql = self.property_sql(expression)
1116        else:
1117            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1118
1119        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1120
1121    def uncache_sql(self, expression: exp.Uncache) -> str:
1122        table = self.sql(expression, "this")
1123        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1124        return f"UNCACHE TABLE{exists_sql} {table}"
1125
1126    def cache_sql(self, expression: exp.Cache) -> str:
1127        lazy = " LAZY" if expression.args.get("lazy") else ""
1128        table = self.sql(expression, "this")
1129        options = expression.args.get("options")
1130        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1131        sql = self.sql(expression, "expression")
1132        sql = f" AS{self.sep()}{sql}" if sql else ""
1133        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1134        return self.prepend_ctes(expression, sql)
1135
1136    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1137        default = "DEFAULT " if expression.args.get("default") else ""
1138        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1139
1140    def column_parts(self, expression: exp.Column) -> str:
1141        return ".".join(
1142            self.sql(part)
1143            for part in (
1144                expression.args.get("catalog"),
1145                expression.args.get("db"),
1146                expression.args.get("table"),
1147                expression.args.get("this"),
1148            )
1149            if part
1150        )
1151
1152    def column_sql(self, expression: exp.Column) -> str:
1153        join_mark = " (+)" if expression.args.get("join_mark") else ""
1154
1155        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1156            join_mark = ""
1157            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1158
1159        return f"{self.column_parts(expression)}{join_mark}"
1160
1161    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1162        return self.column_sql(expression)
1163
1164    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1165        this = self.sql(expression, "this")
1166        this = f" {this}" if this else ""
1167        position = self.sql(expression, "position")
1168        return f"{position}{this}"
1169
1170    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1171        column = self.sql(expression, "this")
1172        kind = self.sql(expression, "kind")
1173        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1174        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1175        kind = f"{sep}{kind}" if kind else ""
1176        constraints = f" {constraints}" if constraints else ""
1177        position = self.sql(expression, "position")
1178        position = f" {position}" if position else ""
1179
1180        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1181            kind = ""
1182
1183        return f"{exists}{column}{kind}{constraints}{position}"
1184
1185    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1186        this = self.sql(expression, "this")
1187        kind_sql = self.sql(expression, "kind").strip()
1188        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1189
1190    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1191        this = self.sql(expression, "this")
1192        if expression.args.get("not_null"):
1193            persisted = " PERSISTED NOT NULL"
1194        elif expression.args.get("persisted"):
1195            persisted = " PERSISTED"
1196        else:
1197            persisted = ""
1198
1199        return f"AS {this}{persisted}"
1200
1201    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1202        return self.token_sql(TokenType.AUTO_INCREMENT)
1203
1204    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1205        if isinstance(expression.this, list):
1206            this = self.wrap(self.expressions(expression, key="this", flat=True))
1207        else:
1208            this = self.sql(expression, "this")
1209
1210        return f"COMPRESS {this}"
1211
1212    def generatedasidentitycolumnconstraint_sql(
1213        self, expression: exp.GeneratedAsIdentityColumnConstraint
1214    ) -> str:
1215        this = ""
1216        if expression.this is not None:
1217            on_null = " ON NULL" if expression.args.get("on_null") else ""
1218            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1219
1220        start = expression.args.get("start")
1221        start = f"START WITH {start}" if start else ""
1222        increment = expression.args.get("increment")
1223        increment = f" INCREMENT BY {increment}" if increment else ""
1224        minvalue = expression.args.get("minvalue")
1225        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1226        maxvalue = expression.args.get("maxvalue")
1227        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1228        cycle = expression.args.get("cycle")
1229        cycle_sql = ""
1230
1231        if cycle is not None:
1232            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1233            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1234
1235        sequence_opts = ""
1236        if start or increment or cycle_sql:
1237            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1238            sequence_opts = f" ({sequence_opts.strip()})"
1239
1240        expr = self.sql(expression, "expression")
1241        expr = f"({expr})" if expr else "IDENTITY"
1242
1243        return f"GENERATED{this} AS {expr}{sequence_opts}"
1244
1245    def generatedasrowcolumnconstraint_sql(
1246        self, expression: exp.GeneratedAsRowColumnConstraint
1247    ) -> str:
1248        start = "START" if expression.args.get("start") else "END"
1249        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1250        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1251
1252    def periodforsystemtimeconstraint_sql(
1253        self, expression: exp.PeriodForSystemTimeConstraint
1254    ) -> str:
1255        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1256
1257    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1258        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1259
1260    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1261        desc = expression.args.get("desc")
1262        if desc is not None:
1263            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1264        options = self.expressions(expression, key="options", flat=True, sep=" ")
1265        options = f" {options}" if options else ""
1266        return f"PRIMARY KEY{options}"
1267
1268    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1269        this = self.sql(expression, "this")
1270        this = f" {this}" if this else ""
1271        index_type = expression.args.get("index_type")
1272        index_type = f" USING {index_type}" if index_type else ""
1273        on_conflict = self.sql(expression, "on_conflict")
1274        on_conflict = f" {on_conflict}" if on_conflict else ""
1275        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1276        options = self.expressions(expression, key="options", flat=True, sep=" ")
1277        options = f" {options}" if options else ""
1278        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1279
1280    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1281        input_ = expression.args.get("input_")
1282        output = expression.args.get("output")
1283        variadic = expression.args.get("variadic")
1284
1285        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1286        if variadic:
1287            return "VARIADIC"
1288
1289        if input_ and output:
1290            return f"IN{self.INOUT_SEPARATOR}OUT"
1291        if input_:
1292            return "IN"
1293        if output:
1294            return "OUT"
1295
1296        return ""
1297
1298    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1299        return self.sql(expression, "this")
1300
1301    def create_sql(self, expression: exp.Create) -> str:
1302        kind = self.sql(expression, "kind")
1303        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1304
1305        properties = expression.args.get("properties")
1306
1307        if (
1308            kind == "TRIGGER"
1309            and properties
1310            and properties.expressions
1311            and isinstance(properties.expressions[0], exp.TriggerProperties)
1312            and properties.expressions[0].args.get("constraint")
1313        ):
1314            kind = f"CONSTRAINT {kind}"
1315
1316        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1317
1318        this = self.createable_sql(expression, properties_locs)
1319
1320        properties_sql = ""
1321        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1322            exp.Properties.Location.POST_WITH
1323        ):
1324            props_ast = exp.Properties(
1325                expressions=[
1326                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1327                    *properties_locs[exp.Properties.Location.POST_WITH],
1328                ]
1329            )
1330            props_ast.parent = expression
1331            properties_sql = self.sql(props_ast)
1332
1333            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1334                properties_sql = self.sep() + properties_sql
1335            elif not self.pretty:
1336                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1337                properties_sql = f" {properties_sql}"
1338
1339        begin = " BEGIN" if expression.args.get("begin") else ""
1340
1341        expression_sql = self.sql(expression, "expression")
1342        if expression_sql:
1343            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1344
1345            if not isinstance(expression.expression, exp.MacroOverloads) and (
1346                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1347            ):
1348                postalias_props_sql = ""
1349                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1350                    postalias_props_sql = self.properties(
1351                        exp.Properties(
1352                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1353                        ),
1354                        wrapped=False,
1355                    )
1356                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1357                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1358
1359        postindex_props_sql = ""
1360        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1361            postindex_props_sql = self.properties(
1362                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1363                wrapped=False,
1364                prefix=" ",
1365            )
1366
1367        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1368        indexes = f" {indexes}" if indexes else ""
1369        index_sql = indexes + postindex_props_sql
1370
1371        replace = " OR REPLACE" if expression.args.get("replace") else ""
1372        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1373        unique = " UNIQUE" if expression.args.get("unique") else ""
1374
1375        clustered = expression.args.get("clustered")
1376        if clustered is None:
1377            clustered_sql = ""
1378        elif clustered:
1379            clustered_sql = " CLUSTERED COLUMNSTORE"
1380        else:
1381            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1382
1383        postcreate_props_sql = ""
1384        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1385            postcreate_props_sql = self.properties(
1386                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1387                sep=" ",
1388                prefix=" ",
1389                wrapped=False,
1390            )
1391
1392        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1393
1394        postexpression_props_sql = ""
1395        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1396            postexpression_props_sql = self.properties(
1397                exp.Properties(
1398                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1399                ),
1400                sep=" ",
1401                prefix=" ",
1402                wrapped=False,
1403            )
1404
1405        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1406        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1407        no_schema_binding = (
1408            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1409        )
1410
1411        clone = self.sql(expression, "clone")
1412        clone = f" {clone}" if clone else ""
1413
1414        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1415            properties_expression = f"{expression_sql}{properties_sql}"
1416        else:
1417            properties_expression = f"{properties_sql}{expression_sql}"
1418
1419        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1420        return self.prepend_ctes(expression, expression_sql)
1421
1422    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1423        start = self.sql(expression, "start")
1424        start = f"START WITH {start}" if start else ""
1425        increment = self.sql(expression, "increment")
1426        increment = f" INCREMENT BY {increment}" if increment else ""
1427        minvalue = self.sql(expression, "minvalue")
1428        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1429        maxvalue = self.sql(expression, "maxvalue")
1430        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1431        owned = self.sql(expression, "owned")
1432        owned = f" OWNED BY {owned}" if owned else ""
1433
1434        cache = expression.args.get("cache")
1435        if cache is None:
1436            cache_str = ""
1437        elif cache is True:
1438            cache_str = " CACHE"
1439        else:
1440            cache_str = f" CACHE {cache}"
1441
1442        options = self.expressions(expression, key="options", flat=True, sep=" ")
1443        options = f" {options}" if options else ""
1444
1445        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1446
1447    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1448        timing = expression.args.get("timing", "")
1449        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1450        timing_events = f"{timing} {events}".strip() if timing or events else ""
1451
1452        parts = [timing_events, "ON", self.sql(expression, "table")]
1453
1454        if referenced_table := expression.args.get("referenced_table"):
1455            parts.extend(["FROM", self.sql(referenced_table)])
1456
1457        if deferrable := expression.args.get("deferrable"):
1458            parts.append(deferrable)
1459
1460        if initially := expression.args.get("initially"):
1461            parts.append(f"INITIALLY {initially}")
1462
1463        if referencing := expression.args.get("referencing"):
1464            parts.append(self.sql(referencing))
1465
1466        if for_each := expression.args.get("for_each"):
1467            parts.append(f"FOR EACH {for_each}")
1468
1469        if when := expression.args.get("when"):
1470            parts.append(f"WHEN ({self.sql(when)})")
1471
1472        parts.append(self.sql(expression, "execute"))
1473
1474        return self.sep().join(parts)
1475
1476    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1477        parts = []
1478
1479        if old_alias := expression.args.get("old"):
1480            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1481
1482        if new_alias := expression.args.get("new"):
1483            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1484
1485        return f"REFERENCING {' '.join(parts)}"
1486
1487    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1488        columns = expression.args.get("columns")
1489        if columns:
1490            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1491
1492        return self.sql(expression, "this")
1493
1494    def clone_sql(self, expression: exp.Clone) -> str:
1495        this = self.sql(expression, "this")
1496        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1497        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1498        return f"{shallow}{keyword} {this}"
1499
1500    def describe_sql(self, expression: exp.Describe) -> str:
1501        style = expression.args.get("style")
1502        style = f" {style}" if style else ""
1503        partition = self.sql(expression, "partition")
1504        partition = f" {partition}" if partition else ""
1505        format = self.sql(expression, "format")
1506        format = f" {format}" if format else ""
1507        as_json = " AS JSON" if expression.args.get("as_json") else ""
1508
1509        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1510
1511    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1512        tag = self.sql(expression, "tag")
1513        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1514
1515    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1516        with_ = self.sql(expression, "with_")
1517        if with_:
1518            sql = f"{with_}{self.sep()}{sql}"
1519        return sql
1520
1521    def with_sql(self, expression: exp.With) -> str:
1522        sql = self.expressions(expression, flat=True)
1523        recursive = (
1524            "RECURSIVE "
1525            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1526            else ""
1527        )
1528        search = self.sql(expression, "search")
1529        search = f" {search}" if search else ""
1530
1531        return f"WITH {recursive}{sql}{search}"
1532
1533    def cte_sql(self, expression: exp.CTE) -> str:
1534        alias = expression.args.get("alias")
1535        if alias:
1536            alias.add_comments(expression.pop_comments())
1537
1538        alias_sql = self.sql(expression, "alias")
1539
1540        materialized = expression.args.get("materialized")
1541        if materialized is False:
1542            materialized = "NOT MATERIALIZED "
1543        elif materialized:
1544            materialized = "MATERIALIZED "
1545
1546        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1547        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1548
1549        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1550
1551    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1552        alias = self.sql(expression, "this")
1553        columns = self.expressions(expression, key="columns", flat=True)
1554        columns = f"({columns})" if columns else ""
1555
1556        if (
1557            columns
1558            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1559            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1560        ):
1561            columns = ""
1562            self.unsupported("Named columns are not supported in table alias.")
1563
1564        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1565            alias = self._next_name()
1566
1567        return f"{alias}{columns}"
1568
1569    def bitstring_sql(self, expression: exp.BitString) -> str:
1570        this = self.sql(expression, "this")
1571        if self.dialect.BIT_START:
1572            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1573        return f"{int(this, 2)}"
1574
1575    def hexstring_sql(
1576        self, expression: exp.HexString, binary_function_repr: str | None = None
1577    ) -> str:
1578        this = self.sql(expression, "this")
1579        is_integer_type = expression.args.get("is_integer")
1580
1581        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1582            not self.dialect.HEX_START and not binary_function_repr
1583        ):
1584            # Integer representation will be returned if:
1585            # - The read dialect treats the hex value as integer literal but not the write
1586            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1587            return f"{int(this, 16)}"
1588
1589        if not is_integer_type:
1590            # Read dialect treats the hex value as BINARY/BLOB
1591            if binary_function_repr:
1592                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1593                return self.func(binary_function_repr, exp.Literal.string(this))
1594            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1595                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1596                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1597
1598        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1599
1600    def bytestring_sql(self, expression: exp.ByteString) -> str:
1601        this = self.sql(expression, "this")
1602        if self.dialect.BYTE_START:
1603            escaped_byte_string = self.escape_str(
1604                this,
1605                escape_backslash=False,
1606                delimiter=self.dialect.BYTE_END,
1607                escaped_delimiter=self._escaped_byte_quote_end,
1608                is_byte_string=True,
1609            )
1610            is_bytes = expression.args.get("is_bytes", False)
1611            delimited_byte_string = (
1612                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1613            )
1614            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1615                return self.sql(
1616                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1617                )
1618            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1619                return self.sql(
1620                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1621                )
1622
1623            return delimited_byte_string
1624
1625        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1626            return self.sql(exp.Literal.string(this))
1627
1628        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1629        return ""
1630
1631    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1632        this = self.sql(expression, "this")
1633        escape = expression.args.get("escape")
1634
1635        if self.dialect.UNICODE_START:
1636            escape_substitute = r"\\\1"
1637            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1638        else:
1639            escape_substitute = r"\\u\1"
1640            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1641
1642        if escape:
1643            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1644            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1645        else:
1646            escape_pattern = ESCAPED_UNICODE_RE
1647            escape_sql = ""
1648
1649        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1650            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1651
1652        return f"{left_quote}{this}{right_quote}{escape_sql}"
1653
1654    def rawstring_sql(self, expression: exp.RawString) -> str:
1655        string = expression.this
1656        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1657            string = string.replace("\\", "\\\\")
1658
1659        string = self.escape_str(string, escape_backslash=False)
1660        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1661
1662    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1663        this = self.sql(expression, "this")
1664        specifier = self.sql(expression, "expression")
1665        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1666        return f"{this}{specifier}"
1667
1668    def datatype_param_bound_limiter(
1669        self,
1670        expression: exp.DataType,
1671        type_value: exp.DType,
1672        defaults: tuple[int, ...],
1673        bounds: tuple[int | None, ...],
1674    ) -> exp.DataType:
1675        params = expression.expressions
1676
1677        if not params:
1678            if defaults:
1679                expression.set(
1680                    "expressions",
1681                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1682                )
1683            return expression
1684
1685        if not bounds:
1686            return expression
1687
1688        for i, param in enumerate(params):
1689            bound = bounds[i] if i < len(bounds) else None
1690            if bound is None:
1691                continue
1692
1693            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1694            if (
1695                isinstance(param_value, exp.Literal)
1696                and param_value.is_number
1697                and int(param_value.to_py()) > bound
1698            ):
1699                self.unsupported(
1700                    f"{type_value.value} parameter {param_value.name} exceeds "
1701                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1702                )
1703                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1704
1705        return expression
1706
1707    def datatype_sql(self, expression: exp.DataType) -> str:
1708        nested = ""
1709        values = ""
1710
1711        expr_nested = expression.args.get("nested")
1712        type_value = expression.this
1713
1714        if (
1715            not expr_nested
1716            and isinstance(type_value, exp.DType)
1717            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1718        ):
1719            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1720
1721        interior = (
1722            self.expressions(
1723                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1724            )
1725            if expr_nested and self.pretty
1726            else self.expressions(expression, flat=True)
1727        )
1728
1729        if type_value in self.UNSUPPORTED_TYPES:
1730            self.unsupported(
1731                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1732            )
1733
1734        type_sql: t.Any = ""
1735        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1736            type_sql = self.sql(expression, "kind")
1737        elif type_value == exp.DType.CHARACTER_SET:
1738            return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}"
1739        else:
1740            type_sql = (
1741                self.TYPE_MAPPING.get(type_value, type_value.value)
1742                if isinstance(type_value, exp.DType)
1743                else type_value
1744            )
1745
1746        if interior:
1747            if expr_nested:
1748                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1749                if expression.args.get("values") is not None:
1750                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1751                    values = self.expressions(expression, key="values", flat=True)
1752                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1753            elif type_value == exp.DType.INTERVAL:
1754                nested = f" {interior}"
1755            else:
1756                nested = f"({interior})"
1757
1758        type_sql = f"{type_sql}{nested}{values}"
1759        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1760            exp.DType.TIMETZ,
1761            exp.DType.TIMESTAMPTZ,
1762        ):
1763            type_sql = f"{type_sql} WITH TIME ZONE"
1764
1765        collate = self.sql(expression, "collate")
1766        if collate:
1767            type_sql = f"{type_sql} COLLATE {collate}"
1768
1769        return type_sql
1770
1771    def directory_sql(self, expression: exp.Directory) -> str:
1772        local = "LOCAL " if expression.args.get("local") else ""
1773        row_format = self.sql(expression, "row_format")
1774        row_format = f" {row_format}" if row_format else ""
1775        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1776
1777    def delete_sql(self, expression: exp.Delete) -> str:
1778        hint = self.sql(expression, "hint")
1779        this = self.sql(expression, "this")
1780        this = f" FROM {this}" if this else ""
1781        using = self.expressions(expression, key="using")
1782        using = f" USING {using}" if using else ""
1783        cluster = self.sql(expression, "cluster")
1784        cluster = f" {cluster}" if cluster else ""
1785        where = self.sql(expression, "where")
1786        returning = self.sql(expression, "returning")
1787        order = self.sql(expression, "order")
1788        limit = self.sql(expression, "limit")
1789        tables = self.expressions(expression, key="tables")
1790        tables = f" {tables}" if tables else ""
1791        if self.RETURNING_END:
1792            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1793        else:
1794            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1795        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1796
1797    def drop_sql(self, expression: exp.Drop) -> str:
1798        this = self.sql(expression, "this")
1799        expressions = self.expressions(expression, flat=True)
1800        expressions = f" ({expressions})" if expressions else ""
1801        kind = expression.args["kind"]
1802        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1803        iceberg = (
1804            " ICEBERG"
1805            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1806            else ""
1807        )
1808        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1809        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1810        on_cluster = self.sql(expression, "cluster")
1811        on_cluster = f" {on_cluster}" if on_cluster else ""
1812        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1813        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1814        cascade = " CASCADE" if expression.args.get("cascade") else ""
1815        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1816        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1817        purge = " PURGE" if expression.args.get("purge") else ""
1818        sync = " SYNC" if expression.args.get("sync") else ""
1819        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}"
1820
1821    def set_operation(self, expression: exp.SetOperation) -> str:
1822        op_type = type(expression)
1823        op_name = op_type.key.upper()
1824
1825        distinct = expression.args.get("distinct")
1826        if (
1827            distinct is False
1828            and op_type in (exp.Except, exp.Intersect)
1829            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1830        ):
1831            self.unsupported(f"{op_name} ALL is not supported")
1832
1833        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1834
1835        if distinct is None:
1836            distinct = default_distinct
1837            if distinct is None:
1838                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1839
1840        if distinct is default_distinct:
1841            distinct_or_all = ""
1842        else:
1843            distinct_or_all = " DISTINCT" if distinct else " ALL"
1844
1845        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1846        side_kind = f"{side_kind} " if side_kind else ""
1847
1848        by_name = " BY NAME" if expression.args.get("by_name") else ""
1849        on = self.expressions(expression, key="on", flat=True)
1850        on = f" ON ({on})" if on else ""
1851
1852        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1853
1854    def set_operations(self, expression: exp.SetOperation) -> str:
1855        if not self.SET_OP_MODIFIERS:
1856            limit = expression.args.get("limit")
1857            order = expression.args.get("order")
1858
1859            if limit or order:
1860                select = self._move_ctes_to_top_level(
1861                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1862                )
1863
1864                if limit:
1865                    select = select.limit(limit.pop(), copy=False)
1866                if order:
1867                    select = select.order_by(order.pop(), copy=False)
1868                return self.sql(select)
1869
1870        sqls: list[str] = []
1871        stack: list[str | exp.Expr] = [expression]
1872
1873        while stack:
1874            node = stack.pop()
1875
1876            if isinstance(node, exp.SetOperation):
1877                stack.append(node.expression)
1878                stack.append(
1879                    self.maybe_comment(
1880                        self.set_operation(node), comments=node.comments, separated=True
1881                    )
1882                )
1883                stack.append(node.this)
1884            else:
1885                sqls.append(self.sql(node))
1886
1887        this = self.sep().join(sqls)
1888        this = self.query_modifiers(expression, this)
1889        return self.prepend_ctes(expression, this)
1890
1891    def fetch_sql(self, expression: exp.Fetch) -> str:
1892        direction = expression.args.get("direction")
1893        direction = f" {direction}" if direction else ""
1894        count = self.sql(expression, "count")
1895        count = f" {count}" if count else ""
1896        limit_options = self.sql(expression, "limit_options")
1897        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1898        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1899
1900    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1901        percent = " PERCENT" if expression.args.get("percent") else ""
1902        rows = " ROWS" if expression.args.get("rows") else ""
1903        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1904        if not with_ties and rows:
1905            with_ties = " ONLY"
1906        return f"{percent}{rows}{with_ties}"
1907
1908    def filter_sql(self, expression: exp.Filter) -> str:
1909        if self.AGGREGATE_FILTER_SUPPORTED:
1910            this = self.sql(expression, "this")
1911            where = self.sql(expression, "expression").strip()
1912            return f"{this} FILTER({where})"
1913
1914        agg = expression.this
1915        agg_arg = agg.this
1916        cond = expression.expression.this
1917        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1918        return self.sql(agg)
1919
1920    def hint_sql(self, expression: exp.Hint) -> str:
1921        if not self.QUERY_HINTS:
1922            self.unsupported("Hints are not supported")
1923            return ""
1924
1925        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1926
1927    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1928        using = self.sql(expression, "using")
1929        using = f" USING {using}" if using else ""
1930        columns = self.expressions(expression, key="columns", flat=True)
1931        columns = f"({columns})" if columns else ""
1932        partition_by = self.expressions(expression, key="partition_by", flat=True)
1933        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1934        where = self.sql(expression, "where")
1935        include = self.expressions(expression, key="include", flat=True)
1936        if include:
1937            include = f" INCLUDE ({include})"
1938        with_storage = self.expressions(expression, key="with_storage", flat=True)
1939        with_storage = f" WITH ({with_storage})" if with_storage else ""
1940        tablespace = self.sql(expression, "tablespace")
1941        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1942        on = self.sql(expression, "on")
1943        on = f" ON {on}" if on else ""
1944
1945        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1946
1947    def index_sql(self, expression: exp.Index) -> str:
1948        unique = "UNIQUE " if expression.args.get("unique") else ""
1949        primary = "PRIMARY " if expression.args.get("primary") else ""
1950        amp = "AMP " if expression.args.get("amp") else ""
1951        name = self.sql(expression, "this")
1952        name = f"{name} " if name else ""
1953        table = self.sql(expression, "table")
1954        table = f"{self.INDEX_ON} {table}" if table else ""
1955
1956        index = "INDEX " if not table else ""
1957
1958        params = self.sql(expression, "params")
1959        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1960
1961    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
1962        this = expression.this
1963        if this and this.is_string:
1964            resolved = maybe_parse(this.name).sql(self.dialect)
1965            if "expressions" in expression.args:
1966                # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
1967                # We can't safely emit the call to other dialects since name/arg semantics may differ
1968                self.unsupported(
1969                    "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported"
1970                )
1971            return resolved
1972        self.unsupported("IDENTIFIER() with non-literal arguments is not supported")
1973        return self.func("IDENTIFIER", this)
1974
1975    def identifier_sql(self, expression: exp.Identifier) -> str:
1976        text = expression.name
1977        lower = text.lower()
1978        quoted = expression.quoted
1979        text = lower if self.normalize and not quoted else text
1980        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1981        if (
1982            quoted
1983            or self.dialect.can_quote(expression, self.identify)
1984            or lower in self.RESERVED_KEYWORDS
1985            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1986        ):
1987            text = (
1988                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
1989            )
1990        return text
1991
1992    def hex_sql(self, expression: exp.Hex) -> str:
1993        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1994        if self.dialect.HEX_LOWERCASE:
1995            text = self.func("LOWER", text)
1996
1997        return text
1998
1999    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
2000        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2001        if not self.dialect.HEX_LOWERCASE:
2002            text = self.func("LOWER", text)
2003        return text
2004
2005    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
2006        input_format = self.sql(expression, "input_format")
2007        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
2008        output_format = self.sql(expression, "output_format")
2009        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
2010        return self.sep().join((input_format, output_format))
2011
2012    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
2013        string = self.sql(exp.Literal.string(expression.name))
2014        return f"{prefix}{string}"
2015
2016    def partition_sql(self, expression: exp.Partition) -> str:
2017        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
2018        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
2019
2020    def properties_sql(self, expression: exp.Properties) -> str:
2021        root_properties = []
2022        with_properties = []
2023
2024        for p in expression.expressions:
2025            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2026            if p_loc == exp.Properties.Location.POST_WITH:
2027                with_properties.append(p)
2028            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2029                root_properties.append(p)
2030
2031        root_props_ast = exp.Properties(expressions=root_properties)
2032        root_props_ast.parent = expression.parent
2033
2034        with_props_ast = exp.Properties(expressions=with_properties)
2035        with_props_ast.parent = expression.parent
2036
2037        root_props = self.root_properties(root_props_ast)
2038        with_props = self.with_properties(with_props_ast)
2039
2040        if root_props and with_props and not self.pretty:
2041            with_props = " " + with_props
2042
2043        return root_props + with_props
2044
2045    def root_properties(self, properties: exp.Properties) -> str:
2046        if properties.expressions:
2047            return self.expressions(properties, indent=False, sep=" ")
2048        return ""
2049
2050    def properties(
2051        self,
2052        properties: exp.Properties,
2053        prefix: str = "",
2054        sep: str = ", ",
2055        suffix: str = "",
2056        wrapped: bool = True,
2057    ) -> str:
2058        if properties.expressions:
2059            expressions = self.expressions(properties, sep=sep, indent=False)
2060            if expressions:
2061                expressions = self.wrap(expressions) if wrapped else expressions
2062                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2063        return ""
2064
2065    def with_properties(self, properties: exp.Properties) -> str:
2066        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
2067
2068    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2069        properties_locs = defaultdict(list)
2070        for p in properties.expressions:
2071            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2072            if p_loc != exp.Properties.Location.UNSUPPORTED:
2073                properties_locs[p_loc].append(p)
2074            else:
2075                self.unsupported(f"Unsupported property {p.key}")
2076
2077        return properties_locs
2078
2079    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2080        if isinstance(expression.this, exp.Dot):
2081            return self.sql(expression, "this")
2082        return f"'{expression.name}'" if string_key else expression.name
2083
2084    def property_sql(self, expression: exp.Property) -> str:
2085        property_cls = expression.__class__
2086        if property_cls == exp.Property:
2087            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2088
2089        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2090        if not property_name:
2091            self.unsupported(f"Unsupported property {expression.key}")
2092
2093        return f"{property_name}={self.sql(expression, 'this')}"
2094
2095    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2096        return f"UUID {self.sql(expression, 'this')}"
2097
2098    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2099        if self.SUPPORTS_CREATE_TABLE_LIKE:
2100            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2101            options = f" {options}" if options else ""
2102
2103            like = f"LIKE {self.sql(expression, 'this')}{options}"
2104            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2105                like = f"({like})"
2106
2107            return like
2108
2109        if expression.expressions:
2110            self.unsupported("Transpilation of LIKE property options is unsupported")
2111
2112        select = exp.select("*").from_(expression.this).limit(0)
2113        return f"AS {self.sql(select)}"
2114
2115    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2116        no = "NO " if expression.args.get("no") else ""
2117        protection = " PROTECTION" if expression.args.get("protection") else ""
2118        return f"{no}FALLBACK{protection}"
2119
2120    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2121        no = "NO " if expression.args.get("no") else ""
2122        local = expression.args.get("local")
2123        local = f"{local} " if local else ""
2124        dual = "DUAL " if expression.args.get("dual") else ""
2125        before = "BEFORE " if expression.args.get("before") else ""
2126        after = "AFTER " if expression.args.get("after") else ""
2127        return f"{no}{local}{dual}{before}{after}JOURNAL"
2128
2129    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2130        freespace = self.sql(expression, "this")
2131        percent = " PERCENT" if expression.args.get("percent") else ""
2132        return f"FREESPACE={freespace}{percent}"
2133
2134    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2135        if expression.args.get("default"):
2136            property = "DEFAULT"
2137        elif expression.args.get("on"):
2138            property = "ON"
2139        else:
2140            property = "OFF"
2141        return f"CHECKSUM={property}"
2142
2143    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2144        if expression.args.get("no"):
2145            return "NO MERGEBLOCKRATIO"
2146        if expression.args.get("default"):
2147            return "DEFAULT MERGEBLOCKRATIO"
2148
2149        percent = " PERCENT" if expression.args.get("percent") else ""
2150        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2151
2152    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2153        expressions = self.expressions(expression, flat=True)
2154        expressions = f"({expressions})" if expressions else ""
2155        return f"USING {self.sql(expression, 'this')}{expressions}"
2156
2157    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2158        default = expression.args.get("default")
2159        minimum = expression.args.get("minimum")
2160        maximum = expression.args.get("maximum")
2161        if default or minimum or maximum:
2162            if default:
2163                prop = "DEFAULT"
2164            elif minimum:
2165                prop = "MINIMUM"
2166            else:
2167                prop = "MAXIMUM"
2168            return f"{prop} DATABLOCKSIZE"
2169        units = expression.args.get("units")
2170        units = f" {units}" if units else ""
2171        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2172
2173    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2174        autotemp = expression.args.get("autotemp")
2175        always = expression.args.get("always")
2176        default = expression.args.get("default")
2177        manual = expression.args.get("manual")
2178        never = expression.args.get("never")
2179
2180        if autotemp is not None:
2181            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2182        elif always:
2183            prop = "ALWAYS"
2184        elif default:
2185            prop = "DEFAULT"
2186        elif manual:
2187            prop = "MANUAL"
2188        elif never:
2189            prop = "NEVER"
2190        return f"BLOCKCOMPRESSION={prop}"
2191
2192    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2193        no = expression.args.get("no")
2194        no = " NO" if no else ""
2195        concurrent = expression.args.get("concurrent")
2196        concurrent = " CONCURRENT" if concurrent else ""
2197        target = self.sql(expression, "target")
2198        target = f" {target}" if target else ""
2199        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2200
2201    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2202        if isinstance(expression.this, list):
2203            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2204        if expression.this:
2205            modulus = self.sql(expression, "this")
2206            remainder = self.sql(expression, "expression")
2207            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2208
2209        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2210        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2211        return f"FROM ({from_expressions}) TO ({to_expressions})"
2212
2213    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2214        this = self.sql(expression, "this")
2215
2216        for_values_or_default = expression.expression
2217        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2218            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2219        else:
2220            for_values_or_default = " DEFAULT"
2221
2222        return f"PARTITION OF {this}{for_values_or_default}"
2223
2224    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2225        kind = expression.args.get("kind")
2226        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2227        for_or_in = expression.args.get("for_or_in")
2228        for_or_in = f" {for_or_in}" if for_or_in else ""
2229        lock_type = expression.args.get("lock_type")
2230        override = " OVERRIDE" if expression.args.get("override") else ""
2231        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2232
2233    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2234        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2235        statistics = expression.args.get("statistics")
2236        statistics_sql = ""
2237        if statistics is not None:
2238            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2239        return f"{data_sql}{statistics_sql}"
2240
2241    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2242        this = self.sql(expression, "this")
2243        this = f"HISTORY_TABLE={this}" if this else ""
2244        data_consistency: str | None = self.sql(expression, "data_consistency")
2245        data_consistency = (
2246            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2247        )
2248        retention_period: str | None = self.sql(expression, "retention_period")
2249        retention_period = (
2250            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2251        )
2252
2253        if this:
2254            on_sql = self.func("ON", this, data_consistency, retention_period)
2255        else:
2256            on_sql = "ON" if expression.args.get("on") else "OFF"
2257
2258        sql = f"SYSTEM_VERSIONING={on_sql}"
2259
2260        return f"WITH({sql})" if expression.args.get("with_") else sql
2261
2262    def insert_sql(self, expression: exp.Insert) -> str:
2263        hint = self.sql(expression, "hint")
2264        overwrite = expression.args.get("overwrite")
2265
2266        if isinstance(expression.this, exp.Directory):
2267            this = " OVERWRITE" if overwrite else " INTO"
2268        else:
2269            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2270
2271        stored = self.sql(expression, "stored")
2272        stored = f" {stored}" if stored else ""
2273        alternative = expression.args.get("alternative")
2274        alternative = f" OR {alternative}" if alternative else ""
2275        ignore = " IGNORE" if expression.args.get("ignore") else ""
2276        is_function = expression.args.get("is_function")
2277        if is_function:
2278            this = f"{this} FUNCTION"
2279        this = f"{this} {self.sql(expression, 'this')}"
2280
2281        exists = " IF EXISTS" if expression.args.get("exists") else ""
2282        where = self.sql(expression, "where")
2283        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2284        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2285        on_conflict = self.sql(expression, "conflict")
2286        on_conflict = f" {on_conflict}" if on_conflict else ""
2287        by_name = " BY NAME" if expression.args.get("by_name") else ""
2288        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2289        returning = self.sql(expression, "returning")
2290
2291        if self.RETURNING_END:
2292            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2293        else:
2294            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2295
2296        partition_by = self.sql(expression, "partition")
2297        partition_by = f" {partition_by}" if partition_by else ""
2298        settings = self.sql(expression, "settings")
2299        settings = f" {settings}" if settings else ""
2300
2301        source = self.sql(expression, "source")
2302        source = f"TABLE {source}" if source else ""
2303
2304        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2305        return self.prepend_ctes(expression, sql)
2306
2307    def introducer_sql(self, expression: exp.Introducer) -> str:
2308        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2309
2310    def kill_sql(self, expression: exp.Kill) -> str:
2311        kind = self.sql(expression, "kind")
2312        kind = f" {kind}" if kind else ""
2313        this = self.sql(expression, "this")
2314        this = f" {this}" if this else ""
2315        return f"KILL{kind}{this}"
2316
2317    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2318        return expression.name
2319
2320    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2321        return expression.name
2322
2323    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2324        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2325
2326        constraint = self.sql(expression, "constraint")
2327        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2328
2329        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2330        if conflict_keys:
2331            conflict_keys = f"({conflict_keys})"
2332
2333        index_predicate = self.sql(expression, "index_predicate")
2334        conflict_keys = f"{conflict_keys}{index_predicate} "
2335
2336        action = self.sql(expression, "action")
2337
2338        expressions = self.expressions(expression, flat=True)
2339        if expressions:
2340            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2341            expressions = f" {set_keyword}{expressions}"
2342
2343        where = self.sql(expression, "where")
2344        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2345
2346    def returning_sql(self, expression: exp.Returning) -> str:
2347        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2348
2349    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2350        fields = self.sql(expression, "fields")
2351        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2352        escaped = self.sql(expression, "escaped")
2353        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2354        items = self.sql(expression, "collection_items")
2355        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2356        keys = self.sql(expression, "map_keys")
2357        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2358        lines = self.sql(expression, "lines")
2359        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2360        null = self.sql(expression, "null")
2361        null = f" NULL DEFINED AS {null}" if null else ""
2362        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2363
2364    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2365        return f"WITH ({self.expressions(expression, flat=True)})"
2366
2367    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2368        this = f"{self.sql(expression, 'this')} INDEX"
2369        target = self.sql(expression, "target")
2370        target = f" FOR {target}" if target else ""
2371        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2372
2373    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2374        this = self.sql(expression, "this")
2375        kind = self.sql(expression, "kind")
2376        expr = self.sql(expression, "expression")
2377        return f"{this} ({kind} => {expr})"
2378
2379    def table_parts(self, expression: exp.Table) -> str:
2380        return ".".join(
2381            self.sql(part)
2382            for part in (
2383                expression.args.get("catalog"),
2384                expression.args.get("db"),
2385                expression.args.get("this"),
2386            )
2387            if part is not None
2388        )
2389
2390    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2391        table = self.table_parts(expression)
2392        only = "ONLY " if expression.args.get("only") else ""
2393        partition = self.sql(expression, "partition")
2394        partition = f" {partition}" if partition else ""
2395        version = self.sql(expression, "version")
2396        version = f" {version}" if version else ""
2397        alias = self.sql(expression, "alias")
2398        alias = f"{sep}{alias}" if alias else ""
2399
2400        sample = self.sql(expression, "sample")
2401        post_alias = ""
2402        pre_alias = ""
2403
2404        if self.dialect.ALIAS_POST_TABLESAMPLE:
2405            pre_alias = sample
2406        else:
2407            post_alias = sample
2408
2409        if self.dialect.ALIAS_POST_VERSION:
2410            pre_alias = f"{pre_alias}{version}"
2411        else:
2412            post_alias = f"{post_alias}{version}"
2413
2414        hints = self.expressions(expression, key="hints", sep=" ")
2415        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2416        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2417        joins = self.indent(
2418            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2419        )
2420        laterals = self.expressions(expression, key="laterals", sep="")
2421
2422        file_format = self.sql(expression, "format")
2423        pattern = self.sql(expression, "pattern")
2424        if file_format:
2425            pattern = f", PATTERN => {pattern}" if pattern else ""
2426            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2427        elif pattern:
2428            file_format = f" (PATTERN => {pattern})"
2429
2430        ordinality = expression.args.get("ordinality") or ""
2431        if ordinality:
2432            ordinality = f" WITH ORDINALITY{alias}"
2433            alias = ""
2434
2435        when = self.sql(expression, "when")
2436        if when:
2437            table = f"{table} {when}"
2438
2439        changes = self.sql(expression, "changes")
2440        changes = f" {changes}" if changes else ""
2441
2442        rows_from = self.expressions(expression, key="rows_from")
2443        if rows_from:
2444            table = f"ROWS FROM {self.wrap(rows_from)}"
2445
2446        indexed = expression.args.get("indexed")
2447        if indexed is not None:
2448            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2449        else:
2450            indexed = ""
2451
2452        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2453
2454    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2455        table = self.func("TABLE", expression.this)
2456        alias = self.sql(expression, "alias")
2457        alias = f" AS {alias}" if alias else ""
2458        sample = self.sql(expression, "sample")
2459        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2460        joins = self.indent(
2461            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2462        )
2463        return f"{table}{alias}{pivots}{sample}{joins}"
2464
2465    def tablesample_sql(
2466        self,
2467        expression: exp.TableSample,
2468        tablesample_keyword: str | None = None,
2469    ) -> str:
2470        method = self.sql(expression, "method")
2471        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2472        numerator = self.sql(expression, "bucket_numerator")
2473        denominator = self.sql(expression, "bucket_denominator")
2474        field = self.sql(expression, "bucket_field")
2475        field = f" ON {field}" if field else ""
2476        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2477        seed = self.sql(expression, "seed")
2478        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2479
2480        size = self.sql(expression, "size")
2481        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2482            size = f"{size} ROWS"
2483
2484        percent = self.sql(expression, "percent")
2485        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2486            percent = f"{percent} PERCENT"
2487
2488        expr = f"{bucket}{percent}{size}"
2489        if self.TABLESAMPLE_REQUIRES_PARENS:
2490            expr = f"({expr})"
2491
2492        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2493
2494    def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None:
2495        # Returns the rewritten field.expressions list with PivotAlias wrappers injected where
2496        # the stored column name differs from the target dialect's natural output.
2497        columns = expression.args.get("columns")
2498        if not columns or len(expression.fields) != 1:
2499            return None
2500
2501        args = expression.args
2502        parser_cls = self.dialect.parser_class
2503
2504        tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS
2505        tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS
2506        tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING
2507
2508        src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings)
2509        src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns)
2510        src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming)
2511
2512        if (
2513            src_identify_pivot_strings == tgt_identify_pivot_strings
2514            and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns
2515            and src_pivot_column_naming == tgt_pivot_column_naming
2516        ):
2517            return None
2518
2519        in_exprs = expression.fields[0].expressions
2520        step = len(columns) // len(in_exprs)
2521
2522        # Derive the per-value suffix from the first stored column vs the first IN-list value.
2523        # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases.
2524        first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name
2525        first_stored = columns[0].name
2526
2527        # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed)
2528        if not first_stored.startswith(first_base):
2529            return None
2530
2531        suffix = first_stored[len(first_base) :]
2532
2533        # Whether the target dialect would append an agg-name suffix for this pivot.
2534        # Spark single-agg uniquely drops the agg alias entirely.
2535        target_has_suffix = (
2536            len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple"
2537        ) and any(a.alias for a in expression.expressions)
2538        source_has_suffix = suffix != ""
2539
2540        new_exprs: list[exp.Expression] = []
2541        modified = False
2542        for val_idx, e in enumerate(in_exprs):
2543            if isinstance(e, exp.PivotAlias):
2544                new_exprs.append(e)
2545                continue
2546
2547            i = val_idx * step
2548            stored_full = columns[i].name
2549            stored_value = stored_full[: -len(suffix)] if suffix else stored_full
2550            target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name
2551
2552            # Source had a suffix, but target won't apply one
2553            if source_has_suffix and not target_has_suffix:
2554                new_exprs.append(
2555                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True))
2556                )
2557                modified = True
2558            # Value-part mismatch (e.g. Snowflake's literal-style values vs others).
2559            elif stored_value != target_value:
2560                new_exprs.append(
2561                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True))
2562                )
2563                modified = True
2564            else:
2565                new_exprs.append(e)
2566
2567        return new_exprs if modified else None
2568
2569    def pivot_sql(self, expression: exp.Pivot) -> str:
2570        expressions = self.expressions(expression, flat=True)
2571        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2572
2573        group = self.sql(expression, "group")
2574
2575        if expression.this:
2576            this = self.sql(expression, "this")
2577            if not expressions:
2578                sql = f"UNPIVOT {this}"
2579            else:
2580                on = f"{self.seg('ON')} {expressions}"
2581                into = self.sql(expression, "into")
2582                into = f"{self.seg('INTO')} {into}" if into else ""
2583                using = self.expressions(expression, key="using", flat=True)
2584                using = f"{self.seg('USING')} {using}" if using else ""
2585                sql = f"{direction} {this}{on}{into}{using}{group}"
2586            return self.prepend_ctes(expression, sql)
2587
2588        if not expression.unpivot:
2589            # Wrap IN-list values with explicit aliases where the target dialect would differ
2590            new_field_exprs = self._pivot_in_value_aliases(expression)
2591            if new_field_exprs is not None:
2592                expression.fields[0].set("expressions", new_field_exprs)
2593
2594        alias = self.sql(expression, "alias")
2595        alias = f" AS {alias}" if alias else ""
2596
2597        fields = self.expressions(
2598            expression,
2599            "fields",
2600            sep=" ",
2601            dynamic=True,
2602            new_line=True,
2603            skip_first=True,
2604            skip_last=True,
2605        )
2606
2607        include_nulls = expression.args.get("include_nulls")
2608        if include_nulls is not None:
2609            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2610        else:
2611            nulls = ""
2612
2613        default_on_null = self.sql(expression, "default_on_null")
2614        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2615        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2616        return self.prepend_ctes(expression, sql)
2617
2618    def version_sql(self, expression: exp.Version) -> str:
2619        this = f"FOR {expression.name}"
2620        kind = expression.text("kind")
2621        expr = self.sql(expression, "expression")
2622        return f"{this} {kind} {expr}"
2623
2624    def tuple_sql(self, expression: exp.Tuple) -> str:
2625        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2626
2627    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2628        """
2629        Returns (join_sql, from_sql) for UPDATE statements.
2630        - join_sql: placed after UPDATE table, before SET
2631        - from_sql: placed after SET clause (standard position)
2632        Dialects like MySQL need to convert FROM to JOIN syntax.
2633        """
2634        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2635            return ("", self.sql(expression, "from_"))
2636
2637        # Qualify unqualified columns in SET clause with the target table
2638        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2639        target_table = expression.this
2640        if isinstance(target_table, exp.Table):
2641            target_name = exp.to_identifier(target_table.alias_or_name)
2642            for eq in expression.expressions:
2643                col = eq.this
2644                if isinstance(col, exp.Column) and not col.table:
2645                    col.set("table", target_name)
2646
2647        table = from_expr.this
2648        if nested_joins := table.args.get("joins", []):
2649            table.set("joins", None)
2650
2651        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2652        for nested in nested_joins:
2653            if not nested.args.get("on") and not nested.args.get("using"):
2654                nested.set("on", exp.true())
2655            join_sql += self.sql(nested)
2656
2657        return (join_sql, "")
2658
2659    def update_sql(self, expression: exp.Update) -> str:
2660        hint = self.sql(expression, "hint")
2661        this = self.sql(expression, "this")
2662        join_sql, from_sql = self._update_from_joins_sql(expression)
2663        set_sql = self.expressions(expression, flat=True)
2664        where_sql = self.sql(expression, "where")
2665        returning = self.sql(expression, "returning")
2666        order = self.sql(expression, "order")
2667        limit = self.sql(expression, "limit")
2668        if self.RETURNING_END:
2669            expression_sql = f"{from_sql}{where_sql}{returning}"
2670        else:
2671            expression_sql = f"{returning}{from_sql}{where_sql}"
2672        options = self.expressions(expression, key="options")
2673        options = f" OPTION({options})" if options else ""
2674        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2675        return self.prepend_ctes(expression, sql)
2676
2677    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2678        values_as_table = values_as_table and self.VALUES_AS_TABLE
2679
2680        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2681        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2682            args = self.expressions(expression)
2683            alias = self.sql(expression, "alias")
2684            values = f"VALUES{self.seg('')}{args}"
2685            values = (
2686                f"({values})"
2687                if self.WRAP_DERIVED_VALUES
2688                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2689                else values
2690            )
2691            values = self.query_modifiers(expression, values)
2692            return f"{values} AS {alias}" if alias else values
2693
2694        # Converts `VALUES...` expression into a series of select unions.
2695        alias_node = expression.args.get("alias")
2696        column_names = alias_node and alias_node.columns
2697
2698        selects: list[exp.Query] = []
2699
2700        for i, tup in enumerate(expression.expressions):
2701            row = tup.expressions
2702
2703            if i == 0 and column_names:
2704                row = [
2705                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2706                ]
2707
2708            selects.append(exp.Select(expressions=row))
2709
2710        if self.pretty:
2711            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2712            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2713            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2714            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2715            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2716
2717        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2718        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2719        return f"({unions}){alias}"
2720
2721    def var_sql(self, expression: exp.Var) -> str:
2722        return self.sql(expression, "this")
2723
2724    @unsupported_args("expressions")
2725    def into_sql(self, expression: exp.Into) -> str:
2726        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2727        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2728        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2729
2730    def from_sql(self, expression: exp.From) -> str:
2731        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2732
2733    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2734        grouping_sets = self.expressions(expression, indent=False)
2735        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2736
2737    def rollup_sql(self, expression: exp.Rollup) -> str:
2738        expressions = self.expressions(expression, indent=False)
2739        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2740
2741    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2742        this = self.sql(expression, "this")
2743
2744        columns = self.expressions(expression, flat=True)
2745
2746        from_sql = self.sql(expression, "from_index")
2747        from_sql = f" FROM {from_sql}" if from_sql else ""
2748
2749        properties = expression.args.get("properties")
2750        properties_sql = (
2751            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2752        )
2753
2754        return f"{this}({columns}){from_sql}{properties_sql}"
2755
2756    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2757        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2758
2759    def cube_sql(self, expression: exp.Cube) -> str:
2760        expressions = self.expressions(expression, indent=False)
2761        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2762
2763    def group_sql(self, expression: exp.Group) -> str:
2764        group_by_all = expression.args.get("all")
2765        if group_by_all is True:
2766            modifier = " ALL"
2767        elif group_by_all is False:
2768            modifier = " DISTINCT"
2769        else:
2770            modifier = ""
2771
2772        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2773
2774        grouping_sets = self.expressions(expression, key="grouping_sets")
2775        cube = self.expressions(expression, key="cube")
2776        rollup = self.expressions(expression, key="rollup")
2777
2778        groupings = csv(
2779            self.seg(grouping_sets) if grouping_sets else "",
2780            self.seg(cube) if cube else "",
2781            self.seg(rollup) if rollup else "",
2782            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2783            sep=self.GROUPINGS_SEP,
2784        )
2785
2786        if (
2787            expression.expressions
2788            and groupings
2789            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2790        ):
2791            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2792
2793        return f"{group_by}{groupings}"
2794
2795    def having_sql(self, expression: exp.Having) -> str:
2796        this = self.indent(self.sql(expression, "this"))
2797        return f"{self.seg('HAVING')}{self.sep()}{this}"
2798
2799    def connect_sql(self, expression: exp.Connect) -> str:
2800        start = self.sql(expression, "start")
2801        start = self.seg(f"START WITH {start}") if start else ""
2802        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2803        connect = self.sql(expression, "connect")
2804        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2805        return start + connect
2806
2807    def prior_sql(self, expression: exp.Prior) -> str:
2808        return f"PRIOR {self.sql(expression, 'this')}"
2809
2810    def join_sql(self, expression: exp.Join) -> str:
2811        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2812            side = None
2813        else:
2814            side = expression.side
2815
2816        op_sql = " ".join(
2817            op
2818            for op in (
2819                expression.method,
2820                "GLOBAL" if expression.args.get("global_") else None,
2821                side,
2822                expression.kind,
2823                expression.hint if self.JOIN_HINTS else None,
2824                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2825            )
2826            if op
2827        )
2828        match_cond = self.sql(expression, "match_condition")
2829        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2830        on_sql = self.sql(expression, "on")
2831        using = expression.args.get("using")
2832
2833        if not on_sql and using:
2834            on_sql = csv(*(self.sql(column) for column in using))
2835
2836        this = expression.this
2837        this_sql = self.sql(this)
2838
2839        exprs = self.expressions(expression)
2840        if exprs:
2841            this_sql = f"{this_sql},{self.seg(exprs)}"
2842
2843        if on_sql:
2844            on_sql = self.indent(on_sql, skip_first=True)
2845            space = self.seg(" " * self.pad) if self.pretty else " "
2846            if using:
2847                on_sql = f"{space}USING ({on_sql})"
2848            else:
2849                on_sql = f"{space}ON {on_sql}"
2850        elif not op_sql:
2851            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2852                return f" {this_sql}"
2853
2854            return f", {this_sql}"
2855
2856        if op_sql != "STRAIGHT_JOIN":
2857            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2858
2859        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2860        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2861
2862    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2863        args = self.expressions(expression, flat=True)
2864        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2865        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2866
2867    def lateral_op(self, expression: exp.Lateral) -> str:
2868        cross_apply = expression.args.get("cross_apply")
2869
2870        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2871        if cross_apply is True:
2872            op = "INNER JOIN "
2873        elif cross_apply is False:
2874            op = "LEFT JOIN "
2875        else:
2876            op = ""
2877
2878        return f"{op}LATERAL"
2879
2880    def lateral_sql(self, expression: exp.Lateral) -> str:
2881        this = self.sql(expression, "this")
2882
2883        if expression.args.get("view"):
2884            alias = expression.args["alias"]
2885            columns = self.expressions(alias, key="columns", flat=True)
2886            table = f" {alias.name}" if alias.name else ""
2887            columns = f" AS {columns}" if columns else ""
2888            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2889            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2890
2891        alias = self.sql(expression, "alias")
2892        alias = f" AS {alias}" if alias else ""
2893
2894        ordinality = expression.args.get("ordinality") or ""
2895        if ordinality:
2896            ordinality = f" WITH ORDINALITY{alias}"
2897            alias = ""
2898
2899        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2900
2901    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2902        this = self.sql(expression, "this")
2903
2904        args = [
2905            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2906            for e in (expression.args.get(k) for k in ("offset", "expression"))
2907            if e
2908        ]
2909
2910        args_sql = ", ".join(self.sql(e) for e in args)
2911        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2912        expressions = self.expressions(expression, flat=True)
2913        limit_options = self.sql(expression, "limit_options")
2914        expressions = f" BY {expressions}" if expressions else ""
2915
2916        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2917
2918    def offset_sql(self, expression: exp.Offset) -> str:
2919        this = self.sql(expression, "this")
2920        value = expression.expression
2921        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2922        expressions = self.expressions(expression, flat=True)
2923        expressions = f" BY {expressions}" if expressions else ""
2924        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2925
2926    def setitem_sql(self, expression: exp.SetItem) -> str:
2927        kind = self.sql(expression, "kind")
2928        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2929            kind = ""
2930        else:
2931            kind = f"{kind} " if kind else ""
2932        this = self.sql(expression, "this")
2933        expressions = self.expressions(expression)
2934        collate = self.sql(expression, "collate")
2935        collate = f" COLLATE {collate}" if collate else ""
2936        global_ = "GLOBAL " if expression.args.get("global_") else ""
2937        return f"{global_}{kind}{this}{expressions}{collate}"
2938
2939    def set_sql(self, expression: exp.Set) -> str:
2940        expressions = f" {self.expressions(expression, flat=True)}"
2941        tag = " TAG" if expression.args.get("tag") else ""
2942        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2943
2944    def queryband_sql(self, expression: exp.QueryBand) -> str:
2945        this = self.sql(expression, "this")
2946        update = " UPDATE" if expression.args.get("update") else ""
2947        scope = self.sql(expression, "scope")
2948        scope = f" FOR {scope}" if scope else ""
2949
2950        return f"QUERY_BAND = {this}{update}{scope}"
2951
2952    def pragma_sql(self, expression: exp.Pragma) -> str:
2953        return f"PRAGMA {self.sql(expression, 'this')}"
2954
2955    def lock_sql(self, expression: exp.Lock) -> str:
2956        if not self.LOCKING_READS_SUPPORTED:
2957            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2958            return ""
2959
2960        update = expression.args["update"]
2961        key = expression.args.get("key")
2962        if update:
2963            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2964        else:
2965            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2966        expressions = self.expressions(expression, flat=True)
2967        expressions = f" OF {expressions}" if expressions else ""
2968        wait = expression.args.get("wait")
2969
2970        if wait is not None:
2971            if isinstance(wait, exp.Literal):
2972                wait = f" WAIT {self.sql(wait)}"
2973            else:
2974                wait = " NOWAIT" if wait else " SKIP LOCKED"
2975
2976        return f"{lock_type}{expressions}{wait or ''}"
2977
2978    def literal_sql(self, expression: exp.Literal) -> str:
2979        text = expression.this or ""
2980        if expression.is_string:
2981            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2982        return text
2983
2984    def escape_str(
2985        self,
2986        text: str,
2987        escape_backslash: bool = True,
2988        delimiter: str | None = None,
2989        escaped_delimiter: str | None = None,
2990        is_byte_string: bool = False,
2991    ) -> str:
2992        if is_byte_string:
2993            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
2994        else:
2995            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
2996
2997        if supports_escape_sequences:
2998            text = "".join(
2999                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
3000                for ch in text
3001            )
3002
3003        delimiter = delimiter or self.dialect.QUOTE_END
3004        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
3005
3006        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3007
3008    def loaddata_sql(self, expression: exp.LoadData) -> str:
3009        is_overwrite = expression.args.get("overwrite")
3010        overwrite = " OVERWRITE" if is_overwrite else ""
3011        this = self.sql(expression, "this")
3012
3013        files = expression.args.get("files")
3014        if files:
3015            files_sql = self.expressions(files, flat=True)
3016            files_sql = f"FILES{self.wrap(files_sql)}"
3017            if is_overwrite:
3018                this = f" {this}"
3019            elif expression.args.get("temp"):
3020                this = f" INTO TEMP TABLE {this}"
3021            else:
3022                this = f" INTO TABLE {this}"
3023            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
3024
3025        local = " LOCAL" if expression.args.get("local") else ""
3026        inpath = f" INPATH {self.sql(expression, 'inpath')}"
3027        this = f" INTO TABLE {this}"
3028        partition = self.sql(expression, "partition")
3029        partition = f" {partition}" if partition else ""
3030        input_format = self.sql(expression, "input_format")
3031        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3032        serde = self.sql(expression, "serde")
3033        serde = f" SERDE {serde}" if serde else ""
3034        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3035
3036    def null_sql(self, *_) -> str:
3037        return "NULL"
3038
3039    def boolean_sql(self, expression: exp.Boolean) -> str:
3040        return "TRUE" if expression.this else "FALSE"
3041
3042    def booland_sql(self, expression: exp.Booland) -> str:
3043        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
3044
3045    def boolor_sql(self, expression: exp.Boolor) -> str:
3046        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
3047
3048    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3049        this = self.sql(expression, "this")
3050        this = f"{this} " if this else this
3051        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3052        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3053
3054    def withfill_sql(self, expression: exp.WithFill) -> str:
3055        from_sql = self.sql(expression, "from_")
3056        from_sql = f" FROM {from_sql}" if from_sql else ""
3057        to_sql = self.sql(expression, "to")
3058        to_sql = f" TO {to_sql}" if to_sql else ""
3059        step_sql = self.sql(expression, "step")
3060        step_sql = f" STEP {step_sql}" if step_sql else ""
3061        interpolated_values = [
3062            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3063            if isinstance(e, exp.Alias)
3064            else self.sql(e, "this")
3065            for e in expression.args.get("interpolate") or []
3066        ]
3067        interpolate = (
3068            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3069        )
3070        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3071
3072    def cluster_sql(self, expression: exp.Cluster) -> str:
3073        return self.op_expressions("CLUSTER BY", expression)
3074
3075    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3076        if expression.this:
3077            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3078            return ""
3079        expressions = self.expressions(expression, flat=True)
3080        return f"CLUSTER BY ({expressions})"
3081
3082    def distribute_sql(self, expression: exp.Distribute) -> str:
3083        return self.op_expressions("DISTRIBUTE BY", expression)
3084
3085    def sort_sql(self, expression: exp.Sort) -> str:
3086        return self.op_expressions("SORT BY", expression)
3087
3088    def _resolve_ordered_for_null_ordering_simulation(
3089        self, expression: exp.Ordered
3090    ) -> exp.Expr | None:
3091        """Resolve a bare ORDER BY name against the enclosing SELECT projection.
3092
3093        Returns the underlying expression of the uniquely-matching projection
3094        (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE
3095        simulation, since the CASE is evaluated in FROM-clause scope rather
3096        than alias scope (MySQL error 1052). Returns None if no safe
3097        substitution applies, leaving the original behaviour unchanged.
3098        """
3099        this = expression.this
3100        if not (isinstance(this, exp.Column) and not this.table):
3101            return None
3102
3103        ancestor = expression.find_ancestor(exp.Select, exp.Window)
3104        if not isinstance(ancestor, exp.Select):
3105            return None
3106
3107        column_name = this.name
3108        matched: list[exp.Expr] = [
3109            p.this if isinstance(p, exp.Alias) else p
3110            for p in ancestor.selects
3111            if p.output_name == column_name
3112        ]
3113        match = matched[0] if len(matched) == 1 else None
3114
3115        # Skip the substitution when it would be identical to the existing
3116        # reference (e.g. ``SELECT col FROM t ORDER BY col``).
3117        if isinstance(match, exp.Column) and not match.table and match.name == column_name:
3118            return None
3119
3120        return match
3121
3122    def ordered_sql(self, expression: exp.Ordered) -> str:
3123        desc = expression.args.get("desc")
3124        asc = not desc
3125
3126        nulls_first = expression.args.get("nulls_first")
3127        nulls_last = not nulls_first
3128        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3129        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3130        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3131
3132        this = self.sql(expression, "this")
3133
3134        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3135        nulls_sort_change = ""
3136        if nulls_first and (
3137            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3138        ):
3139            nulls_sort_change = " NULLS FIRST"
3140        elif (
3141            nulls_last
3142            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3143            and not nulls_are_last
3144        ):
3145            nulls_sort_change = " NULLS LAST"
3146
3147        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3148        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3149            window = expression.find_ancestor(exp.Window, exp.Select)
3150
3151            if isinstance(window, exp.Window):
3152                window_this = window.this
3153                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3154                    window_this = window_this.this
3155                spec = window.args.get("spec")
3156            else:
3157                window_this = None
3158                spec = None
3159
3160            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3161            # without a spec or with a ROWS spec, but not with RANGE
3162            if not (
3163                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3164                and (not spec or spec.text("kind").upper() == "ROWS")
3165            ):
3166                if window_this and spec:
3167                    self.unsupported(
3168                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3169                    )
3170                    nulls_sort_change = ""
3171                elif self.NULL_ORDERING_SUPPORTED is False and (
3172                    (asc and nulls_sort_change == " NULLS LAST")
3173                    or (desc and nulls_sort_change == " NULLS FIRST")
3174                ):
3175                    # BigQuery does not allow these ordering/nulls combinations when used under
3176                    # an aggregation func or under a window containing one
3177                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3178
3179                    if isinstance(ancestor, exp.Window):
3180                        ancestor = ancestor.this
3181                    if isinstance(ancestor, exp.AggFunc):
3182                        self.unsupported(
3183                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3184                        )
3185                        nulls_sort_change = ""
3186                elif self.NULL_ORDERING_SUPPORTED is None:
3187                    if expression.this.is_int:
3188                        self.unsupported(
3189                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3190                        )
3191                    elif not isinstance(expression.this, exp.Rand):
3192                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3193                        target = self.sql(resolved) if resolved is not None else this
3194                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3195                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3196                    nulls_sort_change = ""
3197
3198        with_fill = self.sql(expression, "with_fill")
3199        with_fill = f" {with_fill}" if with_fill else ""
3200
3201        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
3202
3203    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3204        window_frame = self.sql(expression, "window_frame")
3205        window_frame = f"{window_frame} " if window_frame else ""
3206
3207        this = self.sql(expression, "this")
3208
3209        return f"{window_frame}{this}"
3210
3211    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3212        partition = self.partition_by_sql(expression)
3213        order = self.sql(expression, "order")
3214        measures = self.expressions(expression, key="measures")
3215        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3216        rows = self.sql(expression, "rows")
3217        rows = self.seg(rows) if rows else ""
3218        after = self.sql(expression, "after")
3219        after = self.seg(after) if after else ""
3220        pattern = self.sql(expression, "pattern")
3221        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3222        definition_sqls = [
3223            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3224            for definition in expression.args.get("define", [])
3225        ]
3226        definitions = self.expressions(sqls=definition_sqls)
3227        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3228        body = "".join(
3229            (
3230                partition,
3231                order,
3232                measures,
3233                rows,
3234                after,
3235                pattern,
3236                define,
3237            )
3238        )
3239        alias = self.sql(expression, "alias")
3240        alias = f" {alias}" if alias else ""
3241        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3242
3243    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3244        limit = expression.args.get("limit")
3245
3246        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3247            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
3248        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
3249            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3250
3251        return csv(
3252            *sqls,
3253            *[self.sql(join) for join in expression.args.get("joins") or []],
3254            self.sql(expression, "match"),
3255            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3256            self.sql(expression, "prewhere"),
3257            self.sql(expression, "where"),
3258            self.sql(expression, "connect"),
3259            self.sql(expression, "group"),
3260            self.sql(expression, "having"),
3261            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3262            self.sql(expression, "order"),
3263            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3264            *self.after_limit_modifiers(expression),
3265            self.options_modifier(expression),
3266            self.sql(expression, "for_"),
3267            sep="",
3268        )
3269
3270    def options_modifier(self, expression: exp.Expr) -> str:
3271        options = self.expressions(expression, key="options")
3272        return f" {options}" if options else ""
3273
3274    def forclause_sql(self, expression: exp.ForClause) -> str:
3275        kind = expression.args["kind"]
3276        if kind == "BROWSE":
3277            return f"{self.sep()}FOR BROWSE"
3278        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3279        # the target dialect doesn't support QueryOption, so we drop the clause.
3280        options = self.expressions(expression, key="expressions")
3281        if not options:
3282            return ""
3283        return f"{self.sep()}FOR {kind}{self.seg(options)}"
3284
3285    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3286        self.unsupported("Unsupported query option.")
3287        return ""
3288
3289    def offset_limit_modifiers(
3290        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3291    ) -> list[str]:
3292        return [
3293            self.sql(expression, "offset") if fetch else self.sql(limit),
3294            self.sql(limit) if fetch else self.sql(expression, "offset"),
3295        ]
3296
3297    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3298        locks = self.expressions(expression, key="locks", sep=" ")
3299        locks = f" {locks}" if locks else ""
3300        return [locks, self.sql(expression, "sample")]
3301
3302    def select_sql(self, expression: exp.Select) -> str:
3303        into = expression.args.get("into")
3304        if not self.SUPPORTS_SELECT_INTO and into:
3305            into.pop()
3306
3307        hint = self.sql(expression, "hint")
3308        distinct = self.sql(expression, "distinct")
3309        distinct = f" {distinct}" if distinct else ""
3310        kind = self.sql(expression, "kind")
3311
3312        limit = expression.args.get("limit")
3313        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3314            top = self.limit_sql(limit, top=True)
3315            limit.pop()
3316        else:
3317            top = ""
3318
3319        expressions = self.expressions(expression)
3320
3321        if kind:
3322            if kind in self.SELECT_KINDS:
3323                kind = f" AS {kind}"
3324            else:
3325                if kind == "STRUCT":
3326                    expressions = self.expressions(
3327                        sqls=[
3328                            self.sql(
3329                                exp.Struct(
3330                                    expressions=[
3331                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3332                                        if isinstance(e, exp.Alias)
3333                                        else e
3334                                        for e in expression.expressions
3335                                    ]
3336                                )
3337                            )
3338                        ]
3339                    )
3340                kind = ""
3341
3342        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3343        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3344
3345        exclude = expression.args.get("exclude")
3346
3347        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3348            exclude_sql = self.expressions(sqls=exclude, flat=True)
3349            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3350
3351        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3352        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3353        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3354        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3355        sql = self.query_modifiers(
3356            expression,
3357            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3358            self.sql(expression, "into", comment=False),
3359            self.sql(expression, "from_", comment=False),
3360        )
3361
3362        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3363        if expression.args.get("with_"):
3364            sql = self.maybe_comment(sql, expression)
3365            expression.pop_comments()
3366
3367        sql = self.prepend_ctes(expression, sql)
3368
3369        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3370            expression.set("exclude", None)
3371            subquery = expression.subquery(copy=False)
3372            star = exp.Star(except_=exclude)
3373            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3374
3375        if not self.SUPPORTS_SELECT_INTO and into:
3376            if into.args.get("temporary"):
3377                table_kind = " TEMPORARY"
3378            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3379                table_kind = " UNLOGGED"
3380            else:
3381                table_kind = ""
3382            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3383
3384        return sql
3385
3386    def schema_sql(self, expression: exp.Schema) -> str:
3387        this = self.sql(expression, "this")
3388        sql = self.schema_columns_sql(expression)
3389        return f"{this} {sql}" if this and sql else this or sql
3390
3391    def schema_columns_sql(self, expression: exp.Expr) -> str:
3392        if expression.expressions:
3393            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3394        return ""
3395
3396    def star_sql(self, expression: exp.Star) -> str:
3397        except_ = self.expressions(expression, key="except_", flat=True)
3398        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3399        replace = self.expressions(expression, key="replace", flat=True)
3400        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3401        rename = self.expressions(expression, key="rename", flat=True)
3402        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3403        ilike = self.sql(expression, "ilike")
3404        ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else ""
3405        return f"*{ilike}{except_}{replace}{rename}"
3406
3407    def parameter_sql(self, expression: exp.Parameter) -> str:
3408        this = self.sql(expression, "this")
3409        return f"{self.PARAMETER_TOKEN}{this}"
3410
3411    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3412        this = self.sql(expression, "this")
3413        kind = expression.text("kind")
3414        if kind:
3415            kind = f"{kind}."
3416        return f"@@{kind}{this}"
3417
3418    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3419        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3420
3421    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3422        alias = self.sql(expression, "alias")
3423        alias = f"{sep}{alias}" if alias else ""
3424        sample = self.sql(expression, "sample")
3425        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3426            alias = f"{sample}{alias}"
3427
3428            # Set to None so it's not generated again by self.query_modifiers()
3429            expression.set("sample", None)
3430
3431        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3432        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3433        return self.prepend_ctes(expression, sql)
3434
3435    def qualify_sql(self, expression: exp.Qualify) -> str:
3436        this = self.indent(self.sql(expression, "this"))
3437        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3438
3439    def unnest_sql(self, expression: exp.Unnest) -> str:
3440        args = self.expressions(expression, flat=True)
3441
3442        alias = expression.args.get("alias")
3443        offset = expression.args.get("offset")
3444
3445        if self.UNNEST_WITH_ORDINALITY:
3446            if alias and isinstance(offset, exp.Expr):
3447                alias.append("columns", offset)
3448                expression.set("offset", None)
3449
3450        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3451            columns = alias.columns
3452            alias = self.sql(columns[0]) if columns else ""
3453        else:
3454            alias = self.sql(alias)
3455
3456        alias = f" AS {alias}" if alias else alias
3457        if self.UNNEST_WITH_ORDINALITY:
3458            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3459        else:
3460            if isinstance(offset, exp.Expr):
3461                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3462            elif offset:
3463                suffix = f"{alias} WITH OFFSET"
3464            else:
3465                suffix = alias
3466
3467        return f"UNNEST({args}){suffix}"
3468
3469    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3470        return ""
3471
3472    def where_sql(self, expression: exp.Where) -> str:
3473        this = self.indent(self.sql(expression, "this"))
3474        return f"{self.seg('WHERE')}{self.sep()}{this}"
3475
3476    def window_sql(self, expression: exp.Window) -> str:
3477        this = self.sql(expression, "this")
3478        partition = self.partition_by_sql(expression)
3479        order = expression.args.get("order")
3480        order = self.order_sql(order, flat=True) if order else ""
3481        spec = self.sql(expression, "spec")
3482        alias = self.sql(expression, "alias")
3483        over = self.sql(expression, "over") or "OVER"
3484
3485        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3486
3487        first = expression.args.get("first")
3488        if first is None:
3489            first = ""
3490        else:
3491            first = "FIRST" if first else "LAST"
3492
3493        if not partition and not order and not spec and alias:
3494            return f"{this} {alias}"
3495
3496        args = self.format_args(
3497            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3498        )
3499        return f"{this} ({args})"
3500
3501    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3502        partition = self.expressions(expression, key="partition_by", flat=True)
3503        return f"PARTITION BY {partition}" if partition else ""
3504
3505    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3506        kind = self.sql(expression, "kind")
3507        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3508        end = (
3509            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3510            or "CURRENT ROW"
3511        )
3512
3513        window_spec = f"{kind} BETWEEN {start} AND {end}"
3514
3515        exclude = self.sql(expression, "exclude")
3516        if exclude:
3517            if self.SUPPORTS_WINDOW_EXCLUDE:
3518                window_spec += f" EXCLUDE {exclude}"
3519            else:
3520                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3521
3522        return window_spec
3523
3524    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3525        this = self.sql(expression, "this")
3526        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3527        return f"{this} WITHIN GROUP ({expression_sql})"
3528
3529    def between_sql(self, expression: exp.Between) -> str:
3530        this = self.sql(expression, "this")
3531        low = self.sql(expression, "low")
3532        high = self.sql(expression, "high")
3533        symmetric = expression.args.get("symmetric")
3534
3535        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3536            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3537
3538        flag = (
3539            " SYMMETRIC"
3540            if symmetric
3541            else " ASYMMETRIC"
3542            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3543            else ""  # silently drop ASYMMETRIC – semantics identical
3544        )
3545        return f"{this} BETWEEN{flag} {low} AND {high}"
3546
3547    def bracket_offset_expressions(
3548        self, expression: exp.Bracket, index_offset: int | None = None
3549    ) -> list[exp.Expr]:
3550        if expression.args.get("json_access"):
3551            return expression.expressions
3552
3553        return apply_index_offset(
3554            expression.this,
3555            expression.expressions,
3556            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3557            dialect=self.dialect,
3558        )
3559
3560    def bracket_sql(self, expression: exp.Bracket) -> str:
3561        expressions = self.bracket_offset_expressions(expression)
3562        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3563        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3564
3565    def all_sql(self, expression: exp.All) -> str:
3566        this = self.sql(expression, "this")
3567        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3568            this = self.wrap(this)
3569        return f"ALL {this}"
3570
3571    def any_sql(self, expression: exp.Any) -> str:
3572        this = self.sql(expression, "this")
3573        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3574            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3575                this = self.wrap(this)
3576            return f"ANY{this}"
3577        return f"ANY {this}"
3578
3579    def exists_sql(self, expression: exp.Exists) -> str:
3580        return f"EXISTS{self.wrap(expression)}"
3581
3582    def case_sql(self, expression: exp.Case) -> str:
3583        this = self.sql(expression, "this")
3584        statements = [f"CASE {this}" if this else "CASE"]
3585
3586        for e in expression.args["ifs"]:
3587            statements.append(f"WHEN {self.sql(e, 'this')}")
3588            statements.append(f"THEN {self.sql(e, 'true')}")
3589
3590        default = self.sql(expression, "default")
3591
3592        if default:
3593            statements.append(f"ELSE {default}")
3594
3595        statements.append("END")
3596
3597        if self.pretty and self.too_wide(statements):
3598            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3599
3600        return " ".join(statements)
3601
3602    def constraint_sql(self, expression: exp.Constraint) -> str:
3603        this = self.sql(expression, "this")
3604        expressions = self.expressions(expression, flat=True)
3605        return f"CONSTRAINT {this} {expressions}"
3606
3607    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3608        order = expression.args.get("order")
3609        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3610        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3611
3612    def extract_sql(self, expression: exp.Extract) -> str:
3613        import sqlglot.dialects.dialect
3614
3615        this = (
3616            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3617            if self.NORMALIZE_EXTRACT_DATE_PARTS
3618            else expression.this
3619        )
3620        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3621        expression_sql = self.sql(expression, "expression")
3622
3623        return f"EXTRACT({this_sql} FROM {expression_sql})"
3624
3625    def trim_sql(self, expression: exp.Trim) -> str:
3626        trim_type = self.sql(expression, "position")
3627
3628        if trim_type == "LEADING":
3629            func_name = "LTRIM"
3630        elif trim_type == "TRAILING":
3631            func_name = "RTRIM"
3632        else:
3633            func_name = "TRIM"
3634
3635        return self.func(func_name, expression.this, expression.expression)
3636
3637    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3638        args = expression.expressions
3639        if isinstance(expression, exp.ConcatWs):
3640            args = args[1:]  # Skip the delimiter
3641
3642        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3643            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3644
3645        concat_coalesce = (
3646            self.dialect.CONCAT_WS_COALESCE
3647            if isinstance(expression, exp.ConcatWs)
3648            else self.dialect.CONCAT_COALESCE
3649        )
3650
3651        if not concat_coalesce and expression.args.get("coalesce"):
3652
3653            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3654                if not e.type:
3655                    import sqlglot.optimizer.annotate_types
3656
3657                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3658
3659                if e.is_string or e.is_type(exp.DType.ARRAY):
3660                    return e
3661
3662                return exp.func("coalesce", e, exp.Literal.string(""))
3663
3664            args = [_wrap_with_coalesce(e) for e in args]
3665
3666        return args
3667
3668    def concat_sql(self, expression: exp.Concat) -> str:
3669        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3670            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3671            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3672            # instead of coalescing them to empty string.
3673            import sqlglot.dialects.dialect
3674
3675            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3676
3677        expressions = self.convert_concat_args(expression)
3678
3679        # Some dialects don't allow a single-argument CONCAT call
3680        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3681            return self.sql(expressions[0])
3682
3683        return self.func("CONCAT", *expressions)
3684
3685    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3686        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3687            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3688            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3689            all_args = expression.expressions
3690            expression.set("coalesce", True)
3691            return self.sql(
3692                exp.case()
3693                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3694                .else_(expression)
3695            )
3696
3697        return self.func(
3698            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3699        )
3700
3701    def check_sql(self, expression: exp.Check) -> str:
3702        this = self.sql(expression, key="this")
3703        return f"CHECK ({this})"
3704
3705    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3706        expressions = self.expressions(expression, flat=True)
3707        expressions = f" ({expressions})" if expressions else ""
3708        reference = self.sql(expression, "reference")
3709        reference = f" {reference}" if reference else ""
3710        delete = self.sql(expression, "delete")
3711        delete = f" ON DELETE {delete}" if delete else ""
3712        update = self.sql(expression, "update")
3713        update = f" ON UPDATE {update}" if update else ""
3714        options = self.expressions(expression, key="options", flat=True, sep=" ")
3715        options = f" {options}" if options else ""
3716        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3717
3718    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3719        this = self.sql(expression, "this")
3720        this = f" {this}" if this else ""
3721        expressions = self.expressions(expression, flat=True)
3722        include = self.sql(expression, "include")
3723        options = self.expressions(expression, key="options", flat=True, sep=" ")
3724        options = f" {options}" if options else ""
3725        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3726
3727    def if_sql(self, expression: exp.If) -> str:
3728        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3729
3730    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3731        if self.MATCH_AGAINST_TABLE_PREFIX:
3732            expressions = []
3733            for expr in expression.expressions:
3734                if isinstance(expr, exp.Table):
3735                    expressions.append(f"TABLE {self.sql(expr)}")
3736                else:
3737                    expressions.append(expr)
3738        else:
3739            expressions = expression.expressions
3740
3741        modifier = expression.args.get("modifier")
3742        modifier = f" {modifier}" if modifier else ""
3743        return (
3744            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3745        )
3746
3747    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3748        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3749
3750    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3751        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3752
3753        if self.QUOTE_JSON_PATH:
3754            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3755
3756        return path
3757
3758    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3759        if isinstance(expression, exp.JSONPathPart):
3760            transform = self.TRANSFORMS.get(expression.__class__)
3761            if not callable(transform):
3762                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3763                return ""
3764
3765            return transform(self, expression)
3766
3767        if isinstance(expression, int):
3768            return str(expression)
3769
3770        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3771            escaped = expression.replace("'", "\\'")
3772            escaped = f"\\'{expression}\\'"
3773        else:
3774            escaped = expression.replace('"', '\\"')
3775            escaped = f'"{escaped}"'
3776
3777        return escaped
3778
3779    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3780        return f"{self.sql(expression, 'this')} FORMAT JSON"
3781
3782    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3783        # Output the Teradata column FORMAT override.
3784        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3785        this = self.sql(expression, "this")
3786        fmt = self.sql(expression, "format")
3787        return f"{this} (FORMAT {fmt})"
3788
3789    def _jsonobject_sql(
3790        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
3791    ) -> str:
3792        null_handling = expression.args.get("null_handling")
3793        null_handling = f" {null_handling}" if null_handling else ""
3794
3795        unique_keys = expression.args.get("unique_keys")
3796        if unique_keys is not None:
3797            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3798        else:
3799            unique_keys = ""
3800
3801        return_type = self.sql(expression, "return_type")
3802        return_type = f" RETURNING {return_type}" if return_type else ""
3803        encoding = self.sql(expression, "encoding")
3804        encoding = f" ENCODING {encoding}" if encoding else ""
3805
3806        if not name:
3807            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
3808
3809        return self.func(
3810            name,
3811            *expression.expressions,
3812            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3813        )
3814
3815    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3816        null_handling = expression.args.get("null_handling")
3817        null_handling = f" {null_handling}" if null_handling else ""
3818        return_type = self.sql(expression, "return_type")
3819        return_type = f" RETURNING {return_type}" if return_type else ""
3820        strict = " STRICT" if expression.args.get("strict") else ""
3821        return self.func(
3822            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3823        )
3824
3825    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3826        this = self.sql(expression, "this")
3827        order = self.sql(expression, "order")
3828        null_handling = expression.args.get("null_handling")
3829        null_handling = f" {null_handling}" if null_handling else ""
3830        return_type = self.sql(expression, "return_type")
3831        return_type = f" RETURNING {return_type}" if return_type else ""
3832        strict = " STRICT" if expression.args.get("strict") else ""
3833        return self.func(
3834            "JSON_ARRAYAGG",
3835            this,
3836            suffix=f"{order}{null_handling}{return_type}{strict})",
3837        )
3838
3839    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3840        path = self.sql(expression, "path")
3841        path = f" PATH {path}" if path else ""
3842        nested_schema = self.sql(expression, "nested_schema")
3843
3844        if nested_schema:
3845            return f"NESTED{path} {nested_schema}"
3846
3847        this = self.sql(expression, "this")
3848        kind = self.sql(expression, "kind")
3849        kind = f" {kind}" if kind else ""
3850        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3851
3852        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3853        return f"{this}{kind}{format_json}{path}{ordinality}"
3854
3855    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3856        return self.func("COLUMNS", *expression.expressions)
3857
3858    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3859        this = self.sql(expression, "this")
3860        path = self.sql(expression, "path")
3861        path = f", {path}" if path else ""
3862        error_handling = expression.args.get("error_handling")
3863        error_handling = f" {error_handling}" if error_handling else ""
3864        empty_handling = expression.args.get("empty_handling")
3865        empty_handling = f" {empty_handling}" if empty_handling else ""
3866        schema = self.sql(expression, "schema")
3867        return self.func(
3868            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3869        )
3870
3871    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3872        this = self.sql(expression, "this")
3873        kind = self.sql(expression, "kind")
3874        path = self.sql(expression, "path")
3875        path = f" {path}" if path else ""
3876        as_json = " AS JSON" if expression.args.get("as_json") else ""
3877        return f"{this} {kind}{path}{as_json}"
3878
3879    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3880        this = self.sql(expression, "this")
3881        path = self.sql(expression, "path")
3882        path = f", {path}" if path else ""
3883        expressions = self.expressions(expression)
3884        with_ = (
3885            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3886            if expressions
3887            else ""
3888        )
3889        return f"OPENJSON({this}{path}){with_}"
3890
3891    def in_sql(self, expression: exp.In) -> str:
3892        query = expression.args.get("query")
3893        unnest = expression.args.get("unnest")
3894        field = expression.args.get("field")
3895        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3896
3897        if query:
3898            in_sql = self.sql(query)
3899        elif unnest:
3900            in_sql = self.in_unnest_op(unnest)
3901        elif field:
3902            in_sql = self.sql(field)
3903        else:
3904            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3905
3906        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3907
3908    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3909        return f"(SELECT {self.sql(unnest)})"
3910
3911    def interval_sql(self, expression: exp.Interval) -> str:
3912        unit_expression = expression.args.get("unit")
3913        unit = self.sql(unit_expression) if unit_expression else ""
3914        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3915            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3916        unit = f" {unit}" if unit else ""
3917
3918        if self.SINGLE_STRING_INTERVAL:
3919            this = expression.this.name if expression.this else ""
3920            if this:
3921                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3922                    return f"INTERVAL '{this}'{unit}"
3923                return f"INTERVAL '{this}{unit}'"
3924            return f"INTERVAL{unit}"
3925
3926        this = self.sql(expression, "this")
3927        if this:
3928            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3929            this = f" {this}" if unwrapped else f" ({this})"
3930
3931        return f"INTERVAL{this}{unit}"
3932
3933    def return_sql(self, expression: exp.Return) -> str:
3934        return f"RETURN {self.sql(expression, 'this')}"
3935
3936    def reference_sql(self, expression: exp.Reference) -> str:
3937        this = self.sql(expression, "this")
3938        expressions = self.expressions(expression, flat=True)
3939        expressions = f"({expressions})" if expressions else ""
3940        options = self.expressions(expression, key="options", flat=True, sep=" ")
3941        options = f" {options}" if options else ""
3942        return f"REFERENCES {this}{expressions}{options}"
3943
3944    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3945        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3946        parent = expression.parent
3947        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3948
3949        return self.func(
3950            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3951        )
3952
3953    def paren_sql(self, expression: exp.Paren) -> str:
3954        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3955        return f"({sql}{self.seg(')', sep='')}"
3956
3957    def neg_sql(self, expression: exp.Neg) -> str:
3958        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3959        this_sql = self.sql(expression, "this")
3960        sep = " " if this_sql[0] == "-" else ""
3961        return f"-{sep}{this_sql}"
3962
3963    def not_sql(self, expression: exp.Not) -> str:
3964        return f"NOT {self.sql(expression, 'this')}"
3965
3966    def alias_sql(self, expression: exp.Alias) -> str:
3967        alias = self.sql(expression, "alias")
3968        alias = f" AS {alias}" if alias else ""
3969        return f"{self.sql(expression, 'this')}{alias}"
3970
3971    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3972        alias = expression.args["alias"]
3973
3974        parent = expression.parent
3975        pivot = parent and parent.parent
3976
3977        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3978            identifier_alias = isinstance(alias, exp.Identifier)
3979            literal_alias = isinstance(alias, exp.Literal)
3980
3981            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3982                alias.replace(exp.Literal.string(alias.output_name))
3983            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3984                alias.replace(exp.to_identifier(alias.output_name))
3985
3986        return self.alias_sql(expression)
3987
3988    def aliases_sql(self, expression: exp.Aliases) -> str:
3989        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3990
3991    def atindex_sql(self, expression: exp.AtIndex) -> str:
3992        this = self.sql(expression, "this")
3993        index = self.sql(expression, "expression")
3994        return f"{this} AT {index}"
3995
3996    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3997        this = self.sql(expression, "this")
3998        zone = self.sql(expression, "zone")
3999        return f"{this} AT TIME ZONE {zone}"
4000
4001    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
4002        this = self.sql(expression, "this")
4003        zone = self.sql(expression, "zone")
4004        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
4005
4006    def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str:
4007        return self.sql(exp.cast(expression.this, exp.DType.DATE))
4008
4009    def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:
4010        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4011
4012    def add_sql(self, expression: exp.Add) -> str:
4013        return self.binary(expression, "+")
4014
4015    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
4016        return self.connector_sql(expression, "AND", stack)
4017
4018    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
4019        return self.connector_sql(expression, "OR", stack)
4020
4021    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
4022        return self.connector_sql(expression, "XOR", stack)
4023
4024    def connector_sql(
4025        self,
4026        expression: exp.Connector,
4027        op: str,
4028        stack: list[str | exp.Expr] | None = None,
4029    ) -> str:
4030        if stack is not None:
4031            if expression.expressions:
4032                stack.append(self.expressions(expression, sep=f" {op} "))
4033            else:
4034                stack.append(expression.right)
4035                if expression.comments and self.comments:
4036                    op = self.maybe_comment(op, comments=expression.comments)
4037                stack.extend((op, expression.left))
4038            return op
4039
4040        stack = [expression]
4041        sqls: list[str] = []
4042        ops = set()
4043
4044        while stack:
4045            node = stack.pop()
4046            if isinstance(node, exp.Connector):
4047                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4048            else:
4049                sql = self.sql(node)
4050                if sqls and sqls[-1] in ops:
4051                    sqls[-1] += f" {sql}"
4052                else:
4053                    sqls.append(sql)
4054
4055        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4056        return sep.join(sqls)
4057
4058    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4059        return self.binary(expression, "&")
4060
4061    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4062        return self.binary(expression, "<<")
4063
4064    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4065        return f"~{self.sql(expression, 'this')}"
4066
4067    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4068        return self.binary(expression, "|")
4069
4070    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4071        return self.binary(expression, ">>")
4072
4073    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4074        return self.binary(expression, "^")
4075
4076    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4077        format_sql = self.sql(expression, "format")
4078        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4079        to_sql = self.sql(expression, "to")
4080        to_sql = f" {to_sql}" if to_sql else ""
4081        action = self.sql(expression, "action")
4082        action = f" {action}" if action else ""
4083        default = self.sql(expression, "default")
4084        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4085        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4086
4087    # Base implementation that excludes safe, zone, and target_type metadata args
4088    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4089        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
4090
4091    def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str:
4092        return self.func(
4093            "PARSE_DATETIME",
4094            expression.this,
4095            expression.args.get("format"),
4096            expression.args.get("zone"),
4097        )
4098
4099    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4100        zone = self.sql(expression, "this")
4101        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
4102
4103    def collate_sql(self, expression: exp.Collate) -> str:
4104        if self.COLLATE_IS_FUNC:
4105            return self.function_fallback_sql(expression)
4106        return self.binary(expression, "COLLATE")
4107
4108    def command_sql(self, expression: exp.Command) -> str:
4109        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
4110
4111    def comment_sql(self, expression: exp.Comment) -> str:
4112        this = self.sql(expression, "this")
4113        kind = expression.args["kind"]
4114        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4115        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4116        expression_sql = self.sql(expression, "expression")
4117        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4118
4119    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4120        this = self.sql(expression, "this")
4121        delete = " DELETE" if expression.args.get("delete") else ""
4122        recompress = self.sql(expression, "recompress")
4123        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4124        to_disk = self.sql(expression, "to_disk")
4125        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4126        to_volume = self.sql(expression, "to_volume")
4127        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4128        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4129
4130    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4131        where = self.sql(expression, "where")
4132        group = self.sql(expression, "group")
4133        aggregates = self.expressions(expression, key="aggregates")
4134        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4135
4136        if not (where or group or aggregates) and len(expression.expressions) == 1:
4137            return f"TTL {self.expressions(expression, flat=True)}"
4138
4139        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4140
4141    def transaction_sql(self, expression: exp.Transaction) -> str:
4142        modes = self.expressions(expression, key="modes")
4143        modes = f" {modes}" if modes else ""
4144        return f"BEGIN{modes}"
4145
4146    def commit_sql(self, expression: exp.Commit) -> str:
4147        chain = expression.args.get("chain")
4148        if chain is not None:
4149            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4150
4151        return f"COMMIT{chain or ''}"
4152
4153    def rollback_sql(self, expression: exp.Rollback) -> str:
4154        savepoint = expression.args.get("savepoint")
4155        savepoint = f" TO {savepoint}" if savepoint else ""
4156        return f"ROLLBACK{savepoint}"
4157
4158    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4159        this = self.sql(expression, "this")
4160
4161        dtype = self.sql(expression, "dtype")
4162        if dtype:
4163            collate = self.sql(expression, "collate")
4164            collate = f" COLLATE {collate}" if collate else ""
4165            using = self.sql(expression, "using")
4166            using = f" USING {using}" if using else ""
4167            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4168            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
4169
4170        default = self.sql(expression, "default")
4171        if default:
4172            return f"ALTER COLUMN {this} SET DEFAULT {default}"
4173
4174        comment = self.sql(expression, "comment")
4175        if comment:
4176            return f"ALTER COLUMN {this} COMMENT {comment}"
4177
4178        visible = expression.args.get("visible")
4179        if visible:
4180            return f"ALTER COLUMN {this} SET {visible}"
4181
4182        allow_null = expression.args.get("allow_null")
4183        drop = expression.args.get("drop")
4184
4185        if not drop and not allow_null:
4186            self.unsupported("Unsupported ALTER COLUMN syntax")
4187
4188        if allow_null is not None:
4189            keyword = "DROP" if drop else "SET"
4190            return f"ALTER COLUMN {this} {keyword} NOT NULL"
4191
4192        return f"ALTER COLUMN {this} DROP DEFAULT"
4193
4194    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4195        this = self.sql(expression, "this")
4196        rename_from = self.sql(expression, "rename_from")
4197        if rename_from:
4198            if not self.SUPPORTS_CHANGE_COLUMN:
4199                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4200            return f"CHANGE COLUMN {rename_from} {this}"
4201        if not self.SUPPORTS_MODIFY_COLUMN:
4202            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4203        return f"MODIFY COLUMN {this}"
4204
4205    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4206        this = self.sql(expression, "this")
4207
4208        visible = expression.args.get("visible")
4209        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4210
4211        return f"ALTER INDEX {this} {visible_sql}"
4212
4213    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4214        this = self.sql(expression, "this")
4215        if not isinstance(expression.this, exp.Var):
4216            this = f"KEY DISTKEY {this}"
4217        return f"ALTER DISTSTYLE {this}"
4218
4219    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4220        compound = " COMPOUND" if expression.args.get("compound") else ""
4221        this = self.sql(expression, "this")
4222        expressions = self.expressions(expression, flat=True)
4223        expressions = f"({expressions})" if expressions else ""
4224        return f"ALTER{compound} SORTKEY {this or expressions}"
4225
4226    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4227        if not self.RENAME_TABLE_WITH_DB:
4228            # Remove db from tables
4229            expression = expression.transform(
4230                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4231            ).assert_is(exp.AlterRename)
4232        this = self.sql(expression, "this")
4233        to_kw = " TO" if include_to else ""
4234        return f"RENAME{to_kw} {this}"
4235
4236    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4237        exists = " IF EXISTS" if expression.args.get("exists") else ""
4238        old_column = self.sql(expression, "this")
4239        new_column = self.sql(expression, "to")
4240        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
4241
4242    def alterset_sql(self, expression: exp.AlterSet) -> str:
4243        exprs = self.expressions(expression, flat=True)
4244        if self.ALTER_SET_WRAPPED:
4245            exprs = f"({exprs})"
4246
4247        return f"SET {exprs}"
4248
4249    def alter_sql(self, expression: exp.Alter) -> str:
4250        actions = expression.args["actions"]
4251
4252        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
4253            actions[0], exp.ColumnDef
4254        ):
4255            actions_sql = self.expressions(expression, key="actions", flat=True)
4256            actions_sql = f"ADD {actions_sql}"
4257        else:
4258            actions_list = []
4259            for action in actions:
4260                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4261                    action_sql = self.add_column_sql(action)
4262                else:
4263                    action_sql = self.sql(action)
4264                    if isinstance(action, exp.Query):
4265                        action_sql = f"AS {action_sql}"
4266
4267                actions_list.append(action_sql)
4268
4269            actions_sql = self.format_args(*actions_list).lstrip("\n")
4270
4271        iceberg = (
4272            "ICEBERG "
4273            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4274            else ""
4275        )
4276        exists = " IF EXISTS" if expression.args.get("exists") else ""
4277        on_cluster = self.sql(expression, "cluster")
4278        on_cluster = f" {on_cluster}" if on_cluster else ""
4279        only = " ONLY" if expression.args.get("only") else ""
4280        options = self.expressions(expression, key="options")
4281        options = f", {options}" if options else ""
4282        kind = self.sql(expression, "kind")
4283        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4284        check = " WITH CHECK" if expression.args.get("check") else ""
4285        cascade = (
4286            " CASCADE"
4287            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4288            else ""
4289        )
4290        this = self.sql(expression, "this")
4291        this = f" {this}" if this else ""
4292
4293        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4294
4295    def altersession_sql(self, expression: exp.AlterSession) -> str:
4296        items_sql = self.expressions(expression, flat=True)
4297        keyword = "UNSET" if expression.args.get("unset") else "SET"
4298        return f"{keyword} {items_sql}"
4299
4300    def add_column_sql(self, expression: exp.Expr) -> str:
4301        sql = self.sql(expression)
4302        if isinstance(expression, exp.Schema):
4303            column_text = " COLUMNS"
4304        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4305            column_text = " COLUMN"
4306        else:
4307            column_text = ""
4308
4309        return f"ADD{column_text} {sql}"
4310
4311    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4312        expressions = self.expressions(expression)
4313        exists = " IF EXISTS " if expression.args.get("exists") else " "
4314        return f"DROP{exists}{expressions}"
4315
4316    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4317        return "DROP PRIMARY KEY"
4318
4319    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4320        return f"ADD {self.expressions(expression, indent=False)}"
4321
4322    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4323        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4324        location = self.sql(expression, "location")
4325        location = f" {location}" if location else ""
4326        return f"ADD {exists}{self.sql(expression.this)}{location}"
4327
4328    def distinct_sql(self, expression: exp.Distinct) -> str:
4329        this = self.expressions(expression, flat=True)
4330
4331        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4332            case = exp.case()
4333            for arg in expression.expressions:
4334                case = case.when(arg.is_(exp.null()), exp.null())
4335            this = self.sql(case.else_(f"({this})"))
4336
4337        this = f" {this}" if this else ""
4338
4339        on = self.sql(expression, "on")
4340        on = f" ON {on}" if on else ""
4341        return f"DISTINCT{this}{on}"
4342
4343    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4344        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4345
4346    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4347        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4348
4349    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4350        this_sql = self.sql(expression, "this")
4351        expression_sql = self.sql(expression, "expression")
4352        kind = "MAX" if expression.args.get("max") else "MIN"
4353        return f"{this_sql} HAVING {kind} {expression_sql}"
4354
4355    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4356        return self.sql(
4357            exp.Cast(
4358                this=exp.Div(this=expression.this, expression=expression.expression),
4359                to=exp.DataType(this=exp.DType.INT),
4360            )
4361        )
4362
4363    def dpipe_sql(self, expression: exp.DPipe) -> str:
4364        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4365            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4366        return self.binary(expression, "||")
4367
4368    def div_sql(self, expression: exp.Div) -> str:
4369        l, r = expression.left, expression.right
4370
4371        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4372            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4373
4374        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4375            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4376                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4377
4378        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4379            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4380                return self.sql(
4381                    exp.cast(
4382                        l / r,
4383                        to=exp.DType.BIGINT,
4384                    )
4385                )
4386
4387        return self.binary(expression, "/")
4388
4389    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4390        n = exp._wrap(expression.this, exp.Binary)
4391        d = exp._wrap(expression.expression, exp.Binary)
4392        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4393
4394    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4395        return self.binary(expression, "OVERLAPS")
4396
4397    def distance_sql(self, expression: exp.Distance) -> str:
4398        return self.binary(expression, "<->")
4399
4400    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4401        return self.binary(expression, "<<->>")
4402
4403    def dot_sql(self, expression: exp.Dot) -> str:
4404        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4405
4406    def eq_sql(self, expression: exp.EQ) -> str:
4407        return self.binary(expression, "=")
4408
4409    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4410        return self.binary(expression, ":=")
4411
4412    def escape_sql(self, expression: exp.Escape) -> str:
4413        this = expression.this
4414        if (
4415            isinstance(this, (exp.Like, exp.ILike))
4416            and isinstance(this.expression, (exp.All, exp.Any))
4417            and not self.SUPPORTS_LIKE_QUANTIFIERS
4418        ):
4419            return self._like_sql(this, escape=expression)
4420        return self.binary(expression, "ESCAPE")
4421
4422    def glob_sql(self, expression: exp.Glob) -> str:
4423        return self.binary(expression, "GLOB")
4424
4425    def gt_sql(self, expression: exp.GT) -> str:
4426        return self.binary(expression, ">")
4427
4428    def gte_sql(self, expression: exp.GTE) -> str:
4429        return self.binary(expression, ">=")
4430
4431    def is_sql(self, expression: exp.Is) -> str:
4432        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4433            return self.sql(
4434                expression.this if expression.expression.this else exp.not_(expression.this)
4435            )
4436        return self.binary(expression, "IS")
4437
4438    def _like_sql(
4439        self,
4440        expression: exp.Like | exp.ILike,
4441        escape: exp.Escape | None = None,
4442    ) -> str:
4443        this = expression.this
4444        rhs = expression.expression
4445
4446        if isinstance(expression, exp.Like):
4447            exp_class: type[exp.Like | exp.ILike] = exp.Like
4448            op = "LIKE"
4449        else:
4450            exp_class = exp.ILike
4451            op = "ILIKE"
4452
4453        if expression.args.get("negate"):
4454            op = f"NOT {op}"
4455
4456        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4457            exprs = rhs.this.unnest()
4458
4459            if isinstance(exprs, exp.Tuple):
4460                exprs = exprs.expressions
4461            else:
4462                exprs = [exprs]
4463
4464            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4465
4466            def _make_like(expr: exp.Expression) -> exp.Expression:
4467                like: exp.Expression = exp_class(
4468                    this=this, expression=expr, negate=expression.args.get("negate")
4469                )
4470                if escape:
4471                    like = exp.Escape(this=like, expression=escape.expression.copy())
4472                return like
4473
4474            like_expr: exp.Expr = _make_like(exprs[0])
4475            for expr in exprs[1:]:
4476                like_expr = connective(like_expr, _make_like(expr), copy=False)
4477
4478            parent = escape.parent if escape else expression.parent
4479            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4480                parent, exp.Condition
4481            ):
4482                like_expr = exp.paren(like_expr, copy=False)
4483
4484            return self.sql(like_expr)
4485
4486        return self.binary(expression, op)
4487
4488    def like_sql(self, expression: exp.Like) -> str:
4489        return self._like_sql(expression)
4490
4491    def ilike_sql(self, expression: exp.ILike) -> str:
4492        return self._like_sql(expression)
4493
4494    def match_sql(self, expression: exp.Match) -> str:
4495        return self.binary(expression, "MATCH")
4496
4497    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4498        return self.binary(expression, "SIMILAR TO")
4499
4500    def lt_sql(self, expression: exp.LT) -> str:
4501        return self.binary(expression, "<")
4502
4503    def lte_sql(self, expression: exp.LTE) -> str:
4504        return self.binary(expression, "<=")
4505
4506    def mod_sql(self, expression: exp.Mod) -> str:
4507        return self.binary(expression, "%")
4508
4509    def mul_sql(self, expression: exp.Mul) -> str:
4510        return self.binary(expression, "*")
4511
4512    def neq_sql(self, expression: exp.NEQ) -> str:
4513        return self.binary(expression, "<>")
4514
4515    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4516        return self.binary(expression, "IS NOT DISTINCT FROM")
4517
4518    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4519        return self.binary(expression, "IS DISTINCT FROM")
4520
4521    def sub_sql(self, expression: exp.Sub) -> str:
4522        return self.binary(expression, "-")
4523
4524    def trycast_sql(self, expression: exp.TryCast) -> str:
4525        return self.cast_sql(expression, safe_prefix="TRY_")
4526
4527    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4528        return self.cast_sql(expression)
4529
4530    def try_sql(self, expression: exp.Try) -> str:
4531        if not self.TRY_SUPPORTED:
4532            self.unsupported("Unsupported TRY function")
4533            return self.sql(expression, "this")
4534
4535        return self.func("TRY", expression.this)
4536
4537    def log_sql(self, expression: exp.Log) -> str:
4538        this = expression.this
4539        expr = expression.expression
4540
4541        if self.dialect.LOG_BASE_FIRST is False:
4542            this, expr = expr, this
4543        elif self.dialect.LOG_BASE_FIRST is None and expr:
4544            if this.name in ("2", "10"):
4545                return self.func(f"LOG{this.name}", expr)
4546
4547            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4548
4549        return self.func("LOG", this, expr)
4550
4551    def use_sql(self, expression: exp.Use) -> str:
4552        kind = self.sql(expression, "kind")
4553        kind = f" {kind}" if kind else ""
4554        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4555        this = f" {this}" if this else ""
4556        return f"USE{kind}{this}"
4557
4558    def binary(self, expression: exp.Binary, op: str) -> str:
4559        sqls: list[str] = []
4560        stack: list[None | str | exp.Expr] = [expression]
4561        binary_type = type(expression)
4562
4563        while stack:
4564            node = stack.pop()
4565
4566            if type(node) is binary_type:
4567                op_func = node.args.get("operator")
4568                if op_func:
4569                    op = f"OPERATOR({self.sql(op_func)})"
4570
4571                stack.append(node.args.get("expression"))
4572                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4573                stack.append(node.args.get("this"))
4574            else:
4575                sqls.append(self.sql(node))
4576
4577        return "".join(sqls)
4578
4579    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4580        to_clause = self.sql(expression, "to")
4581        if to_clause:
4582            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4583
4584        return self.function_fallback_sql(expression)
4585
4586    def function_fallback_sql(self, expression: exp.Func) -> str:
4587        args = []
4588
4589        for key in expression.arg_types:
4590            arg_value = expression.args.get(key)
4591
4592            if isinstance(arg_value, list):
4593                for value in arg_value:
4594                    args.append(value)
4595            elif arg_value is not None:
4596                args.append(arg_value)
4597
4598        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4599            name = expression.meta_get("name") or expression.sql_name()
4600        else:
4601            name = expression.sql_name()
4602
4603        return self.func(name, *args)
4604
4605    def func(
4606        self,
4607        name: str,
4608        *args: t.Any,
4609        prefix: str = "(",
4610        suffix: str = ")",
4611        normalize: bool = True,
4612    ) -> str:
4613        name = self.normalize_func(name) if normalize else name
4614        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4615
4616    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4617        arg_sqls = tuple(
4618            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4619        )
4620        if self.pretty and self.too_wide(arg_sqls):
4621            return self.indent(
4622                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4623            )
4624        return sep.join(arg_sqls)
4625
4626    def too_wide(self, args: t.Iterable) -> bool:
4627        return sum(len(arg) for arg in args) > self.max_text_width
4628
4629    def format_time(
4630        self,
4631        expression: exp.Expr,
4632        inverse_time_mapping: dict[str, str] | None = None,
4633        inverse_time_trie: dict | None = None,
4634    ) -> str | None:
4635        return format_time(
4636            self.sql(expression, "format"),
4637            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4638            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4639        )
4640
4641    def expressions(
4642        self,
4643        expression: exp.Expr | None = None,
4644        key: str | None = None,
4645        sqls: t.Collection[str | exp.Expr] | None = None,
4646        flat: bool = False,
4647        indent: bool = True,
4648        skip_first: bool = False,
4649        skip_last: bool = False,
4650        sep: str = ", ",
4651        prefix: str = "",
4652        dynamic: bool = False,
4653        new_line: bool = False,
4654    ) -> str:
4655        expressions = expression.args.get(key or "expressions") if expression else sqls
4656
4657        if not expressions:
4658            return ""
4659
4660        if flat:
4661            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4662
4663        num_sqls = len(expressions)
4664        result_sqls = []
4665
4666        for i, e in enumerate(expressions):
4667            sql = self.sql(e, comment=False)
4668            if not sql:
4669                continue
4670
4671            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4672
4673            if self.pretty:
4674                if self.leading_comma:
4675                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4676                else:
4677                    result_sqls.append(
4678                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4679                    )
4680            else:
4681                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4682
4683        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4684            if new_line:
4685                result_sqls.insert(0, "")
4686                result_sqls.append("")
4687            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4688        else:
4689            result_sql = "".join(result_sqls)
4690
4691        return (
4692            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4693            if indent
4694            else result_sql
4695        )
4696
4697    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4698        flat = flat or isinstance(expression.parent, exp.Properties)
4699        expressions_sql = self.expressions(expression, flat=flat)
4700        if flat:
4701            return f"{op} {expressions_sql}"
4702        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4703
4704    def naked_property(self, expression: exp.Property) -> str:
4705        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4706        if not property_name:
4707            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4708        return f"{property_name} {self.sql(expression, 'this')}"
4709
4710    def tag_sql(self, expression: exp.Tag) -> str:
4711        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4712
4713    def token_sql(self, token_type: TokenType) -> str:
4714        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4715
4716    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4717        this = self.sql(expression, "this")
4718        expressions = self.no_identify(self.expressions, expression)
4719        expressions = (
4720            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4721        )
4722        return f"{this}{expressions}" if expressions.strip() != "" else this
4723
4724    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
4725        return self.expressions(expression, flat=True)
4726
4727    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
4728        params = self.no_identify(self.expressions, expression, flat=True)
4729        body = self.sql(expression, "this")
4730        prefix = "TABLE " if expression.args.get("is_table") else ""
4731        return f"({params}) AS {prefix}{body}"
4732
4733    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4734        this = self.sql(expression, "this")
4735        expressions = self.expressions(expression, flat=True)
4736        return f"{this}({expressions})"
4737
4738    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4739        return self.binary(expression, "=>")
4740
4741    def when_sql(self, expression: exp.When) -> str:
4742        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4743        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4744        condition = self.sql(expression, "condition")
4745        condition = f" AND {condition}" if condition else ""
4746
4747        then_expression = expression.args.get("then")
4748        if isinstance(then_expression, exp.Insert):
4749            this = self.sql(then_expression, "this")
4750            this = f"INSERT {this}" if this else "INSERT"
4751            then = self.sql(then_expression, "expression")
4752            then = f"{this} VALUES {then}" if then else this
4753        elif isinstance(then_expression, exp.Update):
4754            if isinstance(then_expression.args.get("expressions"), exp.Star):
4755                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4756            else:
4757                expressions_sql = self.expressions(then_expression)
4758                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4759        else:
4760            then = self.sql(then_expression)
4761
4762        if isinstance(then_expression, (exp.Insert, exp.Update)):
4763            where = self.sql(then_expression, "where")
4764            if where and not self.SUPPORTS_MERGE_WHERE:
4765                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4766                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4767                where = ""
4768            then = f"{then}{where}"
4769        return f"WHEN {matched}{source}{condition} THEN {then}"
4770
4771    def whens_sql(self, expression: exp.Whens) -> str:
4772        return self.expressions(expression, sep=" ", indent=False)
4773
4774    def merge_sql(self, expression: exp.Merge) -> str:
4775        table = expression.this
4776        table_alias = ""
4777
4778        hints = table.args.get("hints")
4779        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4780            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4781            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4782
4783        this = self.sql(table)
4784        using = f"USING {self.sql(expression, 'using')}"
4785        whens = self.sql(expression, "whens")
4786
4787        on = self.sql(expression, "on")
4788        on = f"ON {on}" if on else ""
4789
4790        if not on:
4791            on = self.expressions(expression, key="using_cond")
4792            on = f"USING ({on})" if on else ""
4793
4794        returning = self.sql(expression, "returning")
4795        if returning:
4796            whens = f"{whens}{returning}"
4797
4798        sep = self.sep()
4799
4800        return self.prepend_ctes(
4801            expression,
4802            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4803        )
4804
4805    @unsupported_args("format")
4806    def tochar_sql(self, expression: exp.ToChar) -> str:
4807        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
4808
4809    @unsupported_args("default")
4810    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4811        if not self.SUPPORTS_TO_NUMBER:
4812            self.unsupported("Unsupported TO_NUMBER function")
4813            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4814
4815        fmt = expression.args.get("format")
4816        if not fmt:
4817            self.unsupported("Conversion format is required for TO_NUMBER")
4818            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4819
4820        return self.func("TO_NUMBER", expression.this, fmt)
4821
4822    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4823        this = self.sql(expression, "this")
4824        kind = self.sql(expression, "kind")
4825        settings_sql = self.expressions(expression, key="settings", sep=" ")
4826        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4827        return f"{this}({kind}{args})"
4828
4829    def dictrange_sql(self, expression: exp.DictRange) -> str:
4830        this = self.sql(expression, "this")
4831        max = self.sql(expression, "max")
4832        min = self.sql(expression, "min")
4833        return f"{this}(MIN {min} MAX {max})"
4834
4835    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4836        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4837
4838    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4839        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4840
4841    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4842    def uniquekeyproperty_sql(
4843        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4844    ) -> str:
4845        return f"{prefix} ({self.expressions(expression, flat=True)})"
4846
4847    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4848    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4849        expressions = self.expressions(expression, flat=True)
4850        expressions = f" {self.wrap(expressions)}" if expressions else ""
4851        buckets = self.sql(expression, "buckets")
4852        kind = self.sql(expression, "kind")
4853        buckets = f" BUCKETS {buckets}" if buckets else ""
4854        order = self.sql(expression, "order")
4855        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4856
4857    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4858        return ""
4859
4860    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4861        expressions = self.expressions(expression, key="expressions", flat=True)
4862        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4863        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4864        buckets = self.sql(expression, "buckets")
4865        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4866
4867    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4868        this = self.sql(expression, "this")
4869        having = self.sql(expression, "having")
4870
4871        if having:
4872            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4873
4874        return self.func("ANY_VALUE", this)
4875
4876    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4877        transform = self.func("TRANSFORM", *expression.expressions)
4878        row_format_before = self.sql(expression, "row_format_before")
4879        row_format_before = f" {row_format_before}" if row_format_before else ""
4880        record_writer = self.sql(expression, "record_writer")
4881        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4882        using = f" USING {self.sql(expression, 'command_script')}"
4883        schema = self.sql(expression, "schema")
4884        schema = f" AS {schema}" if schema else ""
4885        row_format_after = self.sql(expression, "row_format_after")
4886        row_format_after = f" {row_format_after}" if row_format_after else ""
4887        record_reader = self.sql(expression, "record_reader")
4888        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4889        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4890
4891    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4892        key_block_size = self.sql(expression, "key_block_size")
4893        if key_block_size:
4894            return f"KEY_BLOCK_SIZE = {key_block_size}"
4895
4896        using = self.sql(expression, "using")
4897        if using:
4898            return f"USING {using}"
4899
4900        parser = self.sql(expression, "parser")
4901        if parser:
4902            return f"WITH PARSER {parser}"
4903
4904        comment = self.sql(expression, "comment")
4905        if comment:
4906            return f"COMMENT {comment}"
4907
4908        visible = expression.args.get("visible")
4909        if visible is not None:
4910            return "VISIBLE" if visible else "INVISIBLE"
4911
4912        engine_attr = self.sql(expression, "engine_attr")
4913        if engine_attr:
4914            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4915
4916        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4917        if secondary_engine_attr:
4918            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4919
4920        self.unsupported("Unsupported index constraint option.")
4921        return ""
4922
4923    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4924        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4925        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4926
4927    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4928        kind = self.sql(expression, "kind")
4929        kind = f"{kind} INDEX" if kind else "INDEX"
4930        this = self.sql(expression, "this")
4931        this = f" {this}" if this else ""
4932        index_type = self.sql(expression, "index_type")
4933        index_type = f" USING {index_type}" if index_type else ""
4934        expressions = self.expressions(expression, flat=True)
4935        expressions = f" ({expressions})" if expressions else ""
4936        options = self.expressions(expression, key="options", sep=" ")
4937        options = f" {options}" if options else ""
4938        return f"{kind}{this}{index_type}{expressions}{options}"
4939
4940    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4941        if self.NVL2_SUPPORTED:
4942            return self.function_fallback_sql(expression)
4943
4944        case = exp.Case().when(
4945            expression.this.is_(exp.null()).not_(copy=False),
4946            expression.args["true"],
4947            copy=False,
4948        )
4949        else_cond = expression.args.get("false")
4950        if else_cond:
4951            case.else_(else_cond, copy=False)
4952
4953        return self.sql(case)
4954
4955    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4956        this = self.sql(expression, "this")
4957        expr = self.sql(expression, "expression")
4958        position = self.sql(expression, "position")
4959        position = f", {position}" if position else ""
4960        iterator = self.sql(expression, "iterator")
4961        condition = self.sql(expression, "condition")
4962        condition = f" IF {condition}" if condition else ""
4963        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
4964
4965    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4966        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4967
4968    def opclass_sql(self, expression: exp.Opclass) -> str:
4969        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4970
4971    def _ml_sql(self, expression: exp.Func, name: str) -> str:
4972        model = self.sql(expression, "this")
4973        model = f"MODEL {model}"
4974        expr = expression.expression
4975        if expr:
4976            expr_sql = self.sql(expression, "expression")
4977            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
4978        else:
4979            expr_sql = None
4980
4981        parameters = self.sql(expression, "params_struct") or None
4982
4983        return self.func(name, model, expr_sql, parameters)
4984
4985    def predict_sql(self, expression: exp.Predict) -> str:
4986        return self._ml_sql(expression, "PREDICT")
4987
4988    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4989        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4990        return self._ml_sql(expression, name)
4991
4992    def generatetext_sql(self, expression: exp.GenerateText) -> str:
4993        return self._ml_sql(expression, "GENERATE_TEXT")
4994
4995    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
4996        return self._ml_sql(expression, "GENERATE_TABLE")
4997
4998    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
4999        return self._ml_sql(expression, "GENERATE_BOOL")
5000
5001    def generateint_sql(self, expression: exp.GenerateInt) -> str:
5002        return self._ml_sql(expression, "GENERATE_INT")
5003
5004    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
5005        return self._ml_sql(expression, "GENERATE_DOUBLE")
5006
5007    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
5008        return self._ml_sql(expression, "TRANSLATE")
5009
5010    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
5011        return self._ml_sql(expression, "FORECAST")
5012
5013    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
5014        this_sql = self.sql(expression, "this")
5015        if isinstance(expression.this, exp.Table):
5016            this_sql = f"TABLE {this_sql}"
5017
5018        return self.func(
5019            "FORECAST",
5020            this_sql,
5021            expression.args.get("data_col"),
5022            expression.args.get("timestamp_col"),
5023            expression.args.get("model"),
5024            expression.args.get("id_cols"),
5025            expression.args.get("horizon"),
5026            expression.args.get("forecast_end_timestamp"),
5027            expression.args.get("confidence_level"),
5028            expression.args.get("output_historical_time_series"),
5029            expression.args.get("context_window"),
5030        )
5031
5032    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
5033        this_sql = self.sql(expression, "this")
5034        if isinstance(expression.this, exp.Table):
5035            this_sql = f"TABLE {this_sql}"
5036
5037        return self.func(
5038            "FEATURES_AT_TIME",
5039            this_sql,
5040            expression.args.get("time"),
5041            expression.args.get("num_rows"),
5042            expression.args.get("ignore_feature_nulls"),
5043        )
5044
5045    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5046        this_sql = self.sql(expression, "this")
5047        if isinstance(expression.this, exp.Table):
5048            this_sql = f"TABLE {this_sql}"
5049
5050        query_table = self.sql(expression, "query_table")
5051        if isinstance(expression.args["query_table"], exp.Table):
5052            query_table = f"TABLE {query_table}"
5053
5054        return self.func(
5055            "VECTOR_SEARCH",
5056            this_sql,
5057            expression.args.get("column_to_search"),
5058            query_table,
5059            expression.args.get("query_column_to_search"),
5060            expression.args.get("top_k"),
5061            expression.args.get("distance_type"),
5062            expression.args.get("options"),
5063        )
5064
5065    def forin_sql(self, expression: exp.ForIn) -> str:
5066        this = self.sql(expression, "this")
5067        expression_sql = self.sql(expression, "expression")
5068        return f"FOR {this} DO {expression_sql}"
5069
5070    def refresh_sql(self, expression: exp.Refresh) -> str:
5071        this = self.sql(expression, "this")
5072        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5073        return f"REFRESH {kind}{this}"
5074
5075    def toarray_sql(self, expression: exp.ToArray) -> str:
5076        arg = expression.this
5077        if not arg.type:
5078            import sqlglot.optimizer.annotate_types
5079
5080            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5081
5082        if arg.is_type(exp.DType.ARRAY):
5083            return self.sql(arg)
5084
5085        cond_for_null = arg.is_(exp.null())
5086        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5087
5088    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5089        this = expression.this
5090        time_format = self.format_time(expression)
5091
5092        if time_format:
5093            return self.sql(
5094                exp.cast(
5095                    exp.StrToTime(this=this, format=expression.args["format"]),
5096                    exp.DType.TIME,
5097                )
5098            )
5099
5100        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5101            return self.sql(this)
5102
5103        return self.sql(exp.cast(this, exp.DType.TIME))
5104
5105    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5106        this = expression.this
5107        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5108            return self.sql(this)
5109
5110        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5111
5112    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5113        this = expression.this
5114        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5115            return self.sql(this)
5116
5117        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5118
5119    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5120        this = expression.this
5121        time_format = self.format_time(expression)
5122        safe = expression.args.get("safe")
5123        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5124            return self.sql(
5125                exp.cast(
5126                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5127                    exp.DType.DATE,
5128                )
5129            )
5130
5131        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5132            return self.sql(this)
5133
5134        if safe:
5135            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5136
5137        return self.sql(exp.cast(this, exp.DType.DATE))
5138
5139    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5140        return self.sql(
5141            exp.func(
5142                "DATEDIFF",
5143                expression.this,
5144                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5145                "day",
5146            )
5147        )
5148
5149    def lastday_sql(self, expression: exp.LastDay) -> str:
5150        if self.LAST_DAY_SUPPORTS_DATE_PART:
5151            return self.function_fallback_sql(expression)
5152
5153        unit = expression.text("unit")
5154        if unit and unit != "MONTH":
5155            self.unsupported("Date parts are not supported in LAST_DAY.")
5156
5157        return self.func("LAST_DAY", expression.this)
5158
5159    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5160        import sqlglot.dialects.dialect
5161
5162        return self.func(
5163            "DATE_ADD",
5164            expression.this,
5165            expression.expression,
5166            sqlglot.dialects.dialect.unit_to_str(expression),
5167        )
5168
5169    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5170        if self.CAN_IMPLEMENT_ARRAY_ANY:
5171            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5172            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5173            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5174            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5175
5176        import sqlglot.dialects.dialect
5177
5178        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5179        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5180            self.unsupported("ARRAY_ANY is unsupported")
5181
5182        return self.function_fallback_sql(expression)
5183
5184    def struct_sql(self, expression: exp.Struct) -> str:
5185        expression.set(
5186            "expressions",
5187            [
5188                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5189                if isinstance(e, exp.PropertyEQ)
5190                else e
5191                for e in expression.expressions
5192            ],
5193        )
5194
5195        return self.function_fallback_sql(expression)
5196
5197    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5198        low = self.sql(expression, "this")
5199        high = self.sql(expression, "expression")
5200
5201        return f"{low} TO {high}"
5202
5203    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5204        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5205        tables = f" {self.expressions(expression)}"
5206
5207        exists = " IF EXISTS" if expression.args.get("exists") else ""
5208
5209        on_cluster = self.sql(expression, "cluster")
5210        on_cluster = f" {on_cluster}" if on_cluster else ""
5211
5212        identity = self.sql(expression, "identity")
5213        identity = f" {identity} IDENTITY" if identity else ""
5214
5215        option = self.sql(expression, "option")
5216        option = f" {option}" if option else ""
5217
5218        partition = self.sql(expression, "partition")
5219        partition = f" {partition}" if partition else ""
5220
5221        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5222
5223    # This transpiles T-SQL's CONVERT function
5224    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
5225    def convert_sql(self, expression: exp.Convert) -> str:
5226        to = expression.this
5227        value = expression.expression
5228        style = expression.args.get("style")
5229        safe = expression.args.get("safe")
5230        strict = expression.args.get("strict")
5231
5232        if not to or not value:
5233            return ""
5234
5235        # Retrieve length of datatype and override to default if not specified
5236        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5237            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5238
5239        transformed: exp.Expr | None = None
5240        cast = exp.Cast if strict else exp.TryCast
5241
5242        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5243        if isinstance(style, exp.Literal) and style.is_int:
5244            import sqlglot.dialects.tsql
5245
5246            style_value = style.name
5247            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5248            if not converted_style:
5249                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5250
5251            fmt = exp.Literal.string(converted_style)
5252
5253            if to.this == exp.DType.DATE:
5254                transformed = exp.StrToDate(this=value, format=fmt)
5255            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5256                transformed = exp.StrToTime(this=value, format=fmt)
5257            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5258                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5259            elif to.this == exp.DType.TEXT:
5260                transformed = exp.TimeToStr(this=value, format=fmt)
5261
5262        if not transformed:
5263            transformed = cast(this=value, to=to, safe=safe)
5264
5265        return self.sql(transformed)
5266
5267    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
5268        this = expression.this
5269        if isinstance(this, exp.JSONPathWildcard):
5270            this = self.json_path_part(this)
5271            return f".{this}" if this else ""
5272
5273        quoted = expression.args.get("quoted")
5274        if not (
5275            quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
5276        ) and self.SAFE_JSON_PATH_KEY_RE.match(this):
5277            return f".{this}"
5278
5279        this = self.json_path_part(this)
5280
5281        if quoted and self.QUOTE_JSON_PATH:
5282            # The whole path is rendered as a single quoted string literal, so the bracketed key
5283            # (which may itself contain backslash-escaped quotes, e.g. ["x \"y\"z"]) must be
5284            # escaped again for the outer string literal (-> ["x \\"y\\"z"]).
5285            this = self.escape_str(this)
5286
5287        return (
5288            f"[{this}]"
5289            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5290            else f".{this}"
5291        )
5292
5293    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5294        this = self.json_path_part(expression.this)
5295        return f"[{this}]" if this else ""
5296
5297    def _simplify_unless_literal(self, expression: E) -> E:
5298        if not isinstance(expression, exp.Literal):
5299            import sqlglot.optimizer.simplify
5300
5301            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5302
5303        return expression
5304
5305    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5306        this = expression.this
5307        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5308            self.unsupported(
5309                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5310            )
5311            return self.sql(this)
5312
5313        if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5314            if self.IGNORE_NULLS_BEFORE_ORDER:
5315                # The first modifier here will be the one closest to the AggFunc's arg
5316                mods = sorted(
5317                    expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
5318                    key=lambda x: (
5319                        0
5320                        if isinstance(x, exp.HavingMax)
5321                        else (1 if isinstance(x, exp.Order) else 2)
5322                    ),
5323                )
5324
5325                if mods:
5326                    mod = mods[0]
5327                    this = expression.__class__(this=mod.this.copy())
5328                    this.meta["inline"] = True
5329                    mod.this.replace(this)
5330                    return self.sql(expression.this)
5331
5332            agg_func = expression.find(exp.AggFunc)
5333
5334            if agg_func:
5335                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5336                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5337
5338        return f"{self.sql(expression, 'this')} {text}"
5339
5340    def _replace_line_breaks(self, string: str) -> str:
5341        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5342        if self.pretty:
5343            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5344        return string
5345
5346    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5347        option = self.sql(expression, "this")
5348
5349        if expression.expressions:
5350            upper = option.upper()
5351
5352            # Snowflake FILE_FORMAT options are separated by whitespace
5353            sep = " " if upper == "FILE_FORMAT" else ", "
5354
5355            # Databricks copy/format options do not set their list of values with EQ
5356            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5357            values = self.expressions(expression, flat=True, sep=sep)
5358            return f"{option}{op}({values})"
5359
5360        value = self.sql(expression, "expression")
5361
5362        if not value:
5363            return option
5364
5365        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5366
5367        return f"{option}{op}{value}"
5368
5369    def credentials_sql(self, expression: exp.Credentials) -> str:
5370        cred_expr = expression.args.get("credentials")
5371        if isinstance(cred_expr, exp.Literal):
5372            # Redshift case: CREDENTIALS <string>
5373            credentials = self.sql(expression, "credentials")
5374            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5375        else:
5376            # Snowflake case: CREDENTIALS = (...)
5377            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5378            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5379
5380        storage = self.sql(expression, "storage")
5381        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5382
5383        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5384        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5385
5386        iam_role = self.sql(expression, "iam_role")
5387        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5388
5389        region = self.sql(expression, "region")
5390        region = f" REGION {region}" if region else ""
5391
5392        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5393
5394    def copy_sql(self, expression: exp.Copy) -> str:
5395        this = self.sql(expression, "this")
5396        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5397
5398        credentials = self.sql(expression, "credentials")
5399        credentials = self.seg(credentials) if credentials else ""
5400        files = self.expressions(expression, key="files", flat=True)
5401        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5402
5403        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5404        params = self.expressions(
5405            expression,
5406            key="params",
5407            sep=sep,
5408            new_line=True,
5409            skip_last=True,
5410            skip_first=True,
5411            indent=self.COPY_PARAMS_ARE_WRAPPED,
5412        )
5413
5414        if params:
5415            if self.COPY_PARAMS_ARE_WRAPPED:
5416                params = f" WITH ({params})"
5417            elif not self.pretty and (files or credentials):
5418                params = f" {params}"
5419
5420        return f"COPY{this}{kind} {files}{credentials}{params}"
5421
5422    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5423        return ""
5424
5425    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5426        on_sql = "ON" if expression.args.get("on") else "OFF"
5427        filter_col: str | None = self.sql(expression, "filter_column")
5428        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5429        retention_period: str | None = self.sql(expression, "retention_period")
5430        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5431
5432        if filter_col or retention_period:
5433            on_sql = self.func("ON", filter_col, retention_period)
5434
5435        return f"DATA_DELETION={on_sql}"
5436
5437    def maskingpolicycolumnconstraint_sql(
5438        self, expression: exp.MaskingPolicyColumnConstraint
5439    ) -> str:
5440        this = self.sql(expression, "this")
5441        expressions = self.expressions(expression, flat=True)
5442        expressions = f" USING ({expressions})" if expressions else ""
5443        return f"MASKING POLICY {this}{expressions}"
5444
5445    def gapfill_sql(self, expression: exp.GapFill) -> str:
5446        this = self.sql(expression, "this")
5447        this = f"TABLE {this}"
5448        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5449
5450    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5451        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5452
5453    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5454        this = self.sql(expression, "this")
5455        expr = expression.expression
5456
5457        if isinstance(expr, exp.Func):
5458            # T-SQL's CLR functions are case sensitive
5459            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5460        else:
5461            expr = self.sql(expression, "expression")
5462
5463        return self.scope_resolution(expr, this)
5464
5465    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5466        if self.PARSE_JSON_NAME is None:
5467            return self.sql(expression.this)
5468
5469        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5470
5471    def rand_sql(self, expression: exp.Rand) -> str:
5472        lower = self.sql(expression, "lower")
5473        upper = self.sql(expression, "upper")
5474
5475        if lower and upper:
5476            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5477        return self.func("RAND", expression.this)
5478
5479    def changes_sql(self, expression: exp.Changes) -> str:
5480        information = self.sql(expression, "information")
5481        information = f"INFORMATION => {information}"
5482        at_before = self.sql(expression, "at_before")
5483        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5484        end = self.sql(expression, "end")
5485        end = f"{self.seg('')}{end}" if end else ""
5486
5487        return f"CHANGES ({information}){at_before}{end}"
5488
5489    def pad_sql(self, expression: exp.Pad) -> str:
5490        prefix = "L" if expression.args.get("is_left") else "R"
5491
5492        fill_pattern = self.sql(expression, "fill_pattern") or None
5493        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5494            fill_pattern = "' '"
5495
5496        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5497
5498    def summarize_sql(self, expression: exp.Summarize) -> str:
5499        table = " TABLE" if expression.args.get("table") else ""
5500        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5501
5502    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5503        generate_series = exp.GenerateSeries(**expression.args)
5504
5505        parent = expression.parent
5506        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5507            parent = parent.parent
5508
5509        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5510            return self.sql(exp.Unnest(expressions=[generate_series]))
5511
5512        if isinstance(parent, exp.Select):
5513            self.unsupported("GenerateSeries projection unnesting is not supported.")
5514
5515        return self.sql(generate_series)
5516
5517    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5518        if self.SUPPORTS_CONVERT_TIMEZONE:
5519            return self.function_fallback_sql(expression)
5520
5521        source_tz = expression.args.get("source_tz")
5522        target_tz = expression.args.get("target_tz")
5523        timestamp = expression.args.get("timestamp")
5524
5525        if source_tz and timestamp:
5526            timestamp = exp.AtTimeZone(
5527                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5528            )
5529
5530        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5531
5532        return self.sql(expr)
5533
5534    def json_sql(self, expression: exp.JSON) -> str:
5535        this = self.sql(expression, "this")
5536        this = f" {this}" if this else ""
5537
5538        _with = expression.args.get("with_")
5539
5540        if _with is None:
5541            with_sql = ""
5542        elif not _with:
5543            with_sql = " WITHOUT"
5544        else:
5545            with_sql = " WITH"
5546
5547        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5548
5549        return f"JSON{this}{with_sql}{unique_sql}"
5550
5551    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5552        path = self.sql(expression, "path")
5553        returning = self.sql(expression, "returning")
5554        returning = f" RETURNING {returning}" if returning else ""
5555
5556        on_condition = self.sql(expression, "on_condition")
5557        on_condition = f" {on_condition}" if on_condition else ""
5558
5559        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5560
5561    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5562        regexp = " REGEXP" if expression.args.get("regexp") else ""
5563        return f"SKIP{regexp} {self.sql(expression.expression)}"
5564
5565    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5566        else_ = "ELSE " if expression.args.get("else_") else ""
5567        condition = self.sql(expression, "expression")
5568        condition = f"WHEN {condition} THEN " if condition else else_
5569        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5570        return f"{condition}{insert}"
5571
5572    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5573        kind = self.sql(expression, "kind")
5574        expressions = self.seg(self.expressions(expression, sep=" "))
5575        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
5576        return res
5577
5578    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5579        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5580        empty = expression.args.get("empty")
5581        empty = (
5582            f"DEFAULT {empty} ON EMPTY"
5583            if isinstance(empty, exp.Expr)
5584            else self.sql(expression, "empty")
5585        )
5586
5587        error = expression.args.get("error")
5588        error = (
5589            f"DEFAULT {error} ON ERROR"
5590            if isinstance(error, exp.Expr)
5591            else self.sql(expression, "error")
5592        )
5593
5594        if error and empty:
5595            error = (
5596                f"{empty} {error}"
5597                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5598                else f"{error} {empty}"
5599            )
5600            empty = ""
5601
5602        null = self.sql(expression, "null")
5603
5604        return f"{empty}{error}{null}"
5605
5606    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5607        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5608        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5609
5610    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5611        this = self.sql(expression, "this")
5612        path = self.sql(expression, "path")
5613
5614        passing = self.expressions(expression, "passing")
5615        passing = f" PASSING {passing}" if passing else ""
5616
5617        on_condition = self.sql(expression, "on_condition")
5618        on_condition = f" {on_condition}" if on_condition else ""
5619
5620        path = f"{path}{passing}{on_condition}"
5621
5622        return self.func("JSON_EXISTS", this, path)
5623
5624    def _add_arrayagg_null_filter(
5625        self,
5626        array_agg_sql: str,
5627        array_agg_expr: exp.ArrayAgg,
5628        column_expr: exp.Expr,
5629    ) -> str:
5630        """
5631        Add NULL filter to ARRAY_AGG if dialect requires it.
5632
5633        Args:
5634            array_agg_sql: The generated ARRAY_AGG SQL string
5635            array_agg_expr: The ArrayAgg expression node
5636            column_expr: The column/expression to filter (before ORDER BY wrapping)
5637
5638        Returns:
5639            SQL string with FILTER clause added if needed
5640        """
5641        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5642        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5643        if not (
5644            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5645        ):
5646            return array_agg_sql
5647
5648        parent = array_agg_expr.parent
5649        if isinstance(parent, exp.Filter):
5650            parent_cond = parent.expression.this
5651            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5652        elif column_expr.find(exp.Column):
5653            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5654            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5655            this_sql = (
5656                self.expressions(column_expr)
5657                if isinstance(column_expr, exp.Distinct)
5658                else self.sql(column_expr)
5659            )
5660            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5661
5662        return array_agg_sql
5663
5664    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5665        array_agg = self.function_fallback_sql(expression)
5666        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
5667
5668    def slice_sql(self, expression: exp.Slice) -> str:
5669        step = self.sql(expression, "step")
5670        end = self.sql(expression.expression)
5671        begin = self.sql(expression.this)
5672
5673        sql = f"{end}:{step}" if step else end
5674        return f"{begin}:{sql}" if sql else f"{begin}:"
5675
5676    def apply_sql(self, expression: exp.Apply) -> str:
5677        this = self.sql(expression, "this")
5678        expr = self.sql(expression, "expression")
5679
5680        return f"{this} APPLY({expr})"
5681
5682    def _grant_or_revoke_sql(
5683        self,
5684        expression: exp.Grant | exp.Revoke,
5685        keyword: str,
5686        preposition: str,
5687        grant_option_prefix: str = "",
5688        grant_option_suffix: str = "",
5689    ) -> str:
5690        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5691
5692        kind = self.sql(expression, "kind")
5693        kind = f" {kind}" if kind else ""
5694
5695        securable = self.sql(expression, "securable")
5696        securable = f" {securable}" if securable else ""
5697
5698        principals = self.expressions(expression, key="principals", flat=True)
5699
5700        if not expression.args.get("grant_option"):
5701            grant_option_prefix = grant_option_suffix = ""
5702
5703        # cascade for revoke only
5704        cascade = self.sql(expression, "cascade")
5705        cascade = f" {cascade}" if cascade else ""
5706
5707        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5708
5709    def grant_sql(self, expression: exp.Grant) -> str:
5710        return self._grant_or_revoke_sql(
5711            expression,
5712            keyword="GRANT",
5713            preposition="TO",
5714            grant_option_suffix=" WITH GRANT OPTION",
5715        )
5716
5717    def revoke_sql(self, expression: exp.Revoke) -> str:
5718        return self._grant_or_revoke_sql(
5719            expression,
5720            keyword="REVOKE",
5721            preposition="FROM",
5722            grant_option_prefix="GRANT OPTION FOR ",
5723        )
5724
5725    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5726        this = self.sql(expression, "this")
5727        columns = self.expressions(expression, flat=True)
5728        columns = f"({columns})" if columns else ""
5729
5730        return f"{this}{columns}"
5731
5732    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5733        this = self.sql(expression, "this")
5734
5735        kind = self.sql(expression, "kind")
5736        kind = f"{kind} " if kind else ""
5737
5738        return f"{kind}{this}"
5739
5740    def columns_sql(self, expression: exp.Columns) -> str:
5741        func = self.function_fallback_sql(expression)
5742        if expression.args.get("unpack"):
5743            func = f"*{func}"
5744
5745        return func
5746
5747    def overlay_sql(self, expression: exp.Overlay) -> str:
5748        this = self.sql(expression, "this")
5749        expr = self.sql(expression, "expression")
5750        from_sql = self.sql(expression, "from_")
5751        for_sql = self.sql(expression, "for_")
5752        for_sql = f" FOR {for_sql}" if for_sql else ""
5753
5754        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
5755
5756    @unsupported_args("format")
5757    def todouble_sql(self, expression: exp.ToDouble) -> str:
5758        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5759        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
5760
5761    def string_sql(self, expression: exp.String) -> str:
5762        this = expression.this
5763        zone = expression.args.get("zone")
5764
5765        if zone:
5766            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5767            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5768            # set for source_tz to transpile the time conversion before the STRING cast
5769            this = exp.ConvertTimezone(
5770                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5771            )
5772
5773        return self.sql(exp.cast(this, exp.DType.VARCHAR))
5774
5775    def median_sql(self, expression: exp.Median) -> str:
5776        if not self.SUPPORTS_MEDIAN:
5777            return self.sql(
5778                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5779            )
5780
5781        return self.function_fallback_sql(expression)
5782
5783    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5784        filler = self.sql(expression, "this")
5785        filler = f" {filler}" if filler else ""
5786        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5787        return f"TRUNCATE{filler} {with_count}"
5788
5789    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5790        if self.SUPPORTS_UNIX_SECONDS:
5791            return self.function_fallback_sql(expression)
5792
5793        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
5794
5795        return self.sql(
5796            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5797        )
5798
5799    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5800        dim = expression.expression
5801
5802        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5803        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5804            if not (dim.is_int and dim.name == "1"):
5805                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5806            dim = None
5807
5808        # If dimension is required but not specified, default initialize it
5809        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5810            dim = exp.Literal.number(1)
5811
5812        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5813
5814    def attach_sql(self, expression: exp.Attach) -> str:
5815        this = self.sql(expression, "this")
5816        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5817        expressions = self.expressions(expression)
5818        expressions = f" ({expressions})" if expressions else ""
5819
5820        return f"ATTACH{exists_sql} {this}{expressions}"
5821
5822    def detach_sql(self, expression: exp.Detach) -> str:
5823        kind = self.sql(expression, "kind")
5824        kind = f" {kind}" if kind else ""
5825        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
5826        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5827        exists = " IF EXISTS" if expression.args.get("exists") else ""
5828        if exists:
5829            kind = kind or " DATABASE"
5830
5831        this = self.sql(expression, "this")
5832        this = f" {this}" if this else ""
5833        cluster = self.sql(expression, "cluster")
5834        cluster = f" {cluster}" if cluster else ""
5835        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
5836        sync = " SYNC" if expression.args.get("sync") else ""
5837        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
5838
5839    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5840        this = self.sql(expression, "this")
5841        value = self.sql(expression, "expression")
5842        value = f" {value}" if value else ""
5843        return f"{this}{value}"
5844
5845    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5846        return (
5847            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5848        )
5849
5850    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5851        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5852        encode = f"{encode} {self.sql(expression, 'this')}"
5853
5854        properties = expression.args.get("properties")
5855        if properties:
5856            encode = f"{encode} {self.properties(properties)}"
5857
5858        return encode
5859
5860    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5861        this = self.sql(expression, "this")
5862        include = f"INCLUDE {this}"
5863
5864        column_def = self.sql(expression, "column_def")
5865        if column_def:
5866            include = f"{include} {column_def}"
5867
5868        alias = self.sql(expression, "alias")
5869        if alias:
5870            include = f"{include} AS {alias}"
5871
5872        return include
5873
5874    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5875        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
5876        name = f"{prefix} {self.sql(expression, 'this')}"
5877        return self.func("XMLELEMENT", name, *expression.expressions)
5878
5879    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5880        this = self.sql(expression, "this")
5881        expr = self.sql(expression, "expression")
5882        expr = f"({expr})" if expr else ""
5883        return f"{this}{expr}"
5884
5885    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5886        partitions = self.expressions(expression, "partition_expressions")
5887        create = self.expressions(expression, "create_expressions")
5888        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
5889
5890    def partitionbyrangepropertydynamic_sql(
5891        self, expression: exp.PartitionByRangePropertyDynamic
5892    ) -> str:
5893        start = self.sql(expression, "start")
5894        end = self.sql(expression, "end")
5895
5896        every = expression.args["every"]
5897        if isinstance(every, exp.Interval) and every.this.is_string:
5898            every.this.replace(exp.Literal.number(every.name))
5899
5900        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5901
5902    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5903        name = self.sql(expression, "this")
5904        values = self.expressions(expression, flat=True)
5905
5906        return f"NAME {name} VALUE {values}"
5907
5908    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5909        kind = self.sql(expression, "kind")
5910        sample = self.sql(expression, "sample")
5911        return f"SAMPLE {sample} {kind}"
5912
5913    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5914        kind = self.sql(expression, "kind")
5915        option = self.sql(expression, "option")
5916        option = f" {option}" if option else ""
5917        this = self.sql(expression, "this")
5918        this = f" {this}" if this else ""
5919        columns = self.expressions(expression)
5920        columns = f" {columns}" if columns else ""
5921        return f"{kind}{option} STATISTICS{this}{columns}"
5922
5923    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5924        this = self.sql(expression, "this")
5925        columns = self.expressions(expression)
5926        inner_expression = self.sql(expression, "expression")
5927        inner_expression = f" {inner_expression}" if inner_expression else ""
5928        update_options = self.sql(expression, "update_options")
5929        update_options = f" {update_options} UPDATE" if update_options else ""
5930        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
5931
5932    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5933        kind = self.sql(expression, "kind")
5934        kind = f" {kind}" if kind else ""
5935        return f"DELETE{kind} STATISTICS"
5936
5937    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5938        inner_expression = self.sql(expression, "expression")
5939        return f"LIST CHAINED ROWS{inner_expression}"
5940
5941    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5942        kind = self.sql(expression, "kind")
5943        this = self.sql(expression, "this")
5944        this = f" {this}" if this else ""
5945        inner_expression = self.sql(expression, "expression")
5946        return f"VALIDATE {kind}{this}{inner_expression}"
5947
5948    def analyze_sql(self, expression: exp.Analyze) -> str:
5949        options = self.expressions(expression, key="options", sep=" ")
5950        options = f" {options}" if options else ""
5951        kind = self.sql(expression, "kind")
5952        kind = f" {kind}" if kind else ""
5953        this = self.sql(expression, "this")
5954        this = f" {this}" if this else ""
5955        mode = self.sql(expression, "mode")
5956        mode = f" {mode}" if mode else ""
5957        properties = self.sql(expression, "properties")
5958        properties = f" {properties}" if properties else ""
5959        partition = self.sql(expression, "partition")
5960        partition = f" {partition}" if partition else ""
5961        inner_expression = self.sql(expression, "expression")
5962        inner_expression = f" {inner_expression}" if inner_expression else ""
5963        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5964
5965    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5966        this = self.sql(expression, "this")
5967        namespaces = self.expressions(expression, key="namespaces")
5968        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5969        passing = self.expressions(expression, key="passing")
5970        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5971        columns = self.expressions(expression, key="columns")
5972        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5973        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5974        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5975
5976    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5977        this = self.sql(expression, "this")
5978        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
5979
5980    def export_sql(self, expression: exp.Export) -> str:
5981        this = self.sql(expression, "this")
5982        connection = self.sql(expression, "connection")
5983        connection = f"WITH CONNECTION {connection} " if connection else ""
5984        options = self.sql(expression, "options")
5985        return f"EXPORT DATA {connection}{options} AS {this}"
5986
5987    def declare_sql(self, expression: exp.Declare) -> str:
5988        replace = "OR REPLACE " if expression.args.get("replace") else ""
5989        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
5990
5991    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5992        variables = self.expressions(expression, "this")
5993        default = self.sql(expression, "default")
5994        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
5995
5996        kind = self.sql(expression, "kind")
5997        if isinstance(expression.args.get("kind"), exp.Schema):
5998            kind = f"TABLE {kind}"
5999
6000        kind = f" {kind}" if kind else ""
6001
6002        return f"{variables}{kind}{default}"
6003
6004    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
6005        kind = self.sql(expression, "kind")
6006        this = self.sql(expression, "this")
6007        set = self.sql(expression, "expression")
6008        using = self.sql(expression, "using")
6009        using = f" USING {using}" if using else ""
6010
6011        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
6012
6013        return f"{kind_sql} {this} SET {set}{using}"
6014
6015    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
6016        params = self.expressions(expression, key="params", flat=True)
6017        return self.func(expression.name, *expression.expressions) + f"({params})"
6018
6019    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
6020        return self.func(expression.name, *expression.expressions)
6021
6022    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
6023        return self.anonymousaggfunc_sql(expression)
6024
6025    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
6026        return self.parameterizedagg_sql(expression)
6027
6028    def show_sql(self, expression: exp.Show) -> str:
6029        self.unsupported("Unsupported SHOW statement")
6030        return ""
6031
6032    def install_sql(self, expression: exp.Install) -> str:
6033        self.unsupported("Unsupported INSTALL statement")
6034        return ""
6035
6036    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
6037        # Snowflake GET/PUT statements:
6038        #   PUT <file> <internalStage> <properties>
6039        #   GET <internalStage> <file> <properties>
6040        props = expression.args.get("properties")
6041        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
6042        this = self.sql(expression, "this")
6043        target = self.sql(expression, "target")
6044
6045        if isinstance(expression, exp.Put):
6046            return f"PUT {this} {target}{props_sql}"
6047        else:
6048            return f"GET {target} {this}{props_sql}"
6049
6050    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
6051        this = self.sql(expression, "this")
6052        expr = self.sql(expression, "expression")
6053        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6054        return f"TRANSLATE({this} USING {expr}{with_error})"
6055
6056    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6057        if self.SUPPORTS_DECODE_CASE:
6058            return self.func("DECODE", *expression.expressions)
6059
6060        decode_expr, *expressions = expression.expressions
6061
6062        ifs = []
6063        for search, result in zip(expressions[::2], expressions[1::2]):
6064            if isinstance(search, exp.Literal):
6065                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6066            elif isinstance(search, exp.Null):
6067                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6068            else:
6069                if isinstance(search, exp.Binary):
6070                    search = exp.paren(search)
6071
6072                cond = exp.or_(
6073                    decode_expr.eq(search),
6074                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6075                    copy=False,
6076                )
6077                ifs.append(exp.If(this=cond, true=result))
6078
6079        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6080        return self.sql(case)
6081
6082    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6083        this = self.sql(expression, "this")
6084        this = self.seg(this, sep="")
6085        dimensions = self.expressions(
6086            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6087        )
6088        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6089        metrics = self.expressions(
6090            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6091        )
6092        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6093        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6094        facts = self.seg(f"FACTS {facts}") if facts else ""
6095        where = self.sql(expression, "where")
6096        where = self.seg(f"WHERE {where}") if where else ""
6097        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6098        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6099
6100    def getextract_sql(self, expression: exp.GetExtract) -> str:
6101        this = expression.this
6102        expr = expression.expression
6103
6104        if not this.type or not expression.type:
6105            import sqlglot.optimizer.annotate_types
6106
6107            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6108
6109        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6110            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6111
6112        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
6113
6114    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6115        return self.sql(
6116            exp.DateAdd(
6117                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6118                expression=expression.this,
6119                unit=exp.var("DAY"),
6120            )
6121        )
6122
6123    def space_sql(self: Generator, expression: exp.Space) -> str:
6124        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
6125
6126    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6127        return f"BUILD {self.sql(expression, 'this')}"
6128
6129    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6130        method = self.sql(expression, "method")
6131        kind = expression.args.get("kind")
6132        if not kind:
6133            return f"REFRESH {method}"
6134
6135        every = self.sql(expression, "every")
6136        unit = self.sql(expression, "unit")
6137        every = f" EVERY {every} {unit}" if every else ""
6138        starts = self.sql(expression, "starts")
6139        starts = f" STARTS {starts}" if starts else ""
6140
6141        return f"REFRESH {method} ON {kind}{every}{starts}"
6142
6143    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6144        self.unsupported("The model!attribute syntax is not supported")
6145        return ""
6146
6147    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6148        return self.func("DIRECTORY", expression.this)
6149
6150    def uuid_sql(self, expression: exp.Uuid) -> str:
6151        is_string = expression.args.get("is_string", False)
6152        uuid_func_sql = self.func("UUID")
6153
6154        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6155            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6156
6157        return uuid_func_sql
6158
6159    def initcap_sql(self, expression: exp.Initcap) -> str:
6160        delimiters = expression.expression
6161
6162        if delimiters:
6163            # do not generate delimiters arg if we are round-tripping from default delimiters
6164            if (
6165                delimiters.is_string
6166                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6167            ):
6168                delimiters = None
6169            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6170                self.unsupported("INITCAP does not support custom delimiters")
6171                delimiters = None
6172
6173        return self.func("INITCAP", expression.this, delimiters)
6174
6175    def localtime_sql(self, expression: exp.Localtime) -> str:
6176        this = expression.this
6177        return self.func("LOCALTIME", this) if this else "LOCALTIME"
6178
6179    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6180        this = expression.this
6181        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
6182
6183    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6184        this = expression.this.name.upper()
6185        if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
6186            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
6187            return "WEEK"
6188
6189        return self.func("WEEK", expression.this)
6190
6191    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6192        this = self.expressions(expression)
6193        charset = self.sql(expression, "charset")
6194        using = f" USING {charset}" if charset else ""
6195        return self.func(name, this + using)
6196
6197    def block_sql(self, expression: exp.Block) -> str:
6198        expressions = self.expressions(expression, sep="; ", flat=True)
6199        return f"{expressions}" if expressions else ""
6200
6201    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6202        self.unsupported("Unsupported Stored Procedure syntax")
6203        return ""
6204
6205    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6206        self.unsupported("Unsupported If block syntax")
6207        return ""
6208
6209    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6210        self.unsupported("Unsupported While block syntax")
6211        return ""
6212
6213    def execute_sql(self, expression: exp.Execute) -> str:
6214        self.unsupported("Unsupported Execute syntax")
6215        return ""
6216
6217    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6218        self.unsupported("Unsupported Execute syntax")
6219        return ""
6220
6221    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6222        props = self.expressions(expression, sep=" ")
6223        return f"MODIFY {props}"
6224
6225    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6226        kind = expression.args.get("kind")
6227        return f"USING {kind} {self.sql(expression, 'this')}"
6228
6229    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6230        this = self.sql(expression, "this")
6231        to = self.sql(expression, "to")
6232        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)
868    def __init__(
869        self,
870        pretty: bool | int | None = None,
871        identify: str | bool = False,
872        normalize: bool = False,
873        pad: int = 2,
874        indent: int = 2,
875        normalize_functions: str | bool | None = None,
876        unsupported_level: ErrorLevel = ErrorLevel.WARN,
877        max_unsupported: int = 3,
878        leading_comma: bool = False,
879        max_text_width: int = 80,
880        comments: bool = True,
881        dialect: DialectType = None,
882    ):
883        import sqlglot
884        import sqlglot.dialects.dialect
885
886        self.pretty = pretty if pretty is not None else sqlglot.pretty
887        self.identify = identify
888        self.normalize = normalize
889        self.pad = pad
890        self._indent = indent
891        self.unsupported_level = unsupported_level
892        self.max_unsupported = max_unsupported
893        self.leading_comma = leading_comma
894        self.max_text_width = max_text_width
895        self.comments = comments
896        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
897
898        # This is both a Dialect property and a Generator argument, so we prioritize the latter
899        self.normalize_functions = (
900            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
901        )
902
903        self.unsupported_messages: list[str] = []
904        self._escaped_quote_end: str = (
905            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
906        )
907        self._escaped_byte_quote_end: str = (
908            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
909            if self.dialect.BYTE_END
910            else ""
911        )
912        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
913
914        self._next_name = name_sequence("_t")
915
916        self._identifier_start = self.dialect.IDENTIFIER_START
917        self._identifier_end = self.dialect.IDENTIFIER_END
918
919        self._quote_json_path_key_using_brackets = True
920
921        cls = type(self)
922        dispatch = _DISPATCH_CACHE.get(cls)
923        if dispatch is None:
924            dispatch = _build_dispatch(cls)
925            _DISPATCH_CACHE[cls] = dispatch
926        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
LIMIT_FETCH = 'ALL'
LIMIT_ONLY_LITERALS = False
RENAME_TABLE_WITH_DB = True
GROUPINGS_SEP = ','
INDEX_ON = 'ON'
INOUT_SEPARATOR = ' '
JOIN_HINTS = True
DIRECTED_JOINS = False
TABLE_HINTS = True
QUERY_HINTS = True
QUERY_HINT_SEP = ', '
IS_BOOL_ALLOWED = True
DUPLICATE_KEY_UPDATE_WITH_SET = True
LIMIT_IS_TOP = False
RETURNING_END = True
EXTRACT_ALLOWS_QUOTES = True
TZ_TO_WITH_TIME_ZONE = False
NVL2_SUPPORTED = True
SELECT_KINDS: tuple[str, ...] = ('STRUCT', 'VALUE')
VALUES_AS_TABLE = True
ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
UNNEST_WITH_ORDINALITY = True
AGGREGATE_FILTER_SUPPORTED = True
SEMI_ANTI_JOIN_WITH_SIDE = True
COMPUTED_COLUMN_WITH_TYPE = True
SUPPORTS_TABLE_COPY = True
TABLESAMPLE_REQUIRES_PARENS = True
TABLESAMPLE_SIZE_IS_ROWS = True
TABLESAMPLE_KEYWORDS = 'TABLESAMPLE'
TABLESAMPLE_WITH_METHOD = True
TABLESAMPLE_SEED_KEYWORD = 'SEED'
COLLATE_IS_FUNC = False
DATA_TYPE_SPECIFIERS_ALLOWED = False
ENSURE_BOOLS = False
CTE_RECURSIVE_KEYWORD_REQUIRED = True
SUPPORTS_SINGLE_ARG_CONCAT = True
LAST_DAY_SUPPORTS_DATE_PART = True
SUPPORTS_TABLE_ALIAS_COLUMNS = True
SUPPORTS_NAMED_CTE_COLUMNS = True
UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
JSON_KEY_VALUE_PAIR_SEP = ':'
INSERT_OVERWRITE = ' OVERWRITE TABLE'
SUPPORTS_SELECT_INTO = False
SUPPORTS_UNLOGGED_TABLES = False
SUPPORTS_CREATE_TABLE_LIKE = True
SUPPORTS_MODIFY_COLUMN = False
SUPPORTS_CHANGE_COLUMN = False
LIKE_PROPERTY_INSIDE_SCHEMA = False
MULTI_ARG_DISTINCT = True
JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
JSON_PATH_BRACKETED_KEY_SUPPORTED = True
JSON_PATH_SINGLE_QUOTE_ESCAPE = False
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.NCHAR: 'NCHAR'>, <DType.NVARCHAR: 'NVARCHAR'>, <DType.VARCHAR: 'VARCHAR'>, <DType.CHAR: 'CHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: ClassVar[set[type[sqlglot.expressions.core.Expr]]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = ()
SAFE_JSON_PATH_KEY_RE: ClassVar = re.compile('^[_a-zA-Z][\\w]*$')
SENTINEL_LINE_BREAK = '__SQLGLOT__LB__'
pretty
identify
normalize
pad
unsupported_level
max_unsupported
leading_comma
max_text_width
comments
dialect
normalize_functions
unsupported_messages: list[str]
def generate( self, expression: sqlglot.expressions.core.Expr, copy: bool = True) -> str:
928    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
929        """
930        Generates the SQL string corresponding to the given syntax tree.
931
932        Args:
933            expression: The syntax tree.
934            copy: Whether to copy the expression. The generator performs mutations so
935                it is safer to copy.
936
937        Returns:
938            The SQL string corresponding to `expression`.
939        """
940        if copy:
941            expression = expression.copy()
942
943        expression = self.preprocess(expression)
944
945        self.unsupported_messages = []
946        sql = self.sql(expression).strip()
947
948        if self.pretty:
949            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
950
951        if self.unsupported_level == ErrorLevel.IGNORE:
952            return sql
953
954        if self.unsupported_level == ErrorLevel.WARN:
955            for msg in self.unsupported_messages:
956                logger.warning(msg)
957        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
958            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
959
960        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:
962    def preprocess(self, expression: exp.Expr) -> exp.Expr:
963        """Apply generic preprocessing transformations to a given expression."""
964        expression = self._move_ctes_to_top_level(expression)
965
966        if self.ENSURE_BOOLS:
967            import sqlglot.transforms
968
969            expression = sqlglot.transforms.ensure_bools(expression)
970
971        return expression

Apply generic preprocessing transformations to a given expression.

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