Edit on GitHub

sqlglot.generator

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

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

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

Generator converts a given syntax tree to the corresponding SQL string.

Arguments:
  • pretty: Whether to format the produced SQL string. Default: False.
  • identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True: Always quote except for specials cases. 'safe': Only quote identifiers that are case insensitive.
  • normalize: Whether to normalize identifiers to lowercase. Default: False.
  • pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
  • indent: The indentation size in a formatted string. For example, this affects the indentation of subqueries and filters under a WHERE clause. Default: 2.
  • normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
  • unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
  • max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
  • leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
  • max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
  • comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: bool | int | None = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: str | bool | None = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.Dialect, type[sqlglot.dialects.Dialect], NoneType] = None)
903    def __init__(
904        self,
905        pretty: bool | int | None = None,
906        identify: str | bool = False,
907        normalize: bool = False,
908        pad: int = 2,
909        indent: int = 2,
910        normalize_functions: str | bool | None = None,
911        unsupported_level: ErrorLevel = ErrorLevel.WARN,
912        max_unsupported: int = 3,
913        leading_comma: bool = False,
914        max_text_width: int = 80,
915        comments: bool = True,
916        dialect: DialectType = None,
917    ):
918        import sqlglot
919        import sqlglot.dialects.dialect
920
921        self.pretty = pretty if pretty is not None else sqlglot.pretty
922        self.identify = identify
923        self.normalize = normalize
924        self.pad = pad
925        self._indent = indent
926        self.unsupported_level = unsupported_level
927        self.max_unsupported = max_unsupported
928        self.leading_comma = leading_comma
929        self.max_text_width = max_text_width
930        self.comments = comments
931        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
932
933        # This is both a Dialect property and a Generator argument, so we prioritize the latter
934        self.normalize_functions = (
935            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
936        )
937
938        self.unsupported_messages: list[str] = []
939        self._escaped_quote_end: str = (
940            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
941        )
942        self._escaped_byte_quote_end: str = (
943            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
944            if self.dialect.BYTE_END
945            else ""
946        )
947        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
948
949        self._next_name = name_sequence("_t")
950
951        self._identifier_start = self.dialect.IDENTIFIER_START
952        self._identifier_end = self.dialect.IDENTIFIER_END
953
954        self._quote_json_path_key_using_brackets = True
955
956        cls = type(self)
957        dispatch = _DISPATCH_CACHE.get(cls)
958        if dispatch is None:
959            dispatch = _build_dispatch(cls)
960            _DISPATCH_CACHE[cls] = dispatch
961        self._dispatch = dispatch
TRANSFORMS: ClassVar[dict[type[sqlglot.expressions.core.Expr], Callable[..., str]]] = {<class 'sqlglot.expressions.query.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.core.Adjacent'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainedBy'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.AssumeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.BinaryColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.string.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.SessionUser'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApiProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.EndStatement'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HybridProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.datatypes.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsTopKey'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBPathExists'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObject'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObjectAgg'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.NetFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsRight'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InvisibleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ZeroFillColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.SafeFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.TriggerExecute'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Variadic'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ForceProperty'>: <function Generator.<lambda>>}
NULL_ORDERING_SUPPORTED: bool | None = True
WINDOW_FUNCS_WITH_NULL_ORDERING: ClassVar[tuple[type[sqlglot.expressions.core.Expression], ...]] = ()
IGNORE_NULLS_IN_FUNC = False
IGNORE_NULLS_BEFORE_ORDER = True
LOCKING_READS_SUPPORTED = False
EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
WRAP_DERIVED_VALUES = True
CREATE_FUNCTION_RETURN_AS = True
MATCHED_BY_SOURCE = True
SUPPORTS_MERGE_WHERE = False
SINGLE_STRING_INTERVAL = False
INTERVAL_ALLOWS_PLURAL_FORM = True
AUTO_REFRESH_BARE_INTERVALS = False
LIMIT_FETCH = 'ALL'
LIMIT_ONLY_LITERALS = False
RENAME_TABLE_WITH_DB = True
GROUPINGS_SEP = ','
SUPPORTS_GROUPING_SETS_AS_SUFFIX = False
INDEX_ON = 'ON'
INOUT_SEPARATOR = ' '
JOIN_HINTS = True
DIRECTED_JOINS = False
TABLE_HINTS = True
QUERY_HINTS = True
QUERY_HINT_SEP = ', '
IS_BOOL_ALLOWED = True
DUPLICATE_KEY_UPDATE_WITH_SET = True
LIMIT_IS_TOP = False
RETURNING_END = True
EXTRACT_ALLOWS_QUOTES = True
TZ_TO_WITH_TIME_ZONE = False
NVL2_SUPPORTED = True
SELECT_KINDS: tuple[str, ...] = ('STRUCT', 'VALUE')
VALUES_AS_TABLE = True
ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
UNNEST_WITH_ORDINALITY = True
SEMI_ANTI_JOIN_WITH_SIDE = True
COMPUTED_COLUMN_WITH_TYPE = True
SUPPORTS_TABLE_COPY = True
TABLESAMPLE_REQUIRES_PARENS = True
TABLESAMPLE_SIZE_IS_ROWS = True
TABLESAMPLE_KEYWORDS = 'TABLESAMPLE'
TABLESAMPLE_WITH_METHOD = True
TABLESAMPLE_SEED_KEYWORD = 'SEED'
HISTORICAL_DATA_POST_ALIAS = False
COLLATE_IS_FUNC = False
DATA_TYPE_SPECIFIERS_ALLOWED = False
ENSURE_BOOLS = False
CTE_RECURSIVE_KEYWORD_REQUIRED = True
SUPPORTS_SINGLE_ARG_CONCAT = True
LAST_DAY_SUPPORTS_DATE_PART = True
SUPPORTS_TABLE_ALIAS_COLUMNS = True
SUPPORTS_NAMED_CTE_COLUMNS = True
UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
PIVOT_ALIAS_WITH_AS = True
JSON_KEY_VALUE_PAIR_SEP = ':'
INSERT_OVERWRITE = ' OVERWRITE TABLE'
SUPPORTS_SELECT_INTO = False
SUPPORTS_UNLOGGED_TABLES = False
SUPPORTS_CREATE_TABLE_LIKE = True
SUPPORTS_MODIFY_COLUMN = False
SUPPORTS_CHANGE_COLUMN = False
SUPPORTS_ALTER_COLUMN_NULLABILITY = False
SUPPORTS_ALTER_COLUMN_IF_EXISTS = False
LIKE_PROPERTY_INSIDE_SCHEMA = False
MULTI_ARG_DISTINCT = True
JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
JSON_PATH_BRACKETED_KEY_SUPPORTED = True
JSON_PATH_SINGLE_QUOTE_ESCAPE = False
JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False
CAN_IMPLEMENT_ARRAY_ANY = False
SUPPORTS_TO_NUMBER = True
SUPPORTS_WINDOW_EXCLUDE = False
SET_OP_MODIFIERS = True
SET_OP_LIMITS = False
SET_OP_PARENTHESIZED_OPERANDS = True
COPY_PARAMS_ARE_WRAPPED = True
COPY_PARAMS_EQ_REQUIRED = False
COPY_HAS_INTO_KEYWORD = True
TRY_SUPPORTED = True
SUPPORTS_UESCAPE = True
UNICODE_SUBSTITUTE: ClassVar[Any] = None
STAR_EXCEPT = 'EXCEPT'
HEX_FUNC = 'HEX'
WITH_PROPERTIES_PREFIX = 'WITH'
QUOTE_JSON_PATH = True
PAD_FILL_PATTERN_IS_REQUIRED = False
SUPPORTS_EXPLODING_PROJECTIONS = True
ARRAY_CONCAT_IS_VAR_LEN = True
SUPPORTS_CONVERT_TIMEZONE = False
SUPPORTS_MEDIAN = True
SUPPORTS_UNIX_SECONDS = False
ALTER_SET_WRAPPED = False
NORMALIZE_EXTRACT_DATE_PARTS = False
PARSE_JSON_NAME: str | None = 'PARSE_JSON'
ARRAY_SIZE_NAME: str = 'ARRAY_LENGTH'
ALTER_SET_TYPE = 'SET DATA TYPE'
ARRAY_SIZE_DIM_REQUIRED: bool | None = None
SUPPORTS_DECODE_CASE = True
SUPPORTS_BETWEEN_FLAGS = False
SUPPORTS_LIKE_QUANTIFIERS = True
MATCH_AGAINST_TABLE_PREFIX: str | None = None
SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
DECLARE_DEFAULT_ASSIGNMENT = '='
UPDATE_STATEMENT_SUPPORTS_FROM = True
STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
TYPE_MAPPING: ClassVar = {<DType.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <DType.NCHAR: 'NCHAR'>: 'CHAR', <DType.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <DType.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <DType.LONGTEXT: 'LONGTEXT'>: 'TEXT', <DType.TINYTEXT: 'TINYTEXT'>: 'TEXT', <DType.BLOB: 'BLOB'>: 'VARBINARY', <DType.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <DType.LONGBLOB: 'LONGBLOB'>: 'BLOB', <DType.TINYBLOB: 'TINYBLOB'>: 'BLOB', <DType.INET: 'INET'>: 'INET', <DType.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <DType.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
UNSUPPORTED_TYPES: ClassVar[set[sqlglot.expressions.datatypes.DType]] = set()
TYPE_PARAM_SETTINGS: ClassVar[dict[sqlglot.expressions.datatypes.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]] = {}
TIME_PART_SINGULARS: ClassVar = {'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS: ClassVar = {'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function <lambda>>, 'qualify': <function <lambda>>}
TOKEN_MAPPING: ClassVar[dict[sqlglot.tokenizer_core.TokenType, str]] = {}
STRUCT_DELIMITER: ClassVar = ('<', '>')
PARAMETER_TOKEN = '@'
NAMED_PLACEHOLDER_TOKEN = ':'
EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: ClassVar[set[str]] = set()
PROPERTIES_LOCATION: ClassVar = {<class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AlgorithmProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApiProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.AutoIncrementProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BackupProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BlockCompressionProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ChecksumProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CollateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.query.Cluster'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusteredByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusterProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistributedByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DuplicateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DataBlocksizeProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DataDeletionProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DefinerProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DictRange'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DistKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EncodeProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.EngineProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.FallbackProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.FreespaceProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.HeapProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.HybridProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.IncludeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IsolatedLoadingProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.JournalProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LikeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LocationProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockingProperty'>: <PropertiesLocation.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.properties.LogProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MergeBlockRatioProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.ModuleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.OnProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.query.Order'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.PartitionedOfProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.constraints.PrimaryKey'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Property'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.RefreshTriggerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RollupProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowFormatProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatDelimitedProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatSerdeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SampleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SecureProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SerdeProperties'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.Set'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SetProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SharingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.SequenceProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.TriggerProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.SortKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageHandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.StrictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Tags'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
RESERVED_KEYWORDS: ClassVar[set[str]] = set()
EXCLUDE_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = (<class 'sqlglot.expressions.core.Binary'>, <class 'sqlglot.expressions.query.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = (<class 'sqlglot.expressions.core.Column'>, <class 'sqlglot.expressions.core.Literal'>, <class 'sqlglot.expressions.core.Neg'>, <class 'sqlglot.expressions.core.Paren'>)
PARAMETERIZABLE_TEXT_TYPES: ClassVar = {<DType.NVARCHAR: 'NVARCHAR'>, <DType.VARCHAR: 'VARCHAR'>, <DType.CHAR: 'CHAR'>, <DType.NCHAR: 'NCHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: ClassVar[set[type[sqlglot.expressions.core.Expr]]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = ()
MOD_OPERATOR = '%'
MOD_PAREN_PARENT_TYPES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = (<class 'sqlglot.expressions.core.Mul'>, <class 'sqlglot.expressions.core.Div'>, <class 'sqlglot.expressions.core.IntDiv'>, <class 'sqlglot.expressions.core.Mod'>)
SAFE_JSON_PATH_KEY_RE: ClassVar = re.compile('^[_a-zA-Z][\\w]*$')
SENTINEL_LINE_BREAK = '__SQLGLOT__LB__'
pretty
identify
normalize
pad
unsupported_level
max_unsupported
leading_comma
max_text_width
comments
dialect
normalize_functions
unsupported_messages: list[str]
def generate( self, expression: sqlglot.expressions.core.Expr, copy: bool = True) -> str:
963    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
964        """
965        Generates the SQL string corresponding to the given syntax tree.
966
967        Args:
968            expression: The syntax tree.
969            copy: Whether to copy the expression. The generator performs mutations so
970                it is safer to copy.
971
972        Returns:
973            The SQL string corresponding to `expression`.
974        """
975        if copy:
976            expression = expression.copy()
977
978        expression = self.preprocess(expression)
979
980        self.unsupported_messages = []
981        sql = self.sql(expression).strip()
982
983        if self.pretty:
984            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
985
986        if self.unsupported_level == ErrorLevel.IGNORE:
987            return sql
988
989        if self.unsupported_level == ErrorLevel.WARN:
990            for msg in self.unsupported_messages:
991                logger.warning(msg)
992        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
993            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
994
995        return sql

Generates the SQL string corresponding to the given syntax tree.

Arguments:
  • expression: The syntax tree.
  • copy: Whether to copy the expression. The generator performs mutations so it is safer to copy.
Returns:

The SQL string corresponding to expression.

def preprocess( self, expression: sqlglot.expressions.core.Expr) -> sqlglot.expressions.core.Expr:
 997    def preprocess(self, expression: exp.Expr) -> exp.Expr:
 998        """Apply generic preprocessing transformations to a given expression."""
 999        expression = self._move_ctes_to_top_level(expression)
1000
1001        if self.ENSURE_BOOLS:
1002            import sqlglot.transforms
1003
1004            expression = sqlglot.transforms.ensure_bools(expression)
1005
1006        return expression

Apply generic preprocessing transformations to a given expression.

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