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
  28_HIGH_BYTE_RE = re.compile(r"[\x80-\xff]")
  29
  30ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)")
  31UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}."
  32
  33
  34def unsupported_args(
  35    *args: str | tuple[str, str],
  36) -> t.Callable[[GeneratorMethod], GeneratorMethod]:
  37    """
  38    Decorator that can be used to mark certain args of an `Expr` subclass as unsupported.
  39    It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
  40    """
  41    diagnostic_by_arg: dict[str, str | None] = {}
  42    for arg in args:
  43        if isinstance(arg, str):
  44            diagnostic_by_arg[arg] = None
  45        else:
  46            diagnostic_by_arg[arg[0]] = arg[1]
  47
  48    def decorator(func: GeneratorMethod) -> GeneratorMethod:
  49        @wraps(func)
  50        def _func(generator: G, expression: E) -> str:
  51            expression_name = expression.__class__.__name__
  52            dialect_name = generator.dialect.__class__.__name__
  53
  54            for arg_name, diagnostic in diagnostic_by_arg.items():
  55                if expression.args.get(arg_name):
  56                    diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format(
  57                        arg_name, expression_name, dialect_name
  58                    )
  59                    generator.unsupported(diagnostic)
  60
  61            return func(generator, expression)
  62
  63        return _func
  64
  65    return decorator
  66
  67
  68AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, t.Any] = {
  69    "windows": lambda self, e: (
  70        self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
  71        if e.args.get("windows")
  72        else ""
  73    ),
  74    "qualify": lambda self, e: self.sql(e, "qualify"),
  75}
  76
  77
  78_DISPATCH_CACHE: dict[type[Generator], dict[type[exp.Expr], t.Callable[..., str]]] = {}
  79
  80
  81def _build_dispatch(
  82    cls: type[Generator],
  83) -> dict[type[exp.Expr], t.Callable[..., str]]:
  84    dispatch: dict[type[exp.Expr], t.Callable[..., str]] = dict(cls.TRANSFORMS)
  85
  86    for attr_name in dir(cls):
  87        if not attr_name.endswith("_sql") or attr_name.startswith("_"):
  88            continue
  89
  90        expr_key = attr_name[:-4]
  91        expr_cls = exp.EXPR_CLASSES.get(expr_key)
  92
  93        if expr_cls and expr_cls not in dispatch:
  94            dispatch[expr_cls] = getattr(cls, attr_name)
  95
  96    return dispatch
  97
  98
  99class Generator:
 100    """
 101    Generator converts a given syntax tree to the corresponding SQL string.
 102
 103    Args:
 104        pretty: Whether to format the produced SQL string.
 105            Default: False.
 106        identify: Determines when an identifier should be quoted. Possible values are:
 107            False (default): Never quote, except in cases where it's mandatory by the dialect.
 108            True: Always quote except for specials cases.
 109            'safe': Only quote identifiers that are case insensitive.
 110        normalize: Whether to normalize identifiers to lowercase.
 111            Default: False.
 112        pad: The pad size in a formatted string. For example, this affects the indentation of
 113            a projection in a query, relative to its nesting level.
 114            Default: 2.
 115        indent: The indentation size in a formatted string. For example, this affects the
 116            indentation of subqueries and filters under a `WHERE` clause.
 117            Default: 2.
 118        normalize_functions: How to normalize function names. Possible values are:
 119            "upper" or True (default): Convert names to uppercase.
 120            "lower": Convert names to lowercase.
 121            False: Disables function name normalization.
 122        unsupported_level: Determines the generator's behavior when it encounters unsupported expressions.
 123            Default ErrorLevel.WARN.
 124        max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.
 125            This is only relevant if unsupported_level is ErrorLevel.RAISE.
 126            Default: 3
 127        leading_comma: Whether the comma is leading or trailing in select expressions.
 128            This is only relevant when generating in pretty mode.
 129            Default: False
 130        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
 131            The default is on the smaller end because the length only represents a segment and not the true
 132            line length.
 133            Default: 80
 134        comments: Whether to preserve comments in the output SQL code.
 135            Default: True
 136    """
 137
 138    TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = {
 139        **JSON_PATH_PART_TRANSFORMS,
 140        exp.Adjacent: lambda self, e: self.binary(e, "-|-"),
 141        exp.AllowedValuesProperty: lambda self, e: (
 142            f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
 143        ),
 144        exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
 145        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
 146        exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"),
 147        exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
 148        exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
 149        exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})",
 150        exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
 151        exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
 152        exp.BinaryColumnConstraint: lambda *_: "BINARY",
 153        exp.CaseSpecificColumnConstraint: lambda _, e: (
 154            f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC"
 155        ),
 156        exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT",
 157        exp.Ceil: lambda self, e: self.ceil_floor(e),
 158        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
 159        exp.CharacterSetProperty: lambda self, e: (
 160            f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}"
 161        ),
 162        exp.ClusteredColumnConstraint: lambda self, e: (
 163            f"CLUSTERED ({self.expressions(e, 'this', indent=False)})"
 164        ),
 165        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
 166        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
 167        exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}",
 168        exp.ConvertToCharset: lambda self, e: self.func(
 169            "CONVERT", e.this, e.args["dest"], e.args.get("source")
 170        ),
 171        exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
 172        exp.CredentialsProperty: lambda self, e: (
 173            f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})"
 174        ),
 175        exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG",
 176        exp.SessionUser: lambda *_: "SESSION_USER",
 177        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 178        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 179        exp.ApiProperty: lambda *_: "API",
 180        exp.ApplicationProperty: lambda *_: "APPLICATION",
 181        exp.CatalogProperty: lambda *_: "CATALOG",
 182        exp.ComputeProperty: lambda *_: "COMPUTE",
 183        exp.DatabaseProperty: lambda *_: "DATABASE",
 184        exp.DynamicProperty: lambda *_: "DYNAMIC",
 185        exp.EmptyProperty: lambda *_: "EMPTY",
 186        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 187        exp.EndStatement: lambda *_: "END",
 188        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 189        exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}",
 190        exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}",
 191        exp.EphemeralColumnConstraint: lambda self, e: (
 192            f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}"
 193        ),
 194        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 195        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 196        exp.Except: lambda self, e: self.set_operations(e),
 197        exp.ExternalProperty: lambda *_: "EXTERNAL",
 198        exp.Floor: lambda self, e: self.ceil_floor(e),
 199        exp.Get: lambda self, e: self.get_put_sql(e),
 200        exp.GlobalProperty: lambda *_: "GLOBAL",
 201        exp.HeapProperty: lambda *_: "HEAP",
 202        exp.HybridProperty: lambda *_: "HYBRID",
 203        exp.IcebergProperty: lambda *_: "ICEBERG",
 204        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 205        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 206        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 207        exp.Intersect: lambda self, e: self.set_operations(e),
 208        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 209        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)),
 210        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 211        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 212        exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"),
 213        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 214        exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"),
 215        exp.JSONObject: lambda self, e: self._jsonobject_sql(e),
 216        exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e),
 217        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 218        exp.LocationProperty: lambda self, e: self.naked_property(e),
 219        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 220        exp.MaskingProperty: lambda *_: "MASKING",
 221        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 222        exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
 223        exp.NetworkProperty: lambda *_: "NETWORK",
 224        exp.NonClusteredColumnConstraint: lambda self, e: (
 225            f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})"
 226        ),
 227        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 228        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 229        exp.OnCommitProperty: lambda _, e: (
 230            f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS"
 231        ),
 232        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 233        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 234        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 235        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 236        exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
 237        exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
 238        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 239        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 240        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 241        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 242        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 243        exp.ProjectionPolicyColumnConstraint: lambda self, e: (
 244            f"PROJECTION POLICY {self.sql(e, 'this')}"
 245        ),
 246        exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE",
 247        exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
 248        exp.Put: lambda self, e: self.get_put_sql(e),
 249        exp.RemoteWithConnectionModelProperty: lambda self, e: (
 250            f"REMOTE WITH CONNECTION {self.sql(e, 'this')}"
 251        ),
 252        exp.ReturnsProperty: lambda self, e: (
 253            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 254        ),
 255        exp.RowAccessProperty: lambda *_: "ROW ACCESS",
 256        exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
 257        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 258        exp.SecureProperty: lambda *_: "SECURE",
 259        exp.SecurityIntegrationProperty: lambda *_: "SECURITY",
 260        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 261        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 262        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 263        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 264        exp.SqlReadWriteProperty: lambda _, e: e.name,
 265        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
 266        exp.StabilityProperty: lambda _, e: e.name,
 267        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 268        exp.StreamingTableProperty: lambda *_: "STREAMING",
 269        exp.StrictProperty: lambda *_: "STRICT",
 270        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 271        exp.TableColumn: lambda self, e: self.sql(e.this),
 272        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 273        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 274        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 275        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 276        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 277        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 278        exp.TransientProperty: lambda *_: "TRANSIENT",
 279        exp.VirtualProperty: lambda *_: "VIRTUAL",
 280        exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}",
 281        exp.Union: lambda self, e: self.set_operations(e),
 282        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 283        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 284        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 285        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 286        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 287        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 288        exp.UtcTimestamp: lambda self, e: self.sql(
 289            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 290        ),
 291        exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
 292        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 293        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 294        exp.VolatileProperty: lambda *_: "VOLATILE",
 295        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 296        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 297        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 298        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 299        exp.ForceProperty: lambda *_: "FORCE",
 300    }
 301
 302    # Whether null ordering is supported in order by
 303    # True: Full Support, None: No support, False: No support for certain cases
 304    # such as window specifications, aggregate functions etc
 305    NULL_ORDERING_SUPPORTED: bool | None = True
 306
 307    # Window functions that support NULLS FIRST/LAST
 308    WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = ()
 309
 310    # Whether ignore nulls is inside the agg or outside.
 311    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 312    IGNORE_NULLS_IN_FUNC = False
 313
 314    # Whether IGNORE NULLS is placed before ORDER BY in the agg.
 315    # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS)
 316    IGNORE_NULLS_BEFORE_ORDER = True
 317
 318    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 319    LOCKING_READS_SUPPORTED = False
 320
 321    # Whether the EXCEPT and INTERSECT operations can return duplicates
 322    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 323
 324    # Wrap derived values in parens, usually standard but spark doesn't support it
 325    WRAP_DERIVED_VALUES = True
 326
 327    # Whether create function uses an AS before the RETURN
 328    CREATE_FUNCTION_RETURN_AS = True
 329
 330    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 331    MATCHED_BY_SOURCE = True
 332
 333    # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported
 334    SUPPORTS_MERGE_WHERE = False
 335
 336    # Whether the INTERVAL expression works only with values like '1 day'
 337    SINGLE_STRING_INTERVAL = False
 338
 339    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 340    INTERVAL_ALLOWS_PLURAL_FORM = True
 341
 342    # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the
 343    # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND
 344    AUTO_REFRESH_BARE_INTERVALS = False
 345
 346    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 347    LIMIT_FETCH = "ALL"
 348
 349    # Whether limit and fetch allows expresions or just limits
 350    LIMIT_ONLY_LITERALS = False
 351
 352    # Whether a table is allowed to be renamed with a db
 353    RENAME_TABLE_WITH_DB = True
 354
 355    # The separator for grouping sets and rollups
 356    GROUPINGS_SEP = ","
 357
 358    # Whether GROUPING SETS can follow GROUP BY expressions without a comma
 359    SUPPORTS_GROUPING_SETS_AS_SUFFIX = False
 360
 361    # The string used for creating an index on a table
 362    INDEX_ON = "ON"
 363
 364    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 365    INOUT_SEPARATOR = " "
 366
 367    # Whether join hints should be generated
 368    JOIN_HINTS = True
 369
 370    # Whether directed joins are supported
 371    DIRECTED_JOINS = False
 372
 373    # Whether table hints should be generated
 374    TABLE_HINTS = True
 375
 376    # Whether query hints should be generated
 377    QUERY_HINTS = True
 378
 379    # What kind of separator to use for query hints
 380    QUERY_HINT_SEP = ", "
 381
 382    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 383    IS_BOOL_ALLOWED = True
 384
 385    # Whether casting a fractional number to an integer rounds it instead of truncating it
 386    CAST_TO_INT_ROUNDS = False
 387
 388    # Whether the native integer division operator returns NULL for a zero divisor instead of raising
 389    SAFE_INT_DIVISION = False
 390
 391    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 392    DUPLICATE_KEY_UPDATE_WITH_SET = True
 393
 394    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 395    LIMIT_IS_TOP = False
 396
 397    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 398    RETURNING_END = True
 399
 400    # Whether to generate an unquoted value for EXTRACT's date part argument
 401    EXTRACT_ALLOWS_QUOTES = True
 402
 403    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 404    TZ_TO_WITH_TIME_ZONE = False
 405
 406    # Whether the NVL2 function is supported
 407    NVL2_SUPPORTED = True
 408
 409    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 410    SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE")
 411
 412    # Whether VALUES statements can be used as derived tables.
 413    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 414    # SELECT * VALUES into SELECT UNION
 415    VALUES_AS_TABLE = True
 416
 417    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 418    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 419
 420    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 421    UNNEST_WITH_ORDINALITY = True
 422
 423    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 424    SEMI_ANTI_JOIN_WITH_SIDE = True
 425
 426    # Whether to include the type of a computed column in the CREATE DDL
 427    COMPUTED_COLUMN_WITH_TYPE = True
 428
 429    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 430    SUPPORTS_TABLE_COPY = True
 431
 432    # Whether parentheses are required around the table sample's expression
 433    TABLESAMPLE_REQUIRES_PARENS = True
 434
 435    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 436    TABLESAMPLE_SIZE_IS_ROWS = True
 437
 438    # The keyword(s) to use when generating a sample clause
 439    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 440
 441    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 442    TABLESAMPLE_WITH_METHOD = True
 443
 444    # The keyword to use when specifying the seed of a sample clause
 445    TABLESAMPLE_SEED_KEYWORD = "SEED"
 446
 447    # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias
 448    HISTORICAL_DATA_POST_ALIAS = False
 449
 450    # Whether COLLATE is a function instead of a binary operator
 451    COLLATE_IS_FUNC = False
 452
 453    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 454    DATA_TYPE_SPECIFIERS_ALLOWED = False
 455
 456    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 457    ENSURE_BOOLS = False
 458
 459    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 460    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 461
 462    # Whether CONCAT requires >1 arguments
 463    SUPPORTS_SINGLE_ARG_CONCAT = True
 464
 465    # Whether LAST_DAY function supports a date part argument
 466    LAST_DAY_SUPPORTS_DATE_PART = True
 467
 468    # Whether named columns are allowed in table aliases
 469    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 470
 471    # Whether named columns are allowed in CTE definitions
 472    SUPPORTS_NAMED_CTE_COLUMNS = True
 473
 474    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 475    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 476
 477    # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048)
 478    PIVOT_ALIAS_WITH_AS = True
 479
 480    # What delimiter to use for separating JSON key/value pairs
 481    JSON_KEY_VALUE_PAIR_SEP = ":"
 482
 483    # INSERT OVERWRITE TABLE x override
 484    INSERT_OVERWRITE = " OVERWRITE TABLE"
 485
 486    # Whether the SELECT .. INTO syntax is used instead of CTAS
 487    SUPPORTS_SELECT_INTO = False
 488
 489    # Whether UNLOGGED tables can be created
 490    SUPPORTS_UNLOGGED_TABLES = False
 491
 492    # Whether the CREATE TABLE LIKE statement is supported
 493    SUPPORTS_CREATE_TABLE_LIKE = True
 494
 495    # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 496    SUPPORTS_MODIFY_COLUMN = False
 497
 498    # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported
 499    SUPPORTS_CHANGE_COLUMN = False
 500
 501    # Whether ALTER COLUMN can set a column's nullability together with its type
 502    SUPPORTS_ALTER_COLUMN_NULLABILITY = False
 503
 504    # Whether ALTER COLUMN IF EXISTS is supported
 505    SUPPORTS_ALTER_COLUMN_IF_EXISTS = False
 506
 507    # Whether the LikeProperty needs to be specified inside of the schema clause
 508    LIKE_PROPERTY_INSIDE_SCHEMA = False
 509
 510    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 511    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 512    MULTI_ARG_DISTINCT = True
 513
 514    # Whether the JSON extraction operators expect a value of type JSON
 515    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 516
 517    # Whether bracketed keys like ["foo"] are supported in JSON paths
 518    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 519
 520    # Whether to escape keys using single quotes in JSON paths
 521    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 522
 523    # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be
 524    # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match
 525    # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks
 526    # where a bare colon key is case-insensitive but a bracketed key is case-sensitive.
 527    JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False
 528
 529    # The JSONPathPart expressions supported by this dialect
 530    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 531
 532    # Whether any(f(x) for x in array) can be implemented by this dialect
 533    CAN_IMPLEMENT_ARRAY_ANY = False
 534
 535    # Whether the function TO_NUMBER is supported
 536    SUPPORTS_TO_NUMBER = True
 537
 538    # Whether EXCLUDE in window specification is supported
 539    SUPPORTS_WINDOW_EXCLUDE = False
 540
 541    # Whether or not set op modifiers apply to the outer set op or select.
 542    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 543    # True means limit 1 happens after the set op, False means it it happens on y.
 544    SET_OP_MODIFIERS = True
 545
 546    # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses.
 547    SET_OP_LIMITS = False
 548
 549    # Whether set operation operands can be parenthesized without a SELECT wrapper.
 550    SET_OP_PARENTHESIZED_OPERANDS = True
 551
 552    # Whether parameters from COPY statement are wrapped in parentheses
 553    COPY_PARAMS_ARE_WRAPPED = True
 554
 555    # Whether values of params are set with "=" token or empty space
 556    COPY_PARAMS_EQ_REQUIRED = False
 557
 558    # Whether COPY statement has INTO keyword
 559    COPY_HAS_INTO_KEYWORD = True
 560
 561    # Whether the conditional TRY(expression) function is supported
 562    TRY_SUPPORTED = True
 563
 564    # Whether the UESCAPE syntax in unicode strings is supported
 565    SUPPORTS_UESCAPE = True
 566
 567    # Function used to replace escaped unicode codes in unicode strings
 568    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 569
 570    # The keyword to use when generating a star projection with excluded columns
 571    STAR_EXCEPT = "EXCEPT"
 572
 573    # The HEX function name
 574    HEX_FUNC = "HEX"
 575
 576    # Function that constructs a binary value from a hex-encoded byte string.
 577    BYTE_STRING_FUNCTION = ""
 578
 579    # The keywords to use when prefixing & separating WITH based properties
 580    WITH_PROPERTIES_PREFIX = "WITH"
 581
 582    # Whether to quote the generated expression of exp.JsonPath
 583    QUOTE_JSON_PATH = True
 584
 585    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 586    PAD_FILL_PATTERN_IS_REQUIRED = False
 587
 588    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 589    SUPPORTS_EXPLODING_PROJECTIONS = True
 590
 591    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 592    ARRAY_CONCAT_IS_VAR_LEN = True
 593
 594    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 595    SUPPORTS_CONVERT_TIMEZONE = False
 596
 597    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 598    SUPPORTS_MEDIAN = True
 599
 600    # Whether UNIX_SECONDS(timestamp) is supported
 601    SUPPORTS_UNIX_SECONDS = False
 602
 603    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 604    ALTER_SET_WRAPPED = False
 605
 606    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 607    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 608    # TODO: The normalization should be done by default once we've tested it across all dialects.
 609    NORMALIZE_EXTRACT_DATE_PARTS = False
 610
 611    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 612    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 613
 614    # The function name of the exp.ArraySize expression
 615    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 616
 617    # The syntax to use when altering the type of a column
 618    ALTER_SET_TYPE = "SET DATA TYPE"
 619
 620    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 621    # None -> Doesn't support it at all
 622    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 623    # True (Postgres) -> Explicitly requires it
 624    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 625
 626    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 627    SUPPORTS_DECODE_CASE = True
 628
 629    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 630    SUPPORTS_BETWEEN_FLAGS = False
 631
 632    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 633    SUPPORTS_LIKE_QUANTIFIERS = True
 634
 635    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 636    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 637
 638    # Whether to include the VARIABLE keyword for SET assignments
 639    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 640
 641    # The keyword to use for default value assignment in DECLARE statements
 642    DECLARE_DEFAULT_ASSIGNMENT = "="
 643
 644    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 645    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 646    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 647    UPDATE_STATEMENT_SUPPORTS_FROM = True
 648
 649    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 650    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 651
 652    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 653    # - Snowflake: DROP ICEBERG TABLE a.b;
 654    # - DuckDB:    DROP TABLE a.b;
 655    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 656
 657    TYPE_MAPPING: t.ClassVar = {
 658        exp.DType.DATETIME2: "TIMESTAMP",
 659        exp.DType.NCHAR: "CHAR",
 660        exp.DType.NVARCHAR: "VARCHAR",
 661        exp.DType.MEDIUMTEXT: "TEXT",
 662        exp.DType.LONGTEXT: "TEXT",
 663        exp.DType.TINYTEXT: "TEXT",
 664        exp.DType.BLOB: "VARBINARY",
 665        exp.DType.MEDIUMBLOB: "BLOB",
 666        exp.DType.LONGBLOB: "BLOB",
 667        exp.DType.TINYBLOB: "BLOB",
 668        exp.DType.INET: "INET",
 669        exp.DType.ROWVERSION: "VARBINARY",
 670        exp.DType.SMALLDATETIME: "TIMESTAMP",
 671    }
 672
 673    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 674
 675    # mapping of DType to its default parameters, bounds
 676    TYPE_PARAM_SETTINGS: t.ClassVar[
 677        dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]
 678    ] = {}
 679
 680    TIME_PART_SINGULARS: t.ClassVar = {
 681        "MICROSECONDS": "MICROSECOND",
 682        "SECONDS": "SECOND",
 683        "MINUTES": "MINUTE",
 684        "HOURS": "HOUR",
 685        "DAYS": "DAY",
 686        "WEEKS": "WEEK",
 687        "MONTHS": "MONTH",
 688        "QUARTERS": "QUARTER",
 689        "YEARS": "YEAR",
 690    }
 691
 692    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 693        "cluster": lambda self, e: self.sql(e, "cluster"),
 694        "distribute": lambda self, e: self.sql(e, "distribute"),
 695        "sort": lambda self, e: self.sql(e, "sort"),
 696        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 697    }
 698
 699    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 700
 701    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 702
 703    PARAMETER_TOKEN = "@"
 704    NAMED_PLACEHOLDER_TOKEN = ":"
 705
 706    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 707
 708    PROPERTIES_LOCATION: t.ClassVar = {
 709        exp.AggregateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 710        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 711        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 712        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 713        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 714        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 715        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 716        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 717        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 718        exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA,
 719        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 720        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 721        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 722        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 723        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 724        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 725        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 726        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 727        exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA,
 728        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 729        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 730        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 731        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 732        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 733        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 734        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 735        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 736        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 737        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 738        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 739        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 740        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 741        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 742        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 743        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 744        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 745        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 746        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 747        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 748        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 749        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 750        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 751        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 752        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 753        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 754        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 755        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 756        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 757        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 758        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 759        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 760        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 761        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 762        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 763        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 764        exp.LogProperty: exp.Properties.Location.POST_NAME,
 765        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 766        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 767        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 768        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 769        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 770        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 771        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 772        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 773        exp.Order: exp.Properties.Location.POST_SCHEMA,
 774        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 775        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 776        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 777        exp.PhysicalProperty: exp.Properties.Location.UNSUPPORTED,
 778        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 779        exp.Property: exp.Properties.Location.POST_WITH,
 780        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 781        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 782        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 783        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 784        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 785        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 786        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 787        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 788        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 789        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 790        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 791        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 792        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 793        exp.Set: exp.Properties.Location.POST_SCHEMA,
 794        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 795        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 796        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 797        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 798        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 799        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 800        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 801        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 802        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 803        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 804        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 805        exp.StorageProperty: exp.Properties.Location.UNSUPPORTED,
 806        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 807        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 808        exp.Tags: exp.Properties.Location.POST_WITH,
 809        exp.TableSpaceProperty: exp.Properties.Location.UNSUPPORTED,
 810        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 811        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 812        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 813        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 814        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 815        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 816        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 817        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 818        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 819        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 820        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 821        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 822        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 823        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 824        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 825        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 826        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 827    }
 828
 829    # Keywords that can't be used as unquoted identifier names
 830    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 831
 832    # Exprs whose comments are separated from them for better formatting
 833    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 834        exp.Command,
 835        exp.Create,
 836        exp.Describe,
 837        exp.Delete,
 838        exp.Drop,
 839        exp.From,
 840        exp.Insert,
 841        exp.Join,
 842        exp.MultitableInserts,
 843        exp.Order,
 844        exp.Group,
 845        exp.Having,
 846        exp.Select,
 847        exp.SetOperation,
 848        exp.Update,
 849        exp.Where,
 850        exp.With,
 851    )
 852
 853    # Exprs that should not have their comments generated in maybe_comment
 854    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 855        exp.Binary,
 856        exp.SetOperation,
 857    )
 858
 859    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 860    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 861        exp.Column,
 862        exp.Literal,
 863        exp.Neg,
 864        exp.Paren,
 865    )
 866
 867    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 868        exp.DType.NVARCHAR,
 869        exp.DType.VARCHAR,
 870        exp.DType.CHAR,
 871        exp.DType.NCHAR,
 872    }
 873
 874    # Exprs that need to have all CTEs under them bubbled up to them
 875    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 876
 877    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 878
 879    MOD_OPERATOR = "%"
 880
 881    # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses
 882    MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 883        exp.Mul,
 884        exp.Div,
 885        exp.IntDiv,
 886        exp.Mod,
 887    )
 888
 889    # ALTER options that follow the others without a comma, e.g. MySQL's
 890    # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ...
 891    ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 892
 893    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 894
 895    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 896
 897    __slots__ = (
 898        "pretty",
 899        "identify",
 900        "normalize",
 901        "pad",
 902        "_indent",
 903        "normalize_functions",
 904        "unsupported_level",
 905        "max_unsupported",
 906        "leading_comma",
 907        "max_text_width",
 908        "comments",
 909        "dialect",
 910        "unsupported_messages",
 911        "_escaped_quote_end",
 912        "_escaped_byte_quote_end",
 913        "_escaped_identifier_end",
 914        "_identifier_escaped_sequences",
 915        "_next_name",
 916        "_identifier_start",
 917        "_identifier_end",
 918        "_quote_json_path_key_using_brackets",
 919        "_dispatch",
 920    )
 921
 922    def __init__(
 923        self,
 924        pretty: bool | int | None = None,
 925        identify: str | bool = False,
 926        normalize: bool = False,
 927        pad: int = 2,
 928        indent: int = 2,
 929        normalize_functions: str | bool | None = None,
 930        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 931        max_unsupported: int = 3,
 932        leading_comma: bool = False,
 933        max_text_width: int = 80,
 934        comments: bool = True,
 935        dialect: DialectType = None,
 936    ):
 937        import sqlglot
 938        import sqlglot.dialects.dialect
 939
 940        self.pretty = pretty if pretty is not None else sqlglot.pretty
 941        self.identify = identify
 942        self.normalize = normalize
 943        self.pad = pad
 944        self._indent = indent
 945        self.unsupported_level = unsupported_level
 946        self.max_unsupported = max_unsupported
 947        self.leading_comma = leading_comma
 948        self.max_text_width = max_text_width
 949        self.comments = comments
 950        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 951
 952        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 953        self.normalize_functions = (
 954            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 955        )
 956
 957        self.unsupported_messages: list[str] = []
 958        self._escaped_quote_end: str = (
 959            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 960        )
 961        self._escaped_byte_quote_end: str = (
 962            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 963            if self.dialect.BYTE_END
 964            else ""
 965        )
 966        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 967        self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES
 968
 969        self._next_name = name_sequence("_t")
 970
 971        self._identifier_start = self.dialect.IDENTIFIER_START
 972        self._identifier_end = self.dialect.IDENTIFIER_END
 973
 974        self._quote_json_path_key_using_brackets = True
 975
 976        cls = type(self)
 977        dispatch = _DISPATCH_CACHE.get(cls)
 978        if dispatch is None:
 979            dispatch = _build_dispatch(cls)
 980            _DISPATCH_CACHE[cls] = dispatch
 981        self._dispatch = dispatch
 982
 983    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 984        """
 985        Generates the SQL string corresponding to the given syntax tree.
 986
 987        Args:
 988            expression: The syntax tree.
 989            copy: Whether to copy the expression. The generator performs mutations so
 990                it is safer to copy.
 991
 992        Returns:
 993            The SQL string corresponding to `expression`.
 994        """
 995        if copy:
 996            expression = expression.copy()
 997
 998        expression = self.preprocess(expression)
 999
1000        self.unsupported_messages = []
1001        sql = self.sql(expression).strip()
1002
1003        if self.pretty:
1004            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
1005
1006        if self.unsupported_level == ErrorLevel.IGNORE:
1007            return sql
1008
1009        if self.unsupported_level == ErrorLevel.WARN:
1010            for msg in self.unsupported_messages:
1011                logger.warning(msg)
1012        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
1013            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
1014
1015        return sql
1016
1017    def preprocess(self, expression: exp.Expr) -> exp.Expr:
1018        """Apply generic preprocessing transformations to a given expression."""
1019        expression = self._move_ctes_to_top_level(expression)
1020
1021        if self.ENSURE_BOOLS:
1022            import sqlglot.transforms
1023
1024            expression = sqlglot.transforms.ensure_bools(expression)
1025
1026        return expression
1027
1028    def _move_ctes_to_top_level(self, expression: E) -> E:
1029        if (
1030            not expression.parent
1031            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
1032            and any(node.parent is not expression for node in expression.find_all(exp.With))
1033        ):
1034            import sqlglot.transforms
1035
1036            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
1037        return expression
1038
1039    def unsupported(self, message: str) -> None:
1040        if self.unsupported_level == ErrorLevel.IMMEDIATE:
1041            raise UnsupportedError(message)
1042        self.unsupported_messages.append(message)
1043
1044    def sep(self, sep: str = " ") -> str:
1045        return f"{sep.strip()}\n" if self.pretty else sep
1046
1047    def seg(self, sql: str, sep: str = " ") -> str:
1048        return f"{self.sep(sep)}{sql}"
1049
1050    def sanitize_comment(self, comment: str) -> str:
1051        comment = " " + comment if comment[0].strip() else comment
1052        comment = comment + " " if comment[-1].strip() else comment
1053
1054        # Escape block comment markers to prevent premature closure or unintended nesting.
1055        # This is necessary because single-line comments (--) are converted to block comments
1056        # (/* */) on output, and any */ in the original text would close the comment early.
1057        comment = comment.replace("*/", "* /").replace("/*", "/ *")
1058
1059        return comment
1060
1061    def maybe_comment(
1062        self,
1063        sql: str,
1064        expression: exp.Expr | None = None,
1065        comments: list[str] | None = None,
1066        separated: bool = False,
1067    ) -> str:
1068        comments = (
1069            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1070            if self.comments
1071            else None
1072        )
1073
1074        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1075            return sql
1076
1077        comments_list = [
1078            f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/"
1079            for comment in comments
1080            if comment
1081        ]
1082
1083        if not comments_list:
1084            return sql
1085
1086        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1087            comments_sql = self.sep().join(comments_list)
1088            return (
1089                f"{self.sep()}{comments_sql}{sql}"
1090                if not sql or sql[0].isspace()
1091                else f"{comments_sql}{self.sep()}{sql}"
1092            )
1093
1094        return f"{sql} {' '.join(comments_list)}"
1095
1096    def wrap(self, expression: exp.Expr | str) -> str:
1097        this_sql = (
1098            self.sql(expression)
1099            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1100            else self.sql(expression, "this")
1101        )
1102        if not this_sql:
1103            return "()"
1104
1105        this_sql = self.indent(this_sql, level=1, pad=0)
1106        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1107
1108    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1109        original = self.identify
1110        self.identify = False
1111        result = func(*args, **kwargs)
1112        self.identify = original
1113        return result
1114
1115    def normalize_func(self, name: str) -> str:
1116        if self.normalize_functions == "upper" or self.normalize_functions is True:
1117            return name.upper()
1118        if self.normalize_functions == "lower":
1119            return name.lower()
1120        return name
1121
1122    def indent(
1123        self,
1124        sql: str,
1125        level: int = 0,
1126        pad: int | None = None,
1127        skip_first: bool = False,
1128        skip_last: bool = False,
1129    ) -> str:
1130        if not self.pretty or not sql:
1131            return sql
1132
1133        pad = self.pad if pad is None else pad
1134        lines = sql.split("\n")
1135
1136        return "\n".join(
1137            (
1138                line
1139                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1140                else f"{' ' * (level * self._indent + pad)}{line}"
1141            )
1142            for i, line in enumerate(lines)
1143        )
1144
1145    def sql(
1146        self,
1147        expression: str | exp.Expr | None,
1148        key: str | None = None,
1149        comment: bool = True,
1150    ) -> str:
1151        if not expression:
1152            return ""
1153
1154        if isinstance(expression, str):
1155            return expression
1156
1157        if key:
1158            value = expression.args.get(key)
1159            if value:
1160                return self.sql(value)
1161            return ""
1162
1163        handler = self._dispatch.get(expression.__class__)
1164
1165        if handler:
1166            sql = handler(self, expression)
1167        elif isinstance(expression, exp.Func):
1168            sql = self.function_fallback_sql(expression)
1169        elif isinstance(expression, exp.Property):
1170            sql = self.property_sql(expression)
1171        else:
1172            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1173
1174        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1175
1176    def uncache_sql(self, expression: exp.Uncache) -> str:
1177        table = self.sql(expression, "this")
1178        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1179        return f"UNCACHE TABLE{exists_sql} {table}"
1180
1181    def cache_sql(self, expression: exp.Cache) -> str:
1182        lazy = " LAZY" if expression.args.get("lazy") else ""
1183        table = self.sql(expression, "this")
1184        options = expression.args.get("options")
1185        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1186        sql = self.sql(expression, "expression")
1187        sql = f" AS{self.sep()}{sql}" if sql else ""
1188        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1189        return self.prepend_ctes(expression, sql)
1190
1191    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1192        default = "DEFAULT " if expression.args.get("default") else ""
1193        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1194
1195    def column_parts(self, expression: exp.Column) -> str:
1196        if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES:
1197            # The qualifier would be captured by a colliding projection alias (see qualify_columns)
1198            return self.sql(expression, "this")
1199
1200        return ".".join(
1201            self.sql(part)
1202            for part in (
1203                expression.args.get("catalog"),
1204                expression.args.get("db"),
1205                expression.args.get("table"),
1206                expression.args.get("this"),
1207            )
1208            if part
1209        )
1210
1211    def column_sql(self, expression: exp.Column) -> str:
1212        join_mark = " (+)" if expression.args.get("join_mark") else ""
1213
1214        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1215            join_mark = ""
1216            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1217
1218        return f"{self.column_parts(expression)}{join_mark}"
1219
1220    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1221        return self.column_sql(expression)
1222
1223    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1224        this = self.sql(expression, "this")
1225        this = f" {this}" if this else ""
1226        position = self.sql(expression, "position")
1227        return f"{position}{this}"
1228
1229    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1230        column = self.sql(expression, "this")
1231        kind = self.sql(expression, "kind")
1232        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1233        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1234        kind = f"{sep}{kind}" if kind else ""
1235        constraints = f" {constraints}" if constraints else ""
1236        position = self.sql(expression, "position")
1237        position = f" {position}" if position else ""
1238
1239        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1240            kind = ""
1241
1242        return f"{exists}{column}{kind}{constraints}{position}"
1243
1244    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1245        this = self.sql(expression, "this")
1246        kind_sql = self.sql(expression, "kind").strip()
1247        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1248
1249    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1250        this = self.sql(expression, "this")
1251        if expression.args.get("not_null"):
1252            persisted = " PERSISTED NOT NULL"
1253        elif expression.args.get("persisted"):
1254            persisted = " PERSISTED"
1255        else:
1256            persisted = ""
1257
1258        return f"AS {this}{persisted}"
1259
1260    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1261        return self.token_sql(TokenType.AUTO_INCREMENT)
1262
1263    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1264        if isinstance(expression.this, list):
1265            this = self.wrap(self.expressions(expression, key="this", flat=True))
1266        else:
1267            this = self.sql(expression, "this")
1268
1269        return f"COMPRESS {this}"
1270
1271    def generatedasidentitycolumnconstraint_sql(
1272        self, expression: exp.GeneratedAsIdentityColumnConstraint
1273    ) -> str:
1274        this = ""
1275        if expression.this is not None:
1276            on_null = " ON NULL" if expression.args.get("on_null") else ""
1277            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1278
1279        start = expression.args.get("start")
1280        start = f"START WITH {start}" if start else ""
1281        increment = expression.args.get("increment")
1282        increment = f" INCREMENT BY {increment}" if increment else ""
1283        minvalue = expression.args.get("minvalue")
1284        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1285        maxvalue = expression.args.get("maxvalue")
1286        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1287        cycle = expression.args.get("cycle")
1288        cycle_sql = ""
1289
1290        if cycle is not None:
1291            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1292            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1293
1294        sequence_opts = ""
1295        if start or increment or cycle_sql:
1296            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1297            sequence_opts = f" ({sequence_opts.strip()})"
1298
1299        expr = self.sql(expression, "expression")
1300        expr = f"({expr})" if expr else "IDENTITY"
1301
1302        return f"GENERATED{this} AS {expr}{sequence_opts}"
1303
1304    def generatedasrowcolumnconstraint_sql(
1305        self, expression: exp.GeneratedAsRowColumnConstraint
1306    ) -> str:
1307        start = "START" if expression.args.get("start") else "END"
1308        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1309        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1310
1311    def periodforsystemtimeconstraint_sql(
1312        self, expression: exp.PeriodForSystemTimeConstraint
1313    ) -> str:
1314        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1315
1316    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1317        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1318
1319    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1320        desc = expression.args.get("desc")
1321        if desc is not None:
1322            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1323        options = self.expressions(expression, key="options", flat=True, sep=" ")
1324        options = f" {options}" if options else ""
1325        return f"PRIMARY KEY{options}"
1326
1327    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1328        this = self.sql(expression, "this")
1329        this = f" {this}" if this else ""
1330        index_type = expression.args.get("index_type")
1331        index_type = f" USING {index_type}" if index_type else ""
1332        on_conflict = self.sql(expression, "on_conflict")
1333        on_conflict = f" {on_conflict}" if on_conflict else ""
1334        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1335        options = self.expressions(expression, key="options", flat=True, sep=" ")
1336        options = f" {options}" if options else ""
1337        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1338
1339    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1340        input_ = expression.args.get("input_")
1341        output = expression.args.get("output")
1342        variadic = expression.args.get("variadic")
1343
1344        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1345        if variadic:
1346            return "VARIADIC"
1347
1348        if input_ and output:
1349            return f"IN{self.INOUT_SEPARATOR}OUT"
1350        if input_:
1351            return "IN"
1352        if output:
1353            return "OUT"
1354
1355        return ""
1356
1357    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1358        return self.sql(expression, "this")
1359
1360    def create_sql(self, expression: exp.Create) -> str:
1361        kind = self.sql(expression, "kind")
1362        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1363
1364        properties = expression.args.get("properties")
1365
1366        if (
1367            kind == "TRIGGER"
1368            and properties
1369            and properties.expressions
1370            and isinstance(properties.expressions[0], exp.TriggerProperties)
1371            and properties.expressions[0].args.get("constraint")
1372        ):
1373            kind = f"CONSTRAINT {kind}"
1374
1375        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1376
1377        this = self.createable_sql(expression, properties_locs)
1378
1379        properties_sql = ""
1380        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1381            exp.Properties.Location.POST_WITH
1382        ):
1383            props_ast = exp.Properties(
1384                expressions=[
1385                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1386                    *properties_locs[exp.Properties.Location.POST_WITH],
1387                ]
1388            )
1389            props_ast.parent = expression
1390            properties_sql = self.sql(props_ast)
1391
1392            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1393                properties_sql = self.sep() + properties_sql
1394            elif not self.pretty:
1395                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1396                properties_sql = f" {properties_sql}"
1397
1398        begin = " BEGIN" if expression.args.get("begin") else ""
1399
1400        expression_sql = self.sql(expression, "expression")
1401        if expression_sql:
1402            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1403
1404            if not isinstance(expression.expression, exp.MacroOverloads) and (
1405                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1406            ):
1407                postalias_props_sql = ""
1408                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1409                    postalias_props_sql = self.properties(
1410                        exp.Properties(
1411                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1412                        ),
1413                        wrapped=False,
1414                    )
1415                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1416                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1417
1418        postindex_props_sql = ""
1419        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1420            postindex_props_sql = self.properties(
1421                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1422                wrapped=False,
1423                prefix=" ",
1424            )
1425
1426        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1427        indexes = f" {indexes}" if indexes else ""
1428        index_sql = indexes + postindex_props_sql
1429
1430        replace = " OR REPLACE" if expression.args.get("replace") else ""
1431        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1432        unique = " UNIQUE" if expression.args.get("unique") else ""
1433
1434        clustered = expression.args.get("clustered")
1435        if clustered is None:
1436            clustered_sql = ""
1437        elif clustered:
1438            clustered_sql = " CLUSTERED COLUMNSTORE"
1439        else:
1440            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1441
1442        postcreate_props_sql = ""
1443        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1444            postcreate_props_sql = self.properties(
1445                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1446                sep=" ",
1447                prefix=" ",
1448                wrapped=False,
1449            )
1450
1451        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1452
1453        postexpression_props_sql = ""
1454        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1455            postexpression_props_sql = self.properties(
1456                exp.Properties(
1457                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1458                ),
1459                sep=" ",
1460                prefix=" ",
1461                wrapped=False,
1462            )
1463
1464        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1465        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1466        no_schema_binding = (
1467            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1468        )
1469
1470        clone = self.sql(expression, "clone")
1471        clone = f" {clone}" if clone else ""
1472
1473        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1474            properties_expression = f"{expression_sql}{properties_sql}"
1475        else:
1476            properties_expression = f"{properties_sql}{expression_sql}"
1477
1478        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1479        return self.prepend_ctes(expression, expression_sql)
1480
1481    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1482        start = self.sql(expression, "start")
1483        start = f"START WITH {start}" if start else ""
1484        increment = self.sql(expression, "increment")
1485        increment = f" INCREMENT BY {increment}" if increment else ""
1486        minvalue = self.sql(expression, "minvalue")
1487        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1488        maxvalue = self.sql(expression, "maxvalue")
1489        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1490        owned = self.sql(expression, "owned")
1491        owned = f" OWNED BY {owned}" if owned else ""
1492
1493        cache = expression.args.get("cache")
1494        if cache is None:
1495            cache_str = ""
1496        elif cache is True:
1497            cache_str = " CACHE"
1498        else:
1499            cache_str = f" CACHE {cache}"
1500
1501        options = self.expressions(expression, key="options", flat=True, sep=" ")
1502        options = f" {options}" if options else ""
1503
1504        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1505
1506    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1507        timing = expression.args.get("timing", "")
1508        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1509        timing_events = f"{timing} {events}".strip() if timing or events else ""
1510
1511        parts = [timing_events, "ON", self.sql(expression, "table")]
1512
1513        if referenced_table := expression.args.get("referenced_table"):
1514            parts.extend(["FROM", self.sql(referenced_table)])
1515
1516        if deferrable := expression.args.get("deferrable"):
1517            parts.append(deferrable)
1518
1519        if initially := expression.args.get("initially"):
1520            parts.append(f"INITIALLY {initially}")
1521
1522        if referencing := expression.args.get("referencing"):
1523            parts.append(self.sql(referencing))
1524
1525        if for_each := expression.args.get("for_each"):
1526            parts.append(f"FOR EACH {for_each}")
1527
1528        if when := expression.args.get("when"):
1529            parts.append(f"WHEN ({self.sql(when)})")
1530
1531        parts.append(self.sql(expression, "execute"))
1532
1533        return self.sep().join(parts)
1534
1535    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1536        parts = []
1537
1538        if old_alias := expression.args.get("old"):
1539            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1540
1541        if new_alias := expression.args.get("new"):
1542            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1543
1544        return f"REFERENCING {' '.join(parts)}"
1545
1546    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1547        columns = expression.args.get("columns")
1548        if columns:
1549            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1550
1551        return self.sql(expression, "this")
1552
1553    def clone_sql(self, expression: exp.Clone) -> str:
1554        this = self.sql(expression, "this")
1555        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1556        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1557        return f"{shallow}{keyword} {this}"
1558
1559    def describe_sql(self, expression: exp.Describe) -> str:
1560        style = expression.args.get("style")
1561        style = f" {style}" if style else ""
1562        partition = self.sql(expression, "partition")
1563        partition = f" {partition}" if partition else ""
1564        format = self.sql(expression, "format")
1565        format = f" {format}" if format else ""
1566        as_json = " AS JSON" if expression.args.get("as_json") else ""
1567
1568        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1569
1570    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1571        tag = self.sql(expression, "tag")
1572        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1573
1574    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1575        with_ = self.sql(expression, "with_")
1576        if with_:
1577            sql = f"{with_}{self.sep()}{sql}"
1578        return sql
1579
1580    def with_sql(self, expression: exp.With) -> str:
1581        udfs = self.expressions(expression, key="udfs", flat=True)
1582        udfs = f"WITH {udfs}" if udfs else ""
1583
1584        sql = self.expressions(expression, flat=True)
1585
1586        recursive = (
1587            "RECURSIVE "
1588            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1589            else ""
1590        )
1591        search = self.sql(expression, "search")
1592        search = f" {search}" if search else ""
1593        cycle = self.sql(expression, "cycle")
1594        cycle = f" {cycle}" if cycle else ""
1595
1596        sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else ""
1597        return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1598
1599    def cte_sql(self, expression: exp.CTE) -> str:
1600        alias = expression.args.get("alias")
1601        if alias:
1602            alias.add_comments(expression.pop_comments())
1603
1604        alias_sql = self.sql(expression, "alias")
1605
1606        materialized = expression.args.get("materialized")
1607        if materialized is False:
1608            materialized = "NOT MATERIALIZED "
1609        elif materialized:
1610            materialized = "MATERIALIZED "
1611
1612        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1613        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1614
1615        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1616
1617    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1618        alias = self.sql(expression, "this")
1619        columns = self.expressions(expression, key="columns", flat=True)
1620        columns = f"({columns})" if columns else ""
1621
1622        if (
1623            columns
1624            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1625            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1626        ):
1627            columns = ""
1628            self.unsupported("Named columns are not supported in table alias.")
1629
1630        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1631            alias = self._next_name()
1632
1633        return f"{alias}{columns}"
1634
1635    def bitstring_sql(self, expression: exp.BitString) -> str:
1636        this = self.sql(expression, "this")
1637        if self.dialect.BIT_START:
1638            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1639        return f"{int(this, 2)}"
1640
1641    def hexstring_sql(
1642        self, expression: exp.HexString, binary_function_repr: str | None = None
1643    ) -> str:
1644        this = self.sql(expression, "this")
1645        is_integer_type = expression.args.get("is_integer")
1646
1647        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1648            not self.dialect.HEX_START and not binary_function_repr
1649        ):
1650            # Integer representation will be returned if:
1651            # - The read dialect treats the hex value as integer literal but not the write
1652            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1653            return f"{int(this, 16)}"
1654
1655        if not is_integer_type:
1656            # Read dialect treats the hex value as BINARY/BLOB
1657            if binary_function_repr:
1658                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1659                return self.func(binary_function_repr, exp.Literal.string(this))
1660            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1661                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1662                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1663
1664        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1665
1666    def bytestring_sql(self, expression: exp.ByteString) -> str:
1667        this = self.sql(expression, "this")
1668        is_bytes = bool(expression.args.get("is_bytes"))
1669        if is_bytes and self.BYTE_STRING_FUNCTION:
1670            # Hex digits can be quoted directly without escaping.
1671            return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'")
1672
1673        if self.dialect.BYTE_START:
1674            escaped_byte_string = self.escape_str(
1675                this,
1676                escape_backslash=bool(
1677                    self.dialect.tokenizer_class.NUMERIC_ESCAPES
1678                    or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES
1679                ),
1680                delimiter=self.dialect.BYTE_END,
1681                escaped_delimiter=self._escaped_byte_quote_end,
1682                is_byte_string=True,
1683                is_bytes=is_bytes,
1684            )
1685            delimited_byte_string = (
1686                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1687            )
1688            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1689                return self.sql(
1690                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1691                )
1692            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1693                return self.sql(
1694                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1695                )
1696
1697            return delimited_byte_string
1698
1699        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1700            return self.sql(exp.Literal.string(this))
1701
1702        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1703        return ""
1704
1705    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1706        this = self.sql(expression, "this")
1707        escape = expression.args.get("escape")
1708        unicode_start = self.dialect.UNICODE_START
1709
1710        if unicode_start:
1711            escape_substitute = r"\\\1"
1712            left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or ""
1713        else:
1714            escape_substitute = r"\\u\1"
1715            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1716
1717        if escape:
1718            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1719            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1720        else:
1721            escape_pattern = ESCAPED_UNICODE_RE
1722            escape_sql = ""
1723
1724        if not unicode_start or (escape and not self.SUPPORTS_UESCAPE):
1725            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1726
1727        if unicode_start:
1728            # A Unicode literal only escapes its delimiter by doubling it; the escape character
1729            # introduces a code point, so the dialect's ordinary string escapes don't apply here
1730            this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2)
1731        else:
1732            this = self.escape_str(this, escape_backslash=False)
1733
1734        return f"{left_quote}{this}{right_quote}{escape_sql}"
1735
1736    def rawstring_sql(self, expression: exp.RawString) -> str:
1737        string = expression.this
1738        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1739            string = string.replace("\\", "\\\\")
1740
1741        string = self.escape_str(string, escape_backslash=False)
1742        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1743
1744    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1745        this = self.sql(expression, "this")
1746        specifier = self.sql(expression, "expression")
1747        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1748        return f"{this}{specifier}"
1749
1750    def datatype_param_bound_limiter(
1751        self,
1752        expression: exp.DataType,
1753        type_value: exp.DType,
1754        defaults: tuple[int, ...],
1755        bounds: tuple[int | None, ...],
1756    ) -> exp.DataType:
1757        params = expression.expressions
1758
1759        if not params:
1760            if defaults:
1761                expression.set(
1762                    "expressions",
1763                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1764                )
1765            return expression
1766
1767        if not bounds:
1768            return expression
1769
1770        for i, param in enumerate(params):
1771            bound = bounds[i] if i < len(bounds) else None
1772            if bound is None:
1773                continue
1774
1775            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1776            value = (
1777                param_value.to_py()
1778                if isinstance(param_value, exp.Literal) and param_value.is_number
1779                else None
1780            )
1781            if isinstance(value, (int, Decimal)) and value > bound:
1782                self.unsupported(
1783                    f"{type_value.value} parameter {param_value.name} exceeds "
1784                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1785                )
1786                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1787
1788        return expression
1789
1790    def datatype_sql(self, expression: exp.DataType) -> str:
1791        nested = ""
1792        values = ""
1793
1794        expr_nested = expression.args.get("nested")
1795        type_value = expression.this
1796
1797        if (
1798            not expr_nested
1799            and isinstance(type_value, exp.DType)
1800            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1801        ):
1802            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1803
1804        interior = (
1805            self.expressions(
1806                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1807            )
1808            if expr_nested and self.pretty
1809            else self.expressions(expression, flat=True)
1810        )
1811
1812        if type_value in self.UNSUPPORTED_TYPES:
1813            self.unsupported(
1814                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1815            )
1816
1817        type_sql: t.Any = ""
1818        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1819            type_sql = self.sql(expression, "kind")
1820        elif type_value == exp.DType.CHARACTER_SET:
1821            char = f"CHAR({interior})" if interior else "CHAR"
1822            return f"{char} CHARACTER SET {self.sql(expression, 'kind')}"
1823        else:
1824            type_sql = (
1825                self.TYPE_MAPPING.get(type_value, type_value.value)
1826                if isinstance(type_value, exp.DType)
1827                else type_value
1828            )
1829
1830        if interior:
1831            if expr_nested:
1832                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1833                if expression.args.get("values") is not None:
1834                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1835                    values = self.expressions(expression, key="values", flat=True)
1836                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1837            elif type_value == exp.DType.INTERVAL:
1838                nested = f" {interior}"
1839            else:
1840                nested = f"({interior})"
1841
1842        type_sql = f"{type_sql}{nested}{values}"
1843        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1844            exp.DType.TIMETZ,
1845            exp.DType.TIMESTAMPTZ,
1846        ):
1847            type_sql = f"{type_sql} WITH TIME ZONE"
1848
1849        collate = self.sql(expression, "collate")
1850        if collate:
1851            type_sql = f"{type_sql} COLLATE {collate}"
1852
1853        return type_sql
1854
1855    def directory_sql(self, expression: exp.Directory) -> str:
1856        local = "LOCAL " if expression.args.get("local") else ""
1857        row_format = self.sql(expression, "row_format")
1858        row_format = f" {row_format}" if row_format else ""
1859        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1860
1861    def delete_sql(self, expression: exp.Delete) -> str:
1862        hint = self.sql(expression, "hint")
1863        this = self.sql(expression, "this")
1864        this = f" FROM {this}" if this else ""
1865        using = self.expressions(expression, key="using")
1866        using = f" USING {using}" if using else ""
1867        cluster = self.sql(expression, "cluster")
1868        cluster = f" {cluster}" if cluster else ""
1869        where = self.sql(expression, "where")
1870        returning = self.sql(expression, "returning")
1871        order = self.sql(expression, "order")
1872        limit = self.sql(expression, "limit")
1873        tables = self.expressions(expression, key="tables")
1874        tables = f" {tables}" if tables else ""
1875        if self.RETURNING_END:
1876            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1877        else:
1878            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1879        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1880
1881    def drop_sql(self, expression: exp.Drop) -> str:
1882        tables = self.expressions(expression, key="tables", flat=True)
1883        expressions = self.expressions(expression, flat=True)
1884        expressions = f" ({expressions})" if expressions else ""
1885        kind = expression.args["kind"]
1886        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1887        iceberg = (
1888            " ICEBERG"
1889            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1890            else ""
1891        )
1892        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1893        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1894        on_cluster = self.sql(expression, "cluster")
1895        on_cluster = f" {on_cluster}" if on_cluster else ""
1896        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1897        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1898        cascade = " CASCADE" if expression.args.get("cascade") else ""
1899        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1900        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1901        purge = " PURGE" if expression.args.get("purge") else ""
1902        sync = " SYNC" if expression.args.get("sync") else ""
1903        force = " FORCE" if expression.args.get("force") else ""
1904        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1905
1906    def set_operation(self, expression: exp.SetOperation) -> str:
1907        op_type = type(expression)
1908        op_name = op_type.key.upper()
1909
1910        distinct = expression.args.get("distinct")
1911        if (
1912            distinct is False
1913            and op_type in (exp.Except, exp.Intersect)
1914            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1915        ):
1916            self.unsupported(f"{op_name} ALL is not supported")
1917
1918        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1919
1920        if distinct is None:
1921            distinct = default_distinct
1922            if distinct is None:
1923                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1924
1925        if distinct is default_distinct:
1926            distinct_or_all = ""
1927        else:
1928            distinct_or_all = " DISTINCT" if distinct else " ALL"
1929
1930        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1931        side_kind = f"{side_kind} " if side_kind else ""
1932
1933        by_name = " BY NAME" if expression.args.get("by_name") else ""
1934        on = self.expressions(expression, key="on", flat=True)
1935        on = f" ON ({on})" if on else ""
1936
1937        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1938
1939    def set_operations(self, expression: exp.SetOperation) -> str:
1940        if not self.SET_OP_MODIFIERS:
1941            limit = expression.args.get("limit")
1942            order = expression.args.get("order")
1943            offset = expression.args.get("offset")
1944
1945            if limit or order or offset:
1946                select = self._move_ctes_to_top_level(
1947                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1948                )
1949
1950                for arg in ("limit", "order", "offset"):
1951                    if value := expression.args.get(arg):
1952                        select.set(arg, value.pop())
1953                return self.sql(select)
1954
1955        sqls: list[str] = []
1956        stack: list[str | exp.Expr] = [expression]
1957
1958        while stack:
1959            node = stack.pop()
1960
1961            if isinstance(node, exp.SetOperation):
1962                stack.append(node.expression)
1963                stack.append(
1964                    self.maybe_comment(
1965                        self.set_operation(node), comments=node.comments, separated=True
1966                    )
1967                )
1968                stack.append(node.this)
1969            else:
1970                if (
1971                    not self.SET_OP_LIMITS
1972                    and isinstance(node, exp.Select)
1973                    and node.args.get("limit")
1974                ):
1975                    node = node.subquery(copy=False)
1976                    if not self.SET_OP_PARENTHESIZED_OPERANDS:
1977                        node = exp.select("*").from_(node, copy=False)
1978                sqls.append(self.sql(node))
1979
1980        this = self.sep().join(sqls)
1981        this = self.query_modifiers(expression, this)
1982        return self.prepend_ctes(expression, this)
1983
1984    def fetch_sql(self, expression: exp.Fetch) -> str:
1985        direction = expression.args.get("direction")
1986        direction = f" {direction}" if direction else ""
1987        count = self.sql(expression, "count")
1988        count = f" {count}" if count else ""
1989        limit_options = self.sql(expression, "limit_options")
1990        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1991        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1992
1993    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1994        percent = " PERCENT" if expression.args.get("percent") else ""
1995        rows = " ROWS" if expression.args.get("rows") else ""
1996        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1997        if not with_ties and rows:
1998            with_ties = " ONLY"
1999        return f"{percent}{rows}{with_ties}"
2000
2001    def filter_sql(self, expression: exp.Filter) -> str:
2002        this = self.sql(expression, "this")
2003        where = self.sql(expression, "expression").strip()
2004        return f"{this} FILTER({where})"
2005
2006    def hint_sql(self, expression: exp.Hint) -> str:
2007        if not self.QUERY_HINTS:
2008            self.unsupported("Hints are not supported")
2009            return ""
2010
2011        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
2012
2013    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
2014        using = self.sql(expression, "using")
2015        using = f" USING {using}" if using else ""
2016        columns = self.expressions(expression, key="columns", flat=True)
2017        columns = f"({columns})" if columns else ""
2018        partition_by = self.expressions(expression, key="partition_by", flat=True)
2019        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
2020        where = self.sql(expression, "where")
2021        include = self.expressions(expression, key="include", flat=True)
2022        if include:
2023            include = f" INCLUDE ({include})"
2024        with_storage = self.expressions(expression, key="with_storage", flat=True)
2025        with_storage = f" WITH ({with_storage})" if with_storage else ""
2026        tablespace = self.sql(expression, "tablespace")
2027        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
2028        on = self.sql(expression, "on")
2029        on = f" ON {on}" if on else ""
2030
2031        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
2032
2033    def index_sql(self, expression: exp.Index) -> str:
2034        unique = "UNIQUE " if expression.args.get("unique") else ""
2035        primary = "PRIMARY " if expression.args.get("primary") else ""
2036        amp = "AMP " if expression.args.get("amp") else ""
2037        name = self.sql(expression, "this")
2038        name = f"{name} " if name else ""
2039        table = self.sql(expression, "table")
2040        table = f"{self.INDEX_ON} {table}" if table else ""
2041
2042        index = "INDEX " if not table else ""
2043
2044        params = self.sql(expression, "params")
2045        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
2046
2047    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
2048        this = expression.this
2049        if this and this.is_string:
2050            resolved = maybe_parse(this.name).sql(self.dialect)
2051            if "expressions" in expression.args:
2052                # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
2053                # We can't safely emit the call to other dialects since name/arg semantics may differ
2054                self.unsupported(
2055                    "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported"
2056                )
2057            return resolved
2058        self.unsupported("IDENTIFIER() with non-literal arguments is not supported")
2059        return self.func("IDENTIFIER", this)
2060
2061    def identifier_sql(self, expression: exp.Identifier) -> str:
2062        text = expression.name
2063        lower = text.lower()
2064        quoted = expression.quoted
2065        text = lower if self.normalize and not quoted else text
2066        if self._identifier_escaped_sequences:
2067            for char, escaped in self._identifier_escaped_sequences.items():
2068                text = text.replace(char, escaped)
2069        text = text.replace(self._identifier_end, self._escaped_identifier_end)
2070        if (
2071            quoted
2072            or self.dialect.can_quote(expression, self.identify)
2073            or lower in self.RESERVED_KEYWORDS
2074            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
2075        ):
2076            text = (
2077                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
2078            )
2079        return text
2080
2081    def hex_sql(self, expression: exp.Hex) -> str:
2082        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2083        if self.dialect.HEX_LOWERCASE:
2084            text = self.func("LOWER", text)
2085
2086        return text
2087
2088    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
2089        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2090        if not self.dialect.HEX_LOWERCASE:
2091            text = self.func("LOWER", text)
2092        return text
2093
2094    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
2095        input_format = self.sql(expression, "input_format")
2096        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
2097        output_format = self.sql(expression, "output_format")
2098        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
2099        return self.sep().join((input_format, output_format))
2100
2101    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
2102        string = self.sql(exp.Literal.string(expression.name))
2103        return f"{prefix}{string}"
2104
2105    def partition_sql(self, expression: exp.Partition) -> str:
2106        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
2107        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
2108
2109    def properties_sql(self, expression: exp.Properties) -> str:
2110        root_properties = []
2111        with_properties = []
2112
2113        for p in expression.expressions:
2114            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2115            if p_loc == exp.Properties.Location.POST_WITH:
2116                with_properties.append(p)
2117            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2118                root_properties.append(p)
2119
2120        root_props_ast = exp.Properties(expressions=root_properties)
2121        root_props_ast.parent = expression.parent
2122
2123        with_props_ast = exp.Properties(expressions=with_properties)
2124        with_props_ast.parent = expression.parent
2125
2126        root_props = self.root_properties(root_props_ast)
2127        with_props = self.with_properties(with_props_ast)
2128
2129        if root_props and with_props and not self.pretty:
2130            with_props = " " + with_props
2131
2132        return root_props + with_props
2133
2134    def root_properties(self, properties: exp.Properties) -> str:
2135        if properties.expressions:
2136            return self.expressions(properties, indent=False, sep=" ")
2137        return ""
2138
2139    def properties(
2140        self,
2141        properties: exp.Properties,
2142        prefix: str = "",
2143        sep: str = ", ",
2144        suffix: str = "",
2145        wrapped: bool = True,
2146    ) -> str:
2147        if properties.expressions:
2148            expressions = self.expressions(properties, sep=sep, indent=False)
2149            if expressions:
2150                expressions = self.wrap(expressions) if wrapped else expressions
2151                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2152        return ""
2153
2154    def with_properties(self, properties: exp.Properties) -> str:
2155        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
2156
2157    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2158        properties_locs = defaultdict(list)
2159        for p in properties.expressions:
2160            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2161            if p_loc != exp.Properties.Location.UNSUPPORTED:
2162                properties_locs[p_loc].append(p)
2163            else:
2164                self.unsupported(f"Unsupported property {p.key}")
2165
2166        return properties_locs
2167
2168    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2169        if isinstance(expression.this, exp.Dot):
2170            return self.sql(expression, "this")
2171        return f"'{expression.name}'" if string_key else expression.name
2172
2173    def property_sql(self, expression: exp.Property) -> str:
2174        property_cls = expression.__class__
2175        if property_cls == exp.Property:
2176            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2177
2178        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2179        if not property_name:
2180            self.unsupported(f"Unsupported property {expression.key}")
2181
2182        return f"{property_name}={self.sql(expression, 'this')}"
2183
2184    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2185        return f"UUID {self.sql(expression, 'this')}"
2186
2187    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2188        if self.SUPPORTS_CREATE_TABLE_LIKE:
2189            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2190            options = f" {options}" if options else ""
2191
2192            like = f"LIKE {self.sql(expression, 'this')}{options}"
2193            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2194                like = f"({like})"
2195
2196            return like
2197
2198        if expression.expressions:
2199            self.unsupported("Transpilation of LIKE property options is unsupported")
2200
2201        select = exp.select("*").from_(expression.this).limit(0)
2202        return f"AS {self.sql(select)}"
2203
2204    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2205        no = "NO " if expression.args.get("no") else ""
2206        protection = " PROTECTION" if expression.args.get("protection") else ""
2207        return f"{no}FALLBACK{protection}"
2208
2209    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2210        no = "NO " if expression.args.get("no") else ""
2211        local = expression.args.get("local")
2212        local = f"{local} " if local else ""
2213        dual = "DUAL " if expression.args.get("dual") else ""
2214        before = "BEFORE " if expression.args.get("before") else ""
2215        after = "AFTER " if expression.args.get("after") else ""
2216        return f"{no}{local}{dual}{before}{after}JOURNAL"
2217
2218    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2219        freespace = self.sql(expression, "this")
2220        percent = " PERCENT" if expression.args.get("percent") else ""
2221        return f"FREESPACE={freespace}{percent}"
2222
2223    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2224        if expression.args.get("default"):
2225            property = "DEFAULT"
2226        elif expression.args.get("on"):
2227            property = "ON"
2228        else:
2229            property = "OFF"
2230        return f"CHECKSUM={property}"
2231
2232    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2233        if expression.args.get("no"):
2234            return "NO MERGEBLOCKRATIO"
2235        if expression.args.get("default"):
2236            return "DEFAULT MERGEBLOCKRATIO"
2237
2238        percent = " PERCENT" if expression.args.get("percent") else ""
2239        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2240
2241    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2242        expressions = self.expressions(expression, flat=True)
2243        expressions = f"({expressions})" if expressions else ""
2244        return f"USING {self.sql(expression, 'this')}{expressions}"
2245
2246    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2247        default = expression.args.get("default")
2248        minimum = expression.args.get("minimum")
2249        maximum = expression.args.get("maximum")
2250        if default or minimum or maximum:
2251            if default:
2252                prop = "DEFAULT"
2253            elif minimum:
2254                prop = "MINIMUM"
2255            else:
2256                prop = "MAXIMUM"
2257            return f"{prop} DATABLOCKSIZE"
2258        units = expression.args.get("units")
2259        units = f" {units}" if units else ""
2260        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2261
2262    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2263        autotemp = expression.args.get("autotemp")
2264        always = expression.args.get("always")
2265        default = expression.args.get("default")
2266        manual = expression.args.get("manual")
2267        never = expression.args.get("never")
2268
2269        if autotemp is not None:
2270            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2271        elif always:
2272            prop = "ALWAYS"
2273        elif default:
2274            prop = "DEFAULT"
2275        elif manual:
2276            prop = "MANUAL"
2277        elif never:
2278            prop = "NEVER"
2279        return f"BLOCKCOMPRESSION={prop}"
2280
2281    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2282        no = expression.args.get("no")
2283        no = " NO" if no else ""
2284        concurrent = expression.args.get("concurrent")
2285        concurrent = " CONCURRENT" if concurrent else ""
2286        target = self.sql(expression, "target")
2287        target = f" {target}" if target else ""
2288        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2289
2290    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2291        if isinstance(expression.this, list):
2292            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2293        if expression.this:
2294            modulus = self.sql(expression, "this")
2295            remainder = self.sql(expression, "expression")
2296            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2297
2298        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2299        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2300        return f"FROM ({from_expressions}) TO ({to_expressions})"
2301
2302    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2303        this = self.sql(expression, "this")
2304
2305        for_values_or_default = expression.expression
2306        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2307            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2308        else:
2309            for_values_or_default = " DEFAULT"
2310
2311        return f"PARTITION OF {this}{for_values_or_default}"
2312
2313    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2314        kind = expression.args.get("kind")
2315        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2316        for_or_in = expression.args.get("for_or_in")
2317        for_or_in = f" {for_or_in}" if for_or_in else ""
2318        lock_type = expression.args.get("lock_type")
2319        override = " OVERRIDE" if expression.args.get("override") else ""
2320        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2321
2322    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2323        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2324        statistics = expression.args.get("statistics")
2325        statistics_sql = ""
2326        if statistics is not None:
2327            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2328        return f"{data_sql}{statistics_sql}"
2329
2330    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2331        this = self.sql(expression, "this")
2332        this = f"HISTORY_TABLE={this}" if this else ""
2333        data_consistency: str | None = self.sql(expression, "data_consistency")
2334        data_consistency = (
2335            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2336        )
2337        retention_period: str | None = self.sql(expression, "retention_period")
2338        retention_period = (
2339            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2340        )
2341
2342        if this:
2343            on_sql = self.func("ON", this, data_consistency, retention_period)
2344        else:
2345            on_sql = "ON" if expression.args.get("on") else "OFF"
2346
2347        sql = f"SYSTEM_VERSIONING={on_sql}"
2348
2349        return f"WITH({sql})" if expression.args.get("with_") else sql
2350
2351    def insert_sql(self, expression: exp.Insert) -> str:
2352        hint = self.sql(expression, "hint")
2353        overwrite = expression.args.get("overwrite")
2354
2355        if isinstance(expression.this, exp.Directory):
2356            this = " OVERWRITE" if overwrite else " INTO"
2357        else:
2358            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2359
2360        stored = self.sql(expression, "stored")
2361        stored = f" {stored}" if stored else ""
2362        alternative = expression.args.get("alternative")
2363        alternative = f" OR {alternative}" if alternative else ""
2364        ignore = " IGNORE" if expression.args.get("ignore") else ""
2365        is_function = expression.args.get("is_function")
2366        if is_function:
2367            this = f"{this} FUNCTION"
2368        this = f"{this} {self.sql(expression, 'this')}"
2369
2370        exists = " IF EXISTS" if expression.args.get("exists") else ""
2371        where = self.sql(expression, "where")
2372        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2373        using = self.expressions(expression, key="using", flat=True)
2374        using = f"{self.sep()}REPLACE USING ({using})" if using else ""
2375        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2376        on_conflict = self.sql(expression, "conflict")
2377        on_conflict = f" {on_conflict}" if on_conflict else ""
2378        by_name = " BY NAME" if expression.args.get("by_name") else ""
2379        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2380        returning = self.sql(expression, "returning")
2381
2382        if self.RETURNING_END:
2383            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2384        else:
2385            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2386
2387        partition_by = self.sql(expression, "partition")
2388        partition_by = f" {partition_by}" if partition_by else ""
2389        settings = self.sql(expression, "settings")
2390        settings = f" {settings}" if settings else ""
2391
2392        source = self.sql(expression, "source")
2393        source = f"TABLE {source}" if source else ""
2394
2395        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}"
2396        return self.prepend_ctes(expression, sql)
2397
2398    def introducer_sql(self, expression: exp.Introducer) -> str:
2399        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2400
2401    def kill_sql(self, expression: exp.Kill) -> str:
2402        kind = self.sql(expression, "kind")
2403        kind = f" {kind}" if kind else ""
2404        this = self.sql(expression, "this")
2405        this = f" {this}" if this else ""
2406        return f"KILL{kind}{this}"
2407
2408    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2409        return expression.name
2410
2411    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2412        return expression.name
2413
2414    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2415        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2416
2417        constraint = self.sql(expression, "constraint")
2418        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2419
2420        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2421        if conflict_keys:
2422            conflict_keys = f"({conflict_keys})"
2423
2424        index_predicate = self.sql(expression, "index_predicate")
2425        conflict_keys = f"{conflict_keys}{index_predicate} "
2426
2427        action = self.sql(expression, "action")
2428
2429        expressions = self.expressions(expression, flat=True)
2430        if expressions:
2431            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2432            expressions = f" {set_keyword}{expressions}"
2433
2434        where = self.sql(expression, "where")
2435        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2436
2437    def returning_sql(self, expression: exp.Returning) -> str:
2438        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2439
2440    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2441        fields = self.sql(expression, "fields")
2442        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2443        escaped = self.sql(expression, "escaped")
2444        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2445        items = self.sql(expression, "collection_items")
2446        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2447        keys = self.sql(expression, "map_keys")
2448        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2449        lines = self.sql(expression, "lines")
2450        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2451        null = self.sql(expression, "null")
2452        null = f" NULL DEFINED AS {null}" if null else ""
2453        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2454
2455    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2456        return f"WITH ({self.expressions(expression, flat=True)})"
2457
2458    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2459        this = f"{self.sql(expression, 'this')} INDEX"
2460        target = self.sql(expression, "target")
2461        target = f" FOR {target}" if target else ""
2462        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2463
2464    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2465        this = self.sql(expression, "this")
2466        kind = self.sql(expression, "kind")
2467        expr = self.sql(expression, "expression")
2468        return f"{this} ({kind} => {expr})"
2469
2470    def table_parts(self, expression: exp.Table) -> str:
2471        return ".".join(
2472            self.sql(part)
2473            for part in (
2474                expression.args.get("catalog"),
2475                expression.args.get("db"),
2476                expression.args.get("this"),
2477            )
2478            if part is not None
2479        )
2480
2481    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2482        table = self.table_parts(expression)
2483        only = "ONLY " if expression.args.get("only") else ""
2484        partition = self.sql(expression, "partition")
2485        partition = f" {partition}" if partition else ""
2486        version = self.sql(expression, "version")
2487        version = f" {version}" if version else ""
2488        alias = self.sql(expression, "alias")
2489        alias = f"{sep}{alias}" if alias else ""
2490
2491        sample = self.sql(expression, "sample")
2492        post_alias = ""
2493        pre_alias = ""
2494
2495        if self.dialect.ALIAS_POST_TABLESAMPLE:
2496            pre_alias = sample
2497        else:
2498            post_alias = sample
2499
2500        if self.dialect.ALIAS_POST_VERSION:
2501            pre_alias = f"{pre_alias}{version}"
2502        else:
2503            post_alias = f"{post_alias}{version}"
2504
2505        hints = self.expressions(expression, key="hints", sep=" ")
2506        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2507        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2508        joins = self.indent(
2509            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2510        )
2511        laterals = self.expressions(expression, key="laterals", sep="")
2512
2513        file_format = self.sql(expression, "format")
2514        pattern = self.sql(expression, "pattern")
2515        if file_format:
2516            pattern = f", PATTERN => {pattern}" if pattern else ""
2517            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2518        elif pattern:
2519            file_format = f" (PATTERN => {pattern})"
2520
2521        ordinality = expression.args.get("ordinality") or ""
2522        if ordinality:
2523            ordinality = f" WITH ORDINALITY{alias}"
2524            alias = ""
2525
2526        when = self.sql(expression, "when")
2527        if when:
2528            if self.HISTORICAL_DATA_POST_ALIAS:
2529                alias = f"{alias} {when}"
2530            else:
2531                table = f"{table} {when}"
2532
2533        changes = self.sql(expression, "changes")
2534        changes = f" {changes}" if changes else ""
2535
2536        rows_from = self.expressions(expression, key="rows_from")
2537        if rows_from:
2538            table = f"ROWS FROM {self.wrap(rows_from)}"
2539
2540        indexed = expression.args.get("indexed")
2541        if indexed is not None:
2542            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2543        else:
2544            indexed = ""
2545
2546        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2547
2548    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2549        table = self.func("TABLE", expression.this)
2550        alias = self.sql(expression, "alias")
2551        alias = f" AS {alias}" if alias else ""
2552        sample = self.sql(expression, "sample")
2553        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2554        joins = self.indent(
2555            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2556        )
2557        return f"{table}{alias}{pivots}{sample}{joins}"
2558
2559    def tablesample_sql(
2560        self,
2561        expression: exp.TableSample,
2562        tablesample_keyword: str | None = None,
2563    ) -> str:
2564        method = self.sql(expression, "method")
2565        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2566        numerator = self.sql(expression, "bucket_numerator")
2567        denominator = self.sql(expression, "bucket_denominator")
2568        field = self.sql(expression, "bucket_field")
2569        field = f" ON {field}" if field else ""
2570        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2571        seed = self.sql(expression, "seed")
2572        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2573
2574        size = self.sql(expression, "size")
2575        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2576            size = f"{size} ROWS"
2577
2578        percent = self.sql(expression, "percent")
2579        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2580            percent = f"{percent} PERCENT"
2581
2582        expr = f"{bucket}{percent}{size}"
2583        if self.TABLESAMPLE_REQUIRES_PARENS:
2584            expr = f"({expr})"
2585
2586        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2587
2588    def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None:
2589        # Returns the rewritten field.expressions list with PivotAlias wrappers injected where
2590        # the stored column name differs from the target dialect's natural output.
2591        columns = expression.args.get("columns")
2592        if not columns or len(expression.fields) != 1:
2593            return None
2594
2595        args = expression.args
2596        parser_cls = self.dialect.parser_class
2597
2598        tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS
2599        tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS
2600        tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING
2601
2602        src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings)
2603        src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns)
2604        src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming)
2605
2606        if (
2607            src_identify_pivot_strings == tgt_identify_pivot_strings
2608            and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns
2609            and src_pivot_column_naming == tgt_pivot_column_naming
2610        ):
2611            return None
2612
2613        in_exprs = expression.fields[0].expressions
2614        step = len(columns) // len(in_exprs)
2615
2616        # Derive the per-value suffix from the first stored column vs the first IN-list value.
2617        # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases.
2618        first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name
2619        first_stored = columns[0].name
2620
2621        # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed)
2622        if not first_stored.startswith(first_base):
2623            return None
2624
2625        suffix = first_stored[len(first_base) :]
2626
2627        # Whether the target dialect would append an agg-name suffix for this pivot.
2628        # Spark single-agg uniquely drops the agg alias entirely.
2629        target_has_suffix = (
2630            len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple"
2631        ) and any(a.alias for a in expression.expressions)
2632        source_has_suffix = suffix != ""
2633
2634        new_exprs: list[exp.Expression] = []
2635        modified = False
2636        for val_idx, e in enumerate(in_exprs):
2637            if isinstance(e, exp.PivotAlias):
2638                new_exprs.append(e)
2639                continue
2640
2641            i = val_idx * step
2642            stored_full = columns[i].name
2643            stored_value = stored_full[: -len(suffix)] if suffix else stored_full
2644            target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name
2645
2646            # Source had a suffix, but target won't apply one
2647            if source_has_suffix and not target_has_suffix:
2648                new_exprs.append(
2649                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True))
2650                )
2651                modified = True
2652            # Value-part mismatch (e.g. Snowflake's literal-style values vs others).
2653            elif stored_value != target_value:
2654                new_exprs.append(
2655                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True))
2656                )
2657                modified = True
2658            else:
2659                new_exprs.append(e)
2660
2661        return new_exprs if modified else None
2662
2663    def pivot_sql(self, expression: exp.Pivot) -> str:
2664        expressions = self.expressions(expression, flat=True)
2665        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2666
2667        group = self.sql(expression, "group")
2668
2669        if expression.this:
2670            this = self.sql(expression, "this")
2671            if not expressions:
2672                sql = f"UNPIVOT {this}"
2673            else:
2674                on = f"{self.seg('ON')} {expressions}"
2675                into = self.sql(expression, "into")
2676                into = f"{self.seg('INTO')} {into}" if into else ""
2677                using = self.expressions(expression, key="using", flat=True)
2678                using = f"{self.seg('USING')} {using}" if using else ""
2679                sql = f"{direction} {this}{on}{into}{using}{group}"
2680            return self.prepend_ctes(expression, sql)
2681
2682        if not expression.unpivot:
2683            # Wrap IN-list values with explicit aliases where the target dialect would differ
2684            new_field_exprs = self._pivot_in_value_aliases(expression)
2685            if new_field_exprs is not None:
2686                expression.fields[0].set("expressions", new_field_exprs)
2687
2688        alias = self.sql(expression, "alias")
2689        if alias:
2690            alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}"
2691
2692        fields = self.expressions(
2693            expression,
2694            "fields",
2695            sep=" ",
2696            dynamic=True,
2697            new_line=True,
2698            skip_first=True,
2699            skip_last=True,
2700        )
2701
2702        include_nulls = expression.args.get("include_nulls")
2703        if include_nulls is not None:
2704            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2705        else:
2706            nulls = ""
2707
2708        default_on_null = self.sql(expression, "default_on_null")
2709        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2710        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2711        return self.prepend_ctes(expression, sql)
2712
2713    def version_sql(self, expression: exp.Version) -> str:
2714        this = f"FOR {expression.name}"
2715        kind = expression.text("kind")
2716        expr = self.sql(expression, "expression")
2717        return f"{this} {kind} {expr}"
2718
2719    def tuple_sql(self, expression: exp.Tuple) -> str:
2720        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2721
2722    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2723        """
2724        Returns (join_sql, from_sql) for UPDATE statements.
2725        - join_sql: placed after UPDATE table, before SET
2726        - from_sql: placed after SET clause (standard position)
2727        Dialects like MySQL need to convert FROM to JOIN syntax.
2728        """
2729        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2730            return ("", self.sql(expression, "from_"))
2731
2732        # Qualify unqualified columns in SET clause with the target table
2733        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2734        target_table = expression.this
2735        if isinstance(target_table, exp.Table):
2736            target_name = exp.to_identifier(target_table.alias_or_name)
2737            for eq in expression.expressions:
2738                col = eq.this
2739                if isinstance(col, exp.Column) and not col.table:
2740                    col.set("table", target_name)
2741
2742        table = from_expr.this
2743        if nested_joins := table.args.get("joins", []):
2744            table.set("joins", None)
2745
2746        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2747        for nested in nested_joins:
2748            if not nested.args.get("on") and not nested.args.get("using"):
2749                nested.set("on", exp.true())
2750            join_sql += self.sql(nested)
2751
2752        return (join_sql, "")
2753
2754    def update_sql(self, expression: exp.Update) -> str:
2755        hint = self.sql(expression, "hint")
2756        this = self.sql(expression, "this")
2757        join_sql, from_sql = self._update_from_joins_sql(expression)
2758        set_sql = self.expressions(expression, flat=True)
2759        where_sql = self.sql(expression, "where")
2760        returning = self.sql(expression, "returning")
2761        order = self.sql(expression, "order")
2762        limit = self.sql(expression, "limit")
2763        if self.RETURNING_END:
2764            expression_sql = f"{from_sql}{where_sql}{returning}"
2765        else:
2766            expression_sql = f"{returning}{from_sql}{where_sql}"
2767        options = self.expressions(expression, key="options")
2768        options = f" OPTION({options})" if options else ""
2769        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2770        return self.prepend_ctes(expression, sql)
2771
2772    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2773        values_as_table = values_as_table and self.VALUES_AS_TABLE
2774
2775        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2776        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2777            args = self.expressions(expression)
2778            alias = self.sql(expression, "alias")
2779            values = f"VALUES{self.seg('')}{args}"
2780            values = (
2781                f"({values})"
2782                if self.WRAP_DERIVED_VALUES
2783                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2784                else values
2785            )
2786            values = self.query_modifiers(expression, values)
2787            return f"{values} AS {alias}" if alias else values
2788
2789        # Converts `VALUES...` expression into a series of select unions.
2790        alias_node = expression.args.get("alias")
2791        column_names = alias_node and alias_node.columns
2792
2793        selects: list[exp.Query] = []
2794
2795        for i, tup in enumerate(expression.expressions):
2796            row = tup.expressions
2797
2798            if i == 0 and column_names:
2799                row = [
2800                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2801                ]
2802
2803            selects.append(exp.Select(expressions=row))
2804
2805        if self.pretty:
2806            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2807            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2808            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2809            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2810            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2811
2812        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2813        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2814        return f"({unions}){alias}"
2815
2816    def var_sql(self, expression: exp.Var) -> str:
2817        return self.sql(expression, "this")
2818
2819    @unsupported_args("expressions")
2820    def into_sql(self, expression: exp.Into) -> str:
2821        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2822        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2823        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2824
2825    def from_sql(self, expression: exp.From) -> str:
2826        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2827
2828    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2829        grouping_sets = self.expressions(expression, indent=False)
2830        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2831
2832    def rollup_sql(self, expression: exp.Rollup) -> str:
2833        expressions = self.expressions(expression, indent=False)
2834        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2835
2836    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2837        this = self.sql(expression, "this")
2838
2839        columns = self.expressions(expression, flat=True)
2840
2841        from_sql = self.sql(expression, "from_index")
2842        from_sql = f" FROM {from_sql}" if from_sql else ""
2843
2844        properties = expression.args.get("properties")
2845        properties_sql = (
2846            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2847        )
2848
2849        return f"{this}({columns}){from_sql}{properties_sql}"
2850
2851    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2852        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2853
2854    def cube_sql(self, expression: exp.Cube) -> str:
2855        expressions = self.expressions(expression, indent=False)
2856        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2857
2858    def group_sql(self, expression: exp.Group) -> str:
2859        group_by_all = expression.args.get("all")
2860        if group_by_all is True:
2861            modifier = " ALL"
2862        elif group_by_all is False:
2863            modifier = " DISTINCT"
2864        else:
2865            modifier = ""
2866
2867        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2868
2869        grouping_sets = self.expressions(expression, key="grouping_sets")
2870        cube = self.expressions(expression, key="cube")
2871        rollup = self.expressions(expression, key="rollup")
2872
2873        groupings = csv(
2874            self.seg(grouping_sets) if grouping_sets else "",
2875            self.seg(cube) if cube else "",
2876            self.seg(rollup) if rollup else "",
2877            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2878            sep=self.GROUPINGS_SEP,
2879        )
2880
2881        if (
2882            expression.expressions
2883            and groupings
2884            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2885        ):
2886            add_separator = True
2887
2888            if grouping_sets:
2889                if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX:
2890                    add_separator = False
2891                else:
2892                    self.unsupported(
2893                        "GROUPING SETS without a comma after GROUP BY expressions is not supported"
2894                    )
2895
2896            if add_separator:
2897                group_by = f"{group_by}{self.GROUPINGS_SEP}"
2898
2899        return f"{group_by}{groupings}"
2900
2901    def having_sql(self, expression: exp.Having) -> str:
2902        this = self.indent(self.sql(expression, "this"))
2903        return f"{self.seg('HAVING')}{self.sep()}{this}"
2904
2905    def connect_sql(self, expression: exp.Connect) -> str:
2906        start = self.sql(expression, "start")
2907        start = self.seg(f"START WITH {start}") if start else ""
2908        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2909        connect = self.sql(expression, "connect")
2910        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2911        return start + connect
2912
2913    def prior_sql(self, expression: exp.Prior) -> str:
2914        return f"PRIOR {self.sql(expression, 'this')}"
2915
2916    def join_sql(self, expression: exp.Join) -> str:
2917        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2918            side = None
2919        else:
2920            side = expression.side
2921
2922        op_sql = " ".join(
2923            op
2924            for op in (
2925                expression.method,
2926                "GLOBAL" if expression.args.get("global_") else None,
2927                side,
2928                expression.kind,
2929                expression.hint if self.JOIN_HINTS else None,
2930                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2931            )
2932            if op
2933        )
2934        match_cond = self.sql(expression, "match_condition")
2935        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2936        on_sql = self.sql(expression, "on")
2937        using = expression.args.get("using")
2938
2939        if not on_sql and using:
2940            on_sql = csv(*(self.sql(column) for column in using))
2941
2942        this = expression.this
2943        this_sql = self.sql(this)
2944
2945        exprs = self.expressions(expression)
2946        if exprs:
2947            this_sql = f"{this_sql},{self.seg(exprs)}"
2948
2949        if on_sql:
2950            on_sql = self.indent(on_sql, skip_first=True)
2951            space = self.seg(" " * self.pad) if self.pretty else " "
2952            if using:
2953                on_sql = f"{space}USING ({on_sql})"
2954            else:
2955                on_sql = f"{space}ON {on_sql}"
2956        elif not op_sql:
2957            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2958                return f" {this_sql}"
2959
2960            return f", {this_sql}"
2961
2962        if op_sql != "STRAIGHT_JOIN":
2963            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2964
2965        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2966        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2967
2968    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2969        args = self.expressions(expression, flat=True)
2970        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2971        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2972
2973    def lateral_op(self, expression: exp.Lateral) -> str:
2974        cross_apply = expression.args.get("cross_apply")
2975
2976        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2977        if cross_apply is True:
2978            op = "INNER JOIN "
2979        elif cross_apply is False:
2980            op = "LEFT JOIN "
2981        else:
2982            op = ""
2983
2984        return f"{op}LATERAL"
2985
2986    def lateral_sql(self, expression: exp.Lateral) -> str:
2987        this = self.sql(expression, "this")
2988
2989        if expression.args.get("view"):
2990            alias = expression.args["alias"]
2991            columns = self.expressions(alias, key="columns", flat=True)
2992            table = f" {alias.name}" if alias.name else ""
2993            columns = f" AS {columns}" if columns else ""
2994            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2995            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2996
2997        table_alias = expression.args.get("alias")
2998        offset = expression.this.args.get("offset")
2999
3000        if (
3001            self.UNNEST_WITH_ORDINALITY
3002            and table_alias
3003            and isinstance(expression.this, exp.Unnest)
3004            and isinstance(offset, exp.Identifier)
3005        ):
3006            # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset
3007            table_alias = table_alias.copy()
3008            table_alias.append("columns", offset.copy())
3009
3010        alias = self.sql(table_alias)
3011        alias = f" AS {alias}" if alias else ""
3012
3013        ordinality = expression.args.get("ordinality") or ""
3014        if ordinality:
3015            ordinality = f" WITH ORDINALITY{alias}"
3016            alias = ""
3017
3018        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
3019
3020    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
3021        this = self.sql(expression, "this")
3022
3023        if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL:
3024            return this
3025
3026        args = [
3027            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
3028            for e in (expression.args.get(k) for k in ("offset", "expression"))
3029            if e
3030        ]
3031
3032        args_sql = ", ".join(self.sql(e) for e in args)
3033        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
3034        expressions = self.expressions(expression, flat=True)
3035        limit_options = self.sql(expression, "limit_options")
3036        expressions = f" BY {expressions}" if expressions else ""
3037
3038        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
3039
3040    def offset_sql(self, expression: exp.Offset) -> str:
3041        this = self.sql(expression, "this")
3042        value = expression.expression
3043        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
3044        expressions = self.expressions(expression, flat=True)
3045        expressions = f" BY {expressions}" if expressions else ""
3046        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
3047
3048    def setitem_sql(self, expression: exp.SetItem) -> str:
3049        kind = self.sql(expression, "kind")
3050        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
3051            kind = ""
3052        else:
3053            kind = f"{kind} " if kind else ""
3054        this = self.sql(expression, "this")
3055        expressions = self.expressions(expression)
3056        collate = self.sql(expression, "collate")
3057        collate = f" COLLATE {collate}" if collate else ""
3058        global_ = "GLOBAL " if expression.args.get("global_") else ""
3059        return f"{global_}{kind}{this}{expressions}{collate}"
3060
3061    def set_sql(self, expression: exp.Set) -> str:
3062        expressions = f" {self.expressions(expression, flat=True)}"
3063        tag = " TAG" if expression.args.get("tag") else ""
3064        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
3065
3066    def queryband_sql(self, expression: exp.QueryBand) -> str:
3067        this = self.sql(expression, "this")
3068        update = " UPDATE" if expression.args.get("update") else ""
3069        scope = self.sql(expression, "scope")
3070        scope = f" FOR {scope}" if scope else ""
3071
3072        return f"QUERY_BAND = {this}{update}{scope}"
3073
3074    def pragma_sql(self, expression: exp.Pragma) -> str:
3075        return f"PRAGMA {self.sql(expression, 'this')}"
3076
3077    def lock_sql(self, expression: exp.Lock) -> str:
3078        if not self.LOCKING_READS_SUPPORTED:
3079            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
3080            return ""
3081
3082        update = expression.args["update"]
3083        key = expression.args.get("key")
3084        if update:
3085            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
3086        else:
3087            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
3088        expressions = self.expressions(expression, flat=True)
3089        expressions = f" OF {expressions}" if expressions else ""
3090        wait = expression.args.get("wait")
3091
3092        if wait is not None:
3093            if isinstance(wait, exp.Literal):
3094                wait = f" WAIT {self.sql(wait)}"
3095            else:
3096                wait = " NOWAIT" if wait else " SKIP LOCKED"
3097
3098        return f"{lock_type}{expressions}{wait or ''}"
3099
3100    def literal_sql(self, expression: exp.Literal) -> str:
3101        text = expression.this or ""
3102        if expression.is_string:
3103            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
3104        return text
3105
3106    def escape_str(
3107        self,
3108        text: str,
3109        escape_backslash: bool = True,
3110        delimiter: str | None = None,
3111        escaped_delimiter: str | None = None,
3112        is_byte_string: bool = False,
3113        is_bytes: bool = False,
3114    ) -> str:
3115        if is_byte_string:
3116            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
3117        else:
3118            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
3119
3120        if supports_escape_sequences:
3121            text = "".join(
3122                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
3123                for ch in text
3124            )
3125
3126            if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES:
3127                # Escape high bytes so they aren't re-encoded as UTF-8 characters.
3128                text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text)
3129
3130        delimiter = delimiter or self.dialect.QUOTE_END
3131        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
3132
3133        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3134
3135    def loaddata_sql(self, expression: exp.LoadData) -> str:
3136        is_overwrite = expression.args.get("overwrite")
3137        overwrite = " OVERWRITE" if is_overwrite else ""
3138        this = self.sql(expression, "this")
3139
3140        files = expression.args.get("files")
3141        if files:
3142            files_sql = self.expressions(files, flat=True)
3143            files_sql = f"FILES{self.wrap(files_sql)}"
3144            if is_overwrite:
3145                this = f" {this}"
3146            elif expression.args.get("temp"):
3147                this = f" INTO TEMP TABLE {this}"
3148            else:
3149                this = f" INTO TABLE {this}"
3150            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
3151
3152        local = " LOCAL" if expression.args.get("local") else ""
3153        inpath = f" INPATH {self.sql(expression, 'inpath')}"
3154        this = f" INTO TABLE {this}"
3155        partition = self.sql(expression, "partition")
3156        partition = f" {partition}" if partition else ""
3157        input_format = self.sql(expression, "input_format")
3158        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3159        serde = self.sql(expression, "serde")
3160        serde = f" SERDE {serde}" if serde else ""
3161        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3162
3163    def null_sql(self, *_) -> str:
3164        return "NULL"
3165
3166    def boolean_sql(self, expression: exp.Boolean) -> str:
3167        return "TRUE" if expression.this else "FALSE"
3168
3169    def booland_sql(self, expression: exp.Booland) -> str:
3170        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
3171
3172    def boolor_sql(self, expression: exp.Boolor) -> str:
3173        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
3174
3175    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3176        this = self.sql(expression, "this")
3177        this = f"{this} " if this else this
3178        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3179        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3180
3181    def withfill_sql(self, expression: exp.WithFill) -> str:
3182        from_sql = self.sql(expression, "from_")
3183        from_sql = f" FROM {from_sql}" if from_sql else ""
3184        to_sql = self.sql(expression, "to")
3185        to_sql = f" TO {to_sql}" if to_sql else ""
3186        step_sql = self.sql(expression, "step")
3187        step_sql = f" STEP {step_sql}" if step_sql else ""
3188        interpolated_values = [
3189            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3190            if isinstance(e, exp.Alias)
3191            else self.sql(e, "this")
3192            for e in expression.args.get("interpolate") or []
3193        ]
3194        interpolate = (
3195            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3196        )
3197        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3198
3199    def cluster_sql(self, expression: exp.Cluster) -> str:
3200        return self.op_expressions("CLUSTER BY", expression)
3201
3202    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3203        if expression.this:
3204            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3205            return ""
3206        expressions = self.expressions(expression, flat=True)
3207        return f"CLUSTER BY ({expressions})"
3208
3209    def distribute_sql(self, expression: exp.Distribute) -> str:
3210        return self.op_expressions("DISTRIBUTE BY", expression)
3211
3212    def sort_sql(self, expression: exp.Sort) -> str:
3213        return self.op_expressions("SORT BY", expression)
3214
3215    def _resolve_ordered_for_null_ordering_simulation(
3216        self, expression: exp.Ordered
3217    ) -> exp.Expr | None:
3218        """Resolve a bare ORDER BY name against the enclosing SELECT projection.
3219
3220        Returns the underlying expression of the uniquely-matching projection
3221        (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE
3222        simulation, since the CASE is evaluated in FROM-clause scope rather
3223        than alias scope (MySQL error 1052). Returns None if no safe
3224        substitution applies, leaving the original behaviour unchanged.
3225        """
3226        this = expression.this
3227        if not (isinstance(this, exp.Column) and not this.table):
3228            return None
3229
3230        # Only a query's own ORDER BY can reference its projection aliases
3231        order = expression.parent
3232        ancestor = order.parent if isinstance(order, exp.Order) else None
3233        if not isinstance(ancestor, exp.Select):
3234            return None
3235
3236        column_name = this.name
3237        matched: list[exp.Expr] = [
3238            p.this if isinstance(p, exp.Alias) else p
3239            for p in ancestor.selects
3240            if p.output_name == column_name
3241        ]
3242        match = matched[0] if len(matched) == 1 else None
3243
3244        # Skip the substitution when it would be identical to the existing
3245        # reference (e.g. ``SELECT col FROM t ORDER BY col``).
3246        if isinstance(match, exp.Column) and not match.table and match.name == column_name:
3247            return None
3248
3249        return match
3250
3251    def ordered_sql(self, expression: exp.Ordered) -> str:
3252        desc = expression.args.get("desc")
3253        asc = not desc
3254
3255        nulls_first = expression.args.get("nulls_first")
3256        nulls_last = not nulls_first
3257        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3258        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3259        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3260
3261        this = self.sql(expression, "this")
3262
3263        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3264        nulls_sort_change = ""
3265        if nulls_first and (
3266            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3267        ):
3268            nulls_sort_change = " NULLS FIRST"
3269        elif (
3270            nulls_last
3271            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3272            and not nulls_are_last
3273        ):
3274            nulls_sort_change = " NULLS LAST"
3275
3276        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3277        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3278            window = expression.find_ancestor(exp.Window, exp.Select)
3279
3280            if isinstance(window, exp.Window):
3281                window_this = window.this
3282                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3283                    window_this = window_this.this
3284                spec = window.args.get("spec")
3285            else:
3286                window_this = None
3287                spec = None
3288
3289            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3290            # without a spec or with a ROWS spec, but not with RANGE
3291            if not (
3292                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3293                and (not spec or spec.text("kind").upper() == "ROWS")
3294            ):
3295                if window_this and spec:
3296                    self.unsupported(
3297                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3298                    )
3299                    nulls_sort_change = ""
3300                elif self.NULL_ORDERING_SUPPORTED is False and (
3301                    (asc and nulls_sort_change == " NULLS LAST")
3302                    or (desc and nulls_sort_change == " NULLS FIRST")
3303                ):
3304                    # BigQuery does not allow these ordering/nulls combinations when used under
3305                    # an aggregation func or under a window containing one
3306                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3307
3308                    if isinstance(ancestor, exp.Window):
3309                        ancestor = ancestor.this
3310                    if isinstance(ancestor, exp.AggFunc):
3311                        self.unsupported(
3312                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3313                        )
3314                        nulls_sort_change = ""
3315                elif self.NULL_ORDERING_SUPPORTED is None:
3316                    if expression.this.is_int:
3317                        self.unsupported(
3318                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3319                        )
3320                    elif not isinstance(expression.this, exp.Rand):
3321                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3322                        target = self.sql(resolved) if resolved is not None else this
3323                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3324                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3325                    nulls_sort_change = ""
3326
3327        with_fill = self.sql(expression, "with_fill")
3328        with_fill = f" {with_fill}" if with_fill else ""
3329
3330        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
3331
3332    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3333        window_frame = self.sql(expression, "window_frame")
3334        window_frame = f"{window_frame} " if window_frame else ""
3335
3336        this = self.sql(expression, "this")
3337
3338        return f"{window_frame}{this}"
3339
3340    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3341        partition = self.partition_by_sql(expression)
3342        order = self.sql(expression, "order")
3343        measures = self.expressions(expression, key="measures")
3344        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3345        rows = self.sql(expression, "rows")
3346        rows = self.seg(rows) if rows else ""
3347        after = self.sql(expression, "after")
3348        after = self.seg(after) if after else ""
3349        pattern = self.sql(expression, "pattern")
3350        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3351        definition_sqls = [
3352            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3353            for definition in expression.args.get("define", [])
3354        ]
3355        definitions = self.expressions(sqls=definition_sqls)
3356        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3357        body = "".join(
3358            (
3359                partition,
3360                order,
3361                measures,
3362                rows,
3363                after,
3364                pattern,
3365                define,
3366            )
3367        )
3368        alias = self.sql(expression, "alias")
3369        alias = f" {alias}" if alias else ""
3370        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3371
3372    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3373        limit = expression.args.get("limit")
3374
3375        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3376            count = limit.args.get("count")
3377            # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL
3378            # standard; emitting a bare "LIMIT" here would produce invalid SQL.
3379            limit = exp.Limit(
3380                expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1)
3381            )
3382        elif (
3383            self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all
3384        ):
3385            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3386
3387        return csv(
3388            *sqls,
3389            *[self.sql(join) for join in expression.args.get("joins") or []],
3390            self.sql(expression, "match"),
3391            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3392            self.sql(expression, "prewhere"),
3393            self.sql(expression, "where"),
3394            self.sql(expression, "connect"),
3395            self.sql(expression, "group"),
3396            self.sql(expression, "having"),
3397            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3398            self.sql(expression, "order"),
3399            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3400            *self.after_limit_modifiers(expression),
3401            self.sql(expression, "for_"),
3402            self.options_modifier(expression),
3403            sep="",
3404        )
3405
3406    def options_modifier(self, expression: exp.Expr) -> str:
3407        options = self.expressions(expression, key="options")
3408        return f" {options}" if options else ""
3409
3410    def forclause_sql(self, expression: exp.ForClause) -> str:
3411        kind = expression.args["kind"]
3412        if kind == "BROWSE":
3413            return f"{self.sep()}FOR BROWSE"
3414        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3415        # the target dialect doesn't support QueryOption, so we drop the clause.
3416        options = self.expressions(expression, key="expressions")
3417        if not options:
3418            return ""
3419        return f"{self.sep()}FOR {kind}{self.seg(options)}"
3420
3421    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3422        self.unsupported("Unsupported query option.")
3423        return ""
3424
3425    def offset_limit_modifiers(
3426        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3427    ) -> list[str]:
3428        return [
3429            self.sql(expression, "offset") if fetch else self.sql(limit),
3430            self.sql(limit) if fetch else self.sql(expression, "offset"),
3431        ]
3432
3433    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3434        locks = self.expressions(expression, key="locks", sep=" ")
3435        locks = f" {locks}" if locks else ""
3436        return [locks, self.sql(expression, "sample")]
3437
3438    def select_sql(self, expression: exp.Select) -> str:
3439        into = expression.args.get("into")
3440        if not self.SUPPORTS_SELECT_INTO and into:
3441            into.pop()
3442
3443        hint = self.sql(expression, "hint")
3444        distinct = self.sql(expression, "distinct")
3445        distinct = f" {distinct}" if distinct else ""
3446        kind = self.sql(expression, "kind")
3447
3448        limit = expression.args.get("limit")
3449        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3450            top = self.limit_sql(limit, top=True)
3451            limit.pop()
3452        else:
3453            top = ""
3454
3455        expressions = self.expressions(expression)
3456
3457        if kind:
3458            if kind in self.SELECT_KINDS:
3459                kind = f" AS {kind}"
3460            else:
3461                if kind == "STRUCT":
3462                    expressions = self.expressions(
3463                        sqls=[
3464                            self.sql(
3465                                exp.Struct(
3466                                    expressions=[
3467                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3468                                        if isinstance(e, exp.Alias)
3469                                        else e
3470                                        for e in expression.expressions
3471                                    ]
3472                                )
3473                            )
3474                        ]
3475                    )
3476                kind = ""
3477
3478        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3479        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3480
3481        exclude = expression.args.get("exclude")
3482
3483        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3484            exclude_sql = self.expressions(sqls=exclude, flat=True)
3485            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3486
3487        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3488        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3489        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3490        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3491        sql = self.query_modifiers(
3492            expression,
3493            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3494            self.sql(expression, "into", comment=False),
3495            self.sql(expression, "from_", comment=False),
3496        )
3497
3498        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3499        if expression.args.get("with_"):
3500            sql = self.maybe_comment(sql, expression)
3501            expression.pop_comments()
3502
3503        sql = self.prepend_ctes(expression, sql)
3504
3505        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3506            expression.set("exclude", None)
3507            subquery = expression.subquery(copy=False)
3508            star = exp.Star(except_=exclude)
3509            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3510
3511        if not self.SUPPORTS_SELECT_INTO and into:
3512            if into.args.get("temporary"):
3513                table_kind = " TEMPORARY"
3514            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3515                table_kind = " UNLOGGED"
3516            else:
3517                table_kind = ""
3518            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3519
3520        return sql
3521
3522    def schema_sql(self, expression: exp.Schema) -> str:
3523        this = self.sql(expression, "this")
3524        sql = self.schema_columns_sql(expression)
3525        return f"{this} {sql}" if this and sql else this or sql
3526
3527    def schema_columns_sql(self, expression: exp.Expr) -> str:
3528        if expression.expressions:
3529            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3530        return ""
3531
3532    def star_sql(self, expression: exp.Star) -> str:
3533        except_ = self.expressions(expression, key="except_", flat=True)
3534        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3535        replace = self.expressions(expression, key="replace", flat=True)
3536        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3537        rename = self.expressions(expression, key="rename", flat=True)
3538        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3539        ilike = self.sql(expression, "ilike")
3540        ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else ""
3541        return f"*{ilike}{except_}{replace}{rename}"
3542
3543    def parameter_sql(self, expression: exp.Parameter) -> str:
3544        this = self.sql(expression, "this")
3545        return f"{self.PARAMETER_TOKEN}{this}"
3546
3547    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3548        this = self.sql(expression, "this")
3549        kind = expression.text("kind")
3550        if kind:
3551            kind = f"{kind}."
3552        return f"@@{kind}{this}"
3553
3554    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3555        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3556
3557    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3558        alias = self.sql(expression, "alias")
3559        alias = f"{sep}{alias}" if alias else ""
3560        sample = self.sql(expression, "sample")
3561        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3562            alias = f"{sample}{alias}"
3563
3564            # Set to None so it's not generated again by self.query_modifiers()
3565            expression.set("sample", None)
3566
3567        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3568        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3569        return self.prepend_ctes(expression, sql)
3570
3571    def qualify_sql(self, expression: exp.Qualify) -> str:
3572        this = self.indent(self.sql(expression, "this"))
3573        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3574
3575    def unnest_sql(self, expression: exp.Unnest) -> str:
3576        args = self.expressions(expression, flat=True)
3577
3578        alias = expression.args.get("alias")
3579        offset = expression.args.get("offset")
3580
3581        if self.UNNEST_WITH_ORDINALITY:
3582            if alias and isinstance(offset, exp.Expr):
3583                alias.append("columns", offset)
3584                expression.set("offset", None)
3585
3586        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3587            columns = alias.columns
3588            alias = self.sql(columns[0]) if columns else ""
3589        else:
3590            alias = self.sql(alias)
3591
3592        alias = f" AS {alias}" if alias else alias
3593        if self.UNNEST_WITH_ORDINALITY:
3594            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3595        else:
3596            if isinstance(offset, exp.Expr):
3597                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3598            elif offset:
3599                suffix = f"{alias} WITH OFFSET"
3600            else:
3601                suffix = alias
3602
3603        return f"UNNEST({args}){suffix}"
3604
3605    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3606        return ""
3607
3608    def where_sql(self, expression: exp.Where) -> str:
3609        this = self.indent(self.sql(expression, "this"))
3610        return f"{self.seg('WHERE')}{self.sep()}{this}"
3611
3612    def window_sql(self, expression: exp.Window) -> str:
3613        this = self.sql(expression, "this")
3614        partition = self.partition_by_sql(expression)
3615        order = expression.args.get("order")
3616        order = self.order_sql(order, flat=True) if order else ""
3617        spec = self.sql(expression, "spec")
3618        alias = self.sql(expression, "alias")
3619        over = self.sql(expression, "over") or "OVER"
3620
3621        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3622
3623        first = expression.args.get("first")
3624        if first is None:
3625            first = ""
3626        else:
3627            first = "FIRST" if first else "LAST"
3628
3629        if not partition and not order and not spec and alias:
3630            return f"{this} {alias}"
3631
3632        args = self.format_args(
3633            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3634        )
3635        return f"{this} ({args})"
3636
3637    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3638        partition = self.expressions(expression, key="partition_by", flat=True)
3639        return f"PARTITION BY {partition}" if partition else ""
3640
3641    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3642        kind = self.sql(expression, "kind")
3643        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3644        end = (
3645            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3646            or "CURRENT ROW"
3647        )
3648
3649        window_spec = f"{kind} BETWEEN {start} AND {end}"
3650
3651        exclude = self.sql(expression, "exclude")
3652        if exclude:
3653            if self.SUPPORTS_WINDOW_EXCLUDE:
3654                window_spec += f" EXCLUDE {exclude}"
3655            else:
3656                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3657
3658        return window_spec
3659
3660    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3661        this = self.sql(expression, "this")
3662        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3663        return f"{this} WITHIN GROUP ({expression_sql})"
3664
3665    def between_sql(self, expression: exp.Between) -> str:
3666        this = self.sql(expression, "this")
3667        low = self.sql(expression, "low")
3668        high = self.sql(expression, "high")
3669        symmetric = expression.args.get("symmetric")
3670
3671        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3672            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3673
3674        flag = (
3675            " SYMMETRIC"
3676            if symmetric
3677            else " ASYMMETRIC"
3678            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3679            else ""  # silently drop ASYMMETRIC – semantics identical
3680        )
3681        return f"{this} BETWEEN{flag} {low} AND {high}"
3682
3683    def bracket_offset_expressions(
3684        self, expression: exp.Bracket, index_offset: int | None = None
3685    ) -> list[exp.Expr]:
3686        if expression.args.get("json_access"):
3687            return expression.expressions
3688
3689        return apply_index_offset(
3690            expression.this,
3691            expression.expressions,
3692            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3693            dialect=self.dialect,
3694        )
3695
3696    def bracket_sql(self, expression: exp.Bracket) -> str:
3697        expressions = self.bracket_offset_expressions(expression)
3698        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3699        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3700
3701    def all_sql(self, expression: exp.All) -> str:
3702        this = self.sql(expression, "this")
3703        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3704            this = self.wrap(this)
3705        return f"ALL {this}"
3706
3707    def any_sql(self, expression: exp.Any) -> str:
3708        this = self.sql(expression, "this")
3709        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3710            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3711                this = self.wrap(this)
3712            return f"ANY{this}"
3713        return f"ANY {this}"
3714
3715    def exists_sql(self, expression: exp.Exists) -> str:
3716        return f"EXISTS{self.wrap(expression)}"
3717
3718    def case_sql(self, expression: exp.Case) -> str:
3719        this = self.sql(expression, "this")
3720        statements = [f"CASE {this}" if this else "CASE"]
3721
3722        for e in expression.args["ifs"]:
3723            statements.append(f"WHEN {self.sql(e, 'this')}")
3724            statements.append(f"THEN {self.sql(e, 'true')}")
3725
3726        default = self.sql(expression, "default")
3727
3728        if default:
3729            statements.append(f"ELSE {default}")
3730
3731        statements.append("END")
3732
3733        if self.pretty and self.too_wide(statements):
3734            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3735
3736        return " ".join(statements)
3737
3738    def constraint_sql(self, expression: exp.Constraint) -> str:
3739        this = self.sql(expression, "this")
3740        expressions = self.expressions(expression, flat=True)
3741        return f"CONSTRAINT {this} {expressions}"
3742
3743    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3744        order = expression.args.get("order")
3745        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3746        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3747
3748    def extract_sql(self, expression: exp.Extract) -> str:
3749        import sqlglot.dialects.dialect
3750
3751        this = (
3752            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3753            if self.NORMALIZE_EXTRACT_DATE_PARTS
3754            else expression.this
3755        )
3756        if self.EXTRACT_ALLOWS_QUOTES:
3757            this_sql = self.sql(this)
3758        elif isinstance(this, exp.WeekStart):
3759            this_sql = self.weekstart_name(this)
3760        else:
3761            this_sql = this.name
3762        expression_sql = self.sql(expression, "expression")
3763
3764        return f"EXTRACT({this_sql} FROM {expression_sql})"
3765
3766    def trim_sql(self, expression: exp.Trim) -> str:
3767        trim_type = self.sql(expression, "position")
3768
3769        if trim_type == "LEADING":
3770            func_name = "LTRIM"
3771        elif trim_type == "TRAILING":
3772            func_name = "RTRIM"
3773        else:
3774            func_name = "TRIM"
3775
3776        return self.func(func_name, expression.this, expression.expression)
3777
3778    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3779        args = expression.expressions
3780        if isinstance(expression, exp.ConcatWs):
3781            args = args[1:]  # Skip the delimiter
3782
3783        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3784            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3785
3786        concat_coalesce = (
3787            self.dialect.CONCAT_WS_COALESCE
3788            if isinstance(expression, exp.ConcatWs)
3789            else self.dialect.CONCAT_COALESCE
3790        )
3791
3792        if not concat_coalesce and expression.args.get("coalesce"):
3793
3794            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3795                if not e.type:
3796                    import sqlglot.optimizer.annotate_types
3797
3798                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3799
3800                if e.is_string or e.is_type(exp.DType.ARRAY):
3801                    return e
3802
3803                return exp.func("coalesce", e, exp.Literal.string(""))
3804
3805            args = [_wrap_with_coalesce(e) for e in args]
3806
3807        return args
3808
3809    def concat_sql(self, expression: exp.Concat) -> str:
3810        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3811            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3812            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3813            # instead of coalescing them to empty string.
3814            import sqlglot.dialects.dialect
3815
3816            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3817
3818        expressions = self.convert_concat_args(expression)
3819
3820        # Some dialects don't allow a single-argument CONCAT call
3821        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3822            return self.sql(expressions[0])
3823
3824        return self.func("CONCAT", *expressions)
3825
3826    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3827        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3828            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3829            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3830            all_args = expression.expressions
3831            expression.set("coalesce", True)
3832            return self.sql(
3833                exp.case()
3834                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3835                .else_(expression)
3836            )
3837
3838        return self.func(
3839            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3840        )
3841
3842    def check_sql(self, expression: exp.Check) -> str:
3843        this = self.sql(expression, key="this")
3844        return f"CHECK ({this})"
3845
3846    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3847        this = self.sql(expression, "this")
3848        this = f" {this}" if this else ""
3849        expressions = self.expressions(expression, flat=True)
3850        expressions = f" ({expressions})" if expressions else ""
3851        reference = self.sql(expression, "reference")
3852        reference = f" {reference}" if reference else ""
3853        delete = self.sql(expression, "delete")
3854        delete = f" ON DELETE {delete}" if delete else ""
3855        update = self.sql(expression, "update")
3856        update = f" ON UPDATE {update}" if update else ""
3857        options = self.expressions(expression, key="options", flat=True, sep=" ")
3858        options = f" {options}" if options else ""
3859        return f"FOREIGN KEY{this}{expressions}{reference}{delete}{update}{options}"
3860
3861    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3862        this = self.sql(expression, "this")
3863        this = f" {this}" if this else ""
3864        expressions = self.expressions(expression, flat=True)
3865        include = self.sql(expression, "include")
3866        options = self.expressions(expression, key="options", flat=True, sep=" ")
3867        options = f" {options}" if options else ""
3868        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3869
3870    def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str:
3871        self.unsupported("TIMESERIES primary key columns are not supported")
3872        return self.sql(expression, "this")
3873
3874    def if_sql(self, expression: exp.If) -> str:
3875        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3876
3877    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3878        if self.MATCH_AGAINST_TABLE_PREFIX:
3879            expressions = []
3880            for expr in expression.expressions:
3881                if isinstance(expr, exp.Table):
3882                    expressions.append(f"TABLE {self.sql(expr)}")
3883                else:
3884                    expressions.append(expr)
3885        else:
3886            expressions = expression.expressions
3887
3888        modifier = expression.args.get("modifier")
3889        modifier = f" {modifier}" if modifier else ""
3890        return (
3891            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3892        )
3893
3894    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3895        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3896
3897    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3898        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3899
3900        if self.QUOTE_JSON_PATH:
3901            path = self.escape_str(path)
3902            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3903
3904        return path
3905
3906    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3907        if isinstance(expression, exp.JSONPathPart):
3908            transform = self.TRANSFORMS.get(expression.__class__)
3909            if not callable(transform):
3910                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3911                return ""
3912
3913            return transform(self, expression)
3914
3915        if isinstance(expression, int):
3916            return str(expression)
3917
3918        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3919            escaped = expression.replace("'", "\\'")
3920            escaped = f"'{escaped}'"
3921        else:
3922            escaped = expression.replace('"', '\\"')
3923            escaped = f'"{escaped}"'
3924
3925        return escaped
3926
3927    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3928        return f"{self.sql(expression, 'this')} FORMAT JSON"
3929
3930    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3931        # Output the Teradata column FORMAT override.
3932        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3933        this = self.sql(expression, "this")
3934        fmt = self.sql(expression, "format")
3935        return f"{this} (FORMAT {fmt})"
3936
3937    def _jsonobject_sql(
3938        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
3939    ) -> str:
3940        null_handling = expression.args.get("null_handling")
3941        null_handling = f" {null_handling}" if null_handling else ""
3942
3943        unique_keys = expression.args.get("unique_keys")
3944        if unique_keys is not None:
3945            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3946        else:
3947            unique_keys = ""
3948
3949        return_type = self.sql(expression, "return_type")
3950        return_type = f" RETURNING {return_type}" if return_type else ""
3951        encoding = self.sql(expression, "encoding")
3952        encoding = f" ENCODING {encoding}" if encoding else ""
3953
3954        if not name:
3955            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
3956
3957        return self.func(
3958            name,
3959            *expression.expressions,
3960            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3961        )
3962
3963    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3964        null_handling = expression.args.get("null_handling")
3965        null_handling = f" {null_handling}" if null_handling else ""
3966        return_type = self.sql(expression, "return_type")
3967        return_type = f" RETURNING {return_type}" if return_type else ""
3968        strict = " STRICT" if expression.args.get("strict") else ""
3969        return self.func(
3970            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3971        )
3972
3973    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3974        this = self.sql(expression, "this")
3975        order = self.sql(expression, "order")
3976        null_handling = expression.args.get("null_handling")
3977        null_handling = f" {null_handling}" if null_handling else ""
3978        return_type = self.sql(expression, "return_type")
3979        return_type = f" RETURNING {return_type}" if return_type else ""
3980        strict = " STRICT" if expression.args.get("strict") else ""
3981        return self.func(
3982            "JSON_ARRAYAGG",
3983            this,
3984            suffix=f"{order}{null_handling}{return_type}{strict})",
3985        )
3986
3987    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3988        path = self.sql(expression, "path")
3989        path = f" PATH {path}" if path else ""
3990        nested_schema = self.sql(expression, "nested_schema")
3991
3992        if nested_schema:
3993            return f"NESTED{path} {nested_schema}"
3994
3995        this = self.sql(expression, "this")
3996        kind = self.sql(expression, "kind")
3997        kind = f" {kind}" if kind else ""
3998        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3999
4000        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
4001        return f"{this}{kind}{format_json}{path}{ordinality}"
4002
4003    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
4004        return self.func("COLUMNS", *expression.expressions)
4005
4006    def jsontable_sql(self, expression: exp.JSONTable) -> str:
4007        this = self.sql(expression, "this")
4008        path = self.sql(expression, "path")
4009        path = f", {path}" if path else ""
4010        error_handling = expression.args.get("error_handling")
4011        error_handling = f" {error_handling}" if error_handling else ""
4012        empty_handling = expression.args.get("empty_handling")
4013        empty_handling = f" {empty_handling}" if empty_handling else ""
4014        schema = self.sql(expression, "schema")
4015        return self.func(
4016            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
4017        )
4018
4019    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
4020        this = self.sql(expression, "this")
4021        kind = self.sql(expression, "kind")
4022        path = self.sql(expression, "path")
4023        path = f" {path}" if path else ""
4024        as_json = " AS JSON" if expression.args.get("as_json") else ""
4025        return f"{this} {kind}{path}{as_json}"
4026
4027    def openjson_sql(self, expression: exp.OpenJSON) -> str:
4028        this = self.sql(expression, "this")
4029        path = self.sql(expression, "path")
4030        path = f", {path}" if path else ""
4031        expressions = self.expressions(expression)
4032        with_ = (
4033            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
4034            if expressions
4035            else ""
4036        )
4037        return f"OPENJSON({this}{path}){with_}"
4038
4039    def in_sql(self, expression: exp.In) -> str:
4040        query = expression.args.get("query")
4041        unnest = expression.args.get("unnest")
4042        field = expression.args.get("field")
4043        is_global = " GLOBAL" if expression.args.get("is_global") else ""
4044
4045        if query:
4046            in_sql = self.sql(query)
4047        elif unnest:
4048            in_sql = self.in_unnest_op(unnest)
4049        elif field:
4050            in_sql = self.sql(field)
4051        else:
4052            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
4053
4054        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
4055
4056    def in_unnest_op(self, unnest: exp.Unnest) -> str:
4057        return f"(SELECT {self.sql(unnest)})"
4058
4059    def interval_sql(self, expression: exp.Interval) -> str:
4060        include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance(
4061            expression.find_ancestor(exp.AutoRefreshProperty, exp.Select),
4062            exp.AutoRefreshProperty,
4063        )
4064        interval_keyword = "INTERVAL" if include_keyword else ""
4065        unit_expression = expression.args.get("unit")
4066        unit = self.sql(unit_expression) if unit_expression else ""
4067        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
4068            unit = self.TIME_PART_SINGULARS.get(unit, unit)
4069        unit = f" {unit}" if unit else ""
4070
4071        if self.SINGLE_STRING_INTERVAL:
4072            this = expression.this.name if expression.this else ""
4073            if this:
4074                interval_keyword = f"{interval_keyword} " if interval_keyword else ""
4075                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
4076                    return f"{interval_keyword}'{this}'{unit}"
4077                return f"{interval_keyword}'{this}{unit}'"
4078            return f"{interval_keyword}{unit}"
4079
4080        this = self.sql(expression, "this")
4081        if this:
4082            if not include_keyword and expression.this.is_string:
4083                this = expression.this.name
4084            if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES):
4085                this = f"({this})"
4086            if include_keyword:
4087                this = f" {this}"
4088
4089        return f"{interval_keyword}{this}{unit}"
4090
4091    def return_sql(self, expression: exp.Return) -> str:
4092        return f"RETURN {self.sql(expression, 'this')}"
4093
4094    def reference_sql(self, expression: exp.Reference) -> str:
4095        this = self.sql(expression, "this")
4096        expressions = self.expressions(expression, flat=True)
4097        expressions = f"({expressions})" if expressions else ""
4098        options = self.expressions(expression, key="options", flat=True, sep=" ")
4099        options = f" {options}" if options else ""
4100        return f"REFERENCES {this}{expressions}{options}"
4101
4102    def anonymous_sql(self, expression: exp.Anonymous) -> str:
4103        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
4104        parent = expression.parent
4105        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
4106
4107        return self.func(
4108            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
4109        )
4110
4111    def paren_sql(self, expression: exp.Paren) -> str:
4112        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
4113        return f"({sql}{self.seg(')', sep='')}"
4114
4115    def neg_sql(self, expression: exp.Neg) -> str:
4116        # This makes sure we don't convert "- - 5" to "--5", which is a comment
4117        this_sql = self.sql(expression, "this")
4118        sep = " " if this_sql[0] == "-" else ""
4119        return f"-{sep}{this_sql}"
4120
4121    def not_sql(self, expression: exp.Not) -> str:
4122        return f"NOT {self.sql(expression, 'this')}"
4123
4124    def alias_sql(self, expression: exp.Alias) -> str:
4125        alias = self.sql(expression, "alias")
4126        alias = f" AS {alias}" if alias else ""
4127        return f"{self.sql(expression, 'this')}{alias}"
4128
4129    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
4130        alias = expression.args["alias"]
4131
4132        parent = expression.parent
4133        pivot = parent and parent.parent
4134
4135        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
4136            identifier_alias = isinstance(alias, exp.Identifier)
4137            literal_alias = isinstance(alias, exp.Literal)
4138
4139            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4140                alias.replace(exp.Literal.string(alias.output_name))
4141            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4142                alias.replace(exp.to_identifier(alias.output_name))
4143
4144        return self.alias_sql(expression)
4145
4146    def aliases_sql(self, expression: exp.Aliases) -> str:
4147        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
4148
4149    def atindex_sql(self, expression: exp.AtIndex) -> str:
4150        this = self.sql(expression, "this")
4151        index = self.sql(expression, "expression")
4152        return f"{this} AT {index}"
4153
4154    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
4155        this = self.sql(expression, "this")
4156        zone = self.sql(expression, "zone")
4157        return f"{this} AT TIME ZONE {zone}"
4158
4159    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
4160        this = self.sql(expression, "this")
4161        zone = self.sql(expression, "zone")
4162        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
4163
4164    def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str:
4165        return self.sql(exp.cast(expression.this, exp.DType.DATE))
4166
4167    def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:
4168        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4169
4170    def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str:
4171        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4172
4173    def add_sql(self, expression: exp.Add) -> str:
4174        return self.binary(expression, "+")
4175
4176    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
4177        return self.connector_sql(expression, "AND", stack)
4178
4179    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
4180        return self.connector_sql(expression, "OR", stack)
4181
4182    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
4183        return self.connector_sql(expression, "XOR", stack)
4184
4185    def connector_sql(
4186        self,
4187        expression: exp.Connector,
4188        op: str,
4189        stack: list[str | exp.Expr] | None = None,
4190    ) -> str:
4191        if stack is not None:
4192            stack.append(expression.right)
4193            if expression.comments and self.comments:
4194                op = self.maybe_comment(op, comments=expression.comments)
4195
4196            stack.extend((op, expression.left))
4197            return op
4198
4199        stack = [expression]
4200        sqls: list[str] = []
4201        ops = set()
4202
4203        while stack:
4204            node = stack.pop()
4205            if isinstance(node, exp.Connector):
4206                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4207            else:
4208                sql = self.sql(node)
4209                if sqls and sqls[-1] in ops:
4210                    sqls[-1] += f" {sql}"
4211                else:
4212                    sqls.append(sql)
4213
4214        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4215        return sep.join(sqls)
4216
4217    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4218        return self.binary(expression, "&")
4219
4220    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4221        return self.binary(expression, "<<")
4222
4223    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4224        return f"~{self.sql(expression, 'this')}"
4225
4226    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4227        return self.binary(expression, "|")
4228
4229    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4230        return self.binary(expression, ">>")
4231
4232    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4233        return self.binary(expression, "^")
4234
4235    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4236        format_sql = self.sql(expression, "format")
4237        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4238        to_sql = self.sql(expression, "to")
4239        to_sql = f" {to_sql}" if to_sql else ""
4240        action = self.sql(expression, "action")
4241        action = f" {action}" if action else ""
4242        default = self.sql(expression, "default")
4243        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4244        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4245
4246    # Base implementation that excludes safe, zone, and target_type metadata args
4247    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4248        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
4249
4250    # Base implementation that excludes the safe and default_year metadata args
4251    def strtodate_sql(self, expression: exp.StrToDate) -> str:
4252        return self.func("STR_TO_DATE", expression.this, expression.args.get("format"))
4253
4254    def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str:
4255        return self.func(
4256            "PARSE_DATETIME",
4257            expression.this,
4258            expression.args.get("format"),
4259            expression.args.get("zone"),
4260        )
4261
4262    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4263        zone = self.sql(expression, "this")
4264        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
4265
4266    def collate_sql(self, expression: exp.Collate) -> str:
4267        if self.COLLATE_IS_FUNC:
4268            return self.function_fallback_sql(expression)
4269        return self.binary(expression, "COLLATE")
4270
4271    def command_sql(self, expression: exp.Command) -> str:
4272        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
4273
4274    def comment_sql(self, expression: exp.Comment) -> str:
4275        this = self.sql(expression, "this")
4276        kind = expression.args["kind"]
4277        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4278        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4279        expression_sql = self.sql(expression, "expression")
4280        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4281
4282    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4283        this = self.sql(expression, "this")
4284        delete = " DELETE" if expression.args.get("delete") else ""
4285        recompress = self.sql(expression, "recompress")
4286        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4287        to_disk = self.sql(expression, "to_disk")
4288        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4289        to_volume = self.sql(expression, "to_volume")
4290        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4291        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4292
4293    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4294        where = self.sql(expression, "where")
4295        group = self.sql(expression, "group")
4296        aggregates = self.expressions(expression, key="aggregates")
4297        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4298
4299        if not (where or group or aggregates) and len(expression.expressions) == 1:
4300            return f"TTL {self.expressions(expression, flat=True)}"
4301
4302        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4303
4304    def transaction_sql(self, expression: exp.Transaction) -> str:
4305        modes = self.expressions(expression, key="modes")
4306        modes = f" {modes}" if modes else ""
4307        return f"BEGIN{modes}"
4308
4309    def commit_sql(self, expression: exp.Commit) -> str:
4310        chain = expression.args.get("chain")
4311        if chain is not None:
4312            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4313
4314        return f"COMMIT{chain or ''}"
4315
4316    def rollback_sql(self, expression: exp.Rollback) -> str:
4317        savepoint = expression.args.get("savepoint")
4318        savepoint = f" TO {savepoint}" if savepoint else ""
4319        return f"ROLLBACK{savepoint}"
4320
4321    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4322        this = self.sql(expression, "this")
4323
4324        exists = ""
4325        if expression.args.get("exists"):
4326            if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS:
4327                exists = " IF EXISTS"
4328            else:
4329                self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect")
4330
4331        dtype = self.sql(expression, "dtype")
4332        if dtype:
4333            collate = self.sql(expression, "collate")
4334            collate = f" COLLATE {collate}" if collate else ""
4335            using = self.sql(expression, "using")
4336            using = f" USING {using}" if using else ""
4337            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4338            null_constraint = self._alter_column_null_constraint_sql(expression)
4339
4340            return (
4341                f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}"
4342                f"{collate}{using}{null_constraint}"
4343            )
4344
4345        default = self.sql(expression, "default")
4346        if default:
4347            return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}"
4348
4349        comment = self.sql(expression, "comment")
4350        if comment:
4351            return f"ALTER COLUMN{exists} {this} COMMENT {comment}"
4352
4353        visible = expression.args.get("visible")
4354        if visible:
4355            return f"ALTER COLUMN{exists} {this} SET {visible}"
4356
4357        allow_null = expression.args.get("allow_null")
4358        drop = expression.args.get("drop")
4359
4360        if not drop and not allow_null:
4361            self.unsupported("Unsupported ALTER COLUMN syntax")
4362
4363        if allow_null is not None:
4364            keyword = "DROP" if drop else "SET"
4365            return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL"
4366
4367        return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4368
4369    def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str:
4370        allow_null = expression.args.get("allow_null")
4371        if allow_null is None:
4372            return ""
4373
4374        if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY:
4375            self.unsupported("ALTER COLUMN cannot set nullability along with a type")
4376            return ""
4377
4378        return " NULL" if allow_null else " NOT NULL"
4379
4380    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4381        this = self.sql(expression, "this")
4382        rename_from = self.sql(expression, "rename_from")
4383        if rename_from:
4384            if not self.SUPPORTS_CHANGE_COLUMN:
4385                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4386            return f"CHANGE COLUMN {rename_from} {this}"
4387        if not self.SUPPORTS_MODIFY_COLUMN:
4388            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4389        return f"MODIFY COLUMN {this}"
4390
4391    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4392        this = self.sql(expression, "this")
4393
4394        visible = expression.args.get("visible")
4395        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4396
4397        return f"ALTER INDEX {this} {visible_sql}"
4398
4399    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4400        this = self.sql(expression, "this")
4401        if not isinstance(expression.this, exp.Var):
4402            this = f"KEY DISTKEY {this}"
4403        return f"ALTER DISTSTYLE {this}"
4404
4405    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4406        compound = " COMPOUND" if expression.args.get("compound") else ""
4407        this = self.sql(expression, "this")
4408        expressions = self.expressions(expression, flat=True)
4409        expressions = f"({expressions})" if expressions else ""
4410        return f"ALTER{compound} SORTKEY {this or expressions}"
4411
4412    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4413        if not self.RENAME_TABLE_WITH_DB:
4414            # Remove db from tables
4415            expression = expression.transform(
4416                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4417            ).assert_is(exp.AlterRename)
4418        this = self.sql(expression, "this")
4419        to_kw = " TO" if include_to else ""
4420        return f"RENAME{to_kw} {this}"
4421
4422    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4423        exists = " IF EXISTS" if expression.args.get("exists") else ""
4424        old_column = self.sql(expression, "this")
4425        new_column = self.sql(expression, "to")
4426        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
4427
4428    def alterset_sql(self, expression: exp.AlterSet) -> str:
4429        exprs = self.expressions(expression, flat=True)
4430        if self.ALTER_SET_WRAPPED:
4431            exprs = f"({exprs})"
4432
4433        return f"SET {exprs}"
4434
4435    def alter_sql(self, expression: exp.Alter) -> str:
4436        actions = expression.actions
4437
4438        if (
4439            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
4440            and actions
4441            and isinstance(actions[0], exp.ColumnDef)
4442        ):
4443            actions_sql = self.expressions(expression, key="actions", flat=True)
4444            actions_sql = f"ADD {actions_sql}"
4445        else:
4446            actions_list = []
4447            for action in actions:
4448                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4449                    action_sql = self.add_column_sql(action)
4450                else:
4451                    action_sql = self.sql(action)
4452                    if isinstance(action, exp.Query):
4453                        action_sql = f"AS {action_sql}"
4454
4455                actions_list.append(action_sql)
4456
4457            actions_sql = self.format_args(*actions_list).lstrip("\n")
4458
4459        iceberg = (
4460            "ICEBERG "
4461            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4462            else ""
4463        )
4464        exists = " IF EXISTS" if expression.args.get("exists") else ""
4465        on_cluster = self.sql(expression, "cluster")
4466        on_cluster = f" {on_cluster}" if on_cluster else ""
4467        only = " ONLY" if expression.args.get("only") else ""
4468        options = expression.args.get("options") or []
4469        options_sql = self.expressions(
4470            sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)]
4471        )
4472        options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql
4473        partitions_sql = self.expressions(
4474            sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)],
4475            sep=" ",
4476            flat=True,
4477        )
4478        if partitions_sql and (actions_sql or options_sql):
4479            partitions_sql = f" {partitions_sql}"
4480        kind = self.sql(expression, "kind")
4481        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4482        check = " WITH CHECK" if expression.args.get("check") else ""
4483        cascade = (
4484            " CASCADE"
4485            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4486            else ""
4487        )
4488        this = self.sql(expression, "this")
4489        this = f" {this}" if this else ""
4490
4491        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}"
4492
4493    def altersession_sql(self, expression: exp.AlterSession) -> str:
4494        items_sql = self.expressions(expression, flat=True)
4495        keyword = "UNSET" if expression.args.get("unset") else "SET"
4496        return f"{keyword} {items_sql}"
4497
4498    def add_column_sql(self, expression: exp.Expr) -> str:
4499        sql = self.sql(expression)
4500        if isinstance(expression, exp.Schema):
4501            column_text = " COLUMNS"
4502        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4503            column_text = " COLUMN"
4504        else:
4505            column_text = ""
4506
4507        return f"ADD{column_text} {sql}"
4508
4509    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4510        expressions = self.expressions(expression)
4511        exists = " IF EXISTS " if expression.args.get("exists") else " "
4512        return f"DROP{exists}{expressions}"
4513
4514    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4515        return "DROP PRIMARY KEY"
4516
4517    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4518        return f"ADD {self.expressions(expression, indent=False)}"
4519
4520    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4521        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4522        location = self.sql(expression, "location")
4523        location = f" {location}" if location else ""
4524        return f"ADD {exists}{self.sql(expression.this)}{location}"
4525
4526    def distinct_sql(self, expression: exp.Distinct) -> str:
4527        this = self.expressions(expression, flat=True)
4528
4529        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4530            case = exp.case()
4531            for arg in expression.expressions:
4532                case = case.when(arg.is_(exp.null()), exp.null())
4533            this = self.sql(case.else_(f"({this})"))
4534
4535        this = f" {this}" if this else ""
4536
4537        on = self.sql(expression, "on")
4538        on = f" ON {on}" if on else ""
4539        return f"DISTINCT{this}{on}"
4540
4541    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4542        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4543
4544    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4545        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4546
4547    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4548        this_sql = self.sql(expression, "this")
4549        expression_sql = self.sql(expression, "expression")
4550        kind = "MAX" if expression.args.get("max") else "MIN"
4551        return f"{this_sql} HAVING {kind} {expression_sql}"
4552
4553    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4554        return self._truncated_div_sql(expression.this, expression.expression)
4555
4556    def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str:
4557        div: exp.Expr = exp.Div(this=this, expression=divisor)
4558        if self.CAST_TO_INT_ROUNDS:
4559            div = exp.Trunc(this=div)
4560
4561        return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT)))
4562
4563    def dpipe_sql(self, expression: exp.DPipe) -> str:
4564        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4565            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4566        return self.binary(expression, "||")
4567
4568    def div_sql(self, expression: exp.Div) -> str:
4569        l, r = expression.left, expression.right
4570
4571        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4572            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4573
4574        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4575            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4576                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4577
4578        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4579            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4580                # Native integer division depends on the target's operand types, which may differ
4581                # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are
4582                # integer literals or casts
4583                if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and (
4584                    expression.args.get("safe") or not self.SAFE_INT_DIVISION
4585                ):
4586                    return self.sql(exp.IntDiv(this=l, expression=r))
4587                return self._truncated_div_sql(l, r)
4588
4589        return self.binary(expression, "/")
4590
4591    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4592        n = exp._wrap(expression.this, exp.Binary)
4593        d = exp._wrap(expression.expression, exp.Binary)
4594        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4595
4596    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4597        return self.binary(expression, "OVERLAPS")
4598
4599    def distance_sql(self, expression: exp.Distance) -> str:
4600        return self.binary(expression, "<->")
4601
4602    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4603        return self.binary(expression, "<<->>")
4604
4605    def dot_sql(self, expression: exp.Dot) -> str:
4606        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4607
4608    def eq_sql(self, expression: exp.EQ) -> str:
4609        return self.binary(expression, "=")
4610
4611    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4612        return self.binary(expression, ":=")
4613
4614    def escape_sql(self, expression: exp.Escape) -> str:
4615        this = expression.this
4616        if (
4617            isinstance(this, (exp.Like, exp.ILike))
4618            and isinstance(this.expression, (exp.All, exp.Any))
4619            and not self.SUPPORTS_LIKE_QUANTIFIERS
4620        ):
4621            return self._like_sql(this, escape=expression)
4622        return self.binary(expression, "ESCAPE")
4623
4624    def glob_sql(self, expression: exp.Glob) -> str:
4625        return self.binary(expression, "GLOB")
4626
4627    def gt_sql(self, expression: exp.GT) -> str:
4628        return self.binary(expression, ">")
4629
4630    def gte_sql(self, expression: exp.GTE) -> str:
4631        return self.binary(expression, ">=")
4632
4633    def is_sql(self, expression: exp.Is) -> str:
4634        negate = expression.args.get("negate")
4635        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4636            positive = bool(expression.expression.this) != bool(negate)
4637            return self.sql(expression.this if positive else exp.not_(expression.this))
4638        return self.binary(expression, "IS NOT" if negate else "IS")
4639
4640    def _like_sql(
4641        self,
4642        expression: exp.Like | exp.ILike,
4643        escape: exp.Escape | None = None,
4644    ) -> str:
4645        this = expression.this
4646        rhs = expression.expression
4647
4648        if isinstance(expression, exp.Like):
4649            exp_class: type[exp.Like | exp.ILike] = exp.Like
4650            op = "LIKE"
4651        else:
4652            exp_class = exp.ILike
4653            op = "ILIKE"
4654
4655        if expression.args.get("negate"):
4656            op = f"NOT {op}"
4657
4658        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4659            exprs = rhs.this.unnest()
4660
4661            if isinstance(exprs, exp.Tuple):
4662                exprs = exprs.expressions
4663            else:
4664                exprs = [exprs]
4665
4666            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4667
4668            def _make_like(expr: exp.Expression) -> exp.Expression:
4669                like: exp.Expression = exp_class(
4670                    this=this, expression=expr, negate=expression.args.get("negate")
4671                )
4672                if escape:
4673                    like = exp.Escape(this=like, expression=escape.expression.copy())
4674                return like
4675
4676            like_expr: exp.Expr = _make_like(exprs[0])
4677            for expr in exprs[1:]:
4678                like_expr = connective(like_expr, _make_like(expr), copy=False)
4679
4680            parent = escape.parent if escape else expression.parent
4681            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4682                parent, exp.Condition
4683            ):
4684                like_expr = exp.paren(like_expr, copy=False)
4685
4686            return self.sql(like_expr)
4687
4688        return self.binary(expression, op)
4689
4690    def like_sql(self, expression: exp.Like) -> str:
4691        return self._like_sql(expression)
4692
4693    def ilike_sql(self, expression: exp.ILike) -> str:
4694        return self._like_sql(expression)
4695
4696    def match_sql(self, expression: exp.Match) -> str:
4697        if expression.args.get("kind") or expression.args.get("analyzer"):
4698            self.unsupported("MATCH variants and USING ANALYZER are not supported in this dialect")
4699        return self.binary(expression, "MATCH")
4700
4701    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4702        return self.binary(expression, "SIMILAR TO")
4703
4704    def lt_sql(self, expression: exp.LT) -> str:
4705        return self.binary(expression, "<")
4706
4707    def lte_sql(self, expression: exp.LTE) -> str:
4708        return self.binary(expression, "<=")
4709
4710    def mod_sql(self, expression: exp.Mod) -> str:
4711        this = self.sql(expression, "this")
4712        expr = self.sql(expression, "expression")
4713        sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}"
4714
4715        parent = expression.parent
4716        if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression:
4717            return f"({sql})"
4718
4719        return sql
4720
4721    def mul_sql(self, expression: exp.Mul) -> str:
4722        return self.binary(expression, "*")
4723
4724    def neq_sql(self, expression: exp.NEQ) -> str:
4725        return self.binary(expression, "<>")
4726
4727    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4728        return self.binary(expression, "IS NOT DISTINCT FROM")
4729
4730    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4731        return self.binary(expression, "IS DISTINCT FROM")
4732
4733    def sub_sql(self, expression: exp.Sub) -> str:
4734        return self.binary(expression, "-")
4735
4736    def trycast_sql(self, expression: exp.TryCast) -> str:
4737        return self.cast_sql(expression, safe_prefix="TRY_")
4738
4739    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4740        return self.cast_sql(expression)
4741
4742    def try_sql(self, expression: exp.Try) -> str:
4743        if not self.TRY_SUPPORTED:
4744            self.unsupported("Unsupported TRY function")
4745            return self.sql(expression, "this")
4746
4747        return self.func("TRY", expression.this)
4748
4749    def log_sql(self, expression: exp.Log) -> str:
4750        this = expression.this
4751        expr = expression.expression
4752
4753        if self.dialect.LOG_BASE_FIRST is False:
4754            this, expr = expr, this
4755        elif self.dialect.LOG_BASE_FIRST is None and expr:
4756            if this.name in ("2", "10"):
4757                return self.func(f"LOG{this.name}", expr)
4758
4759            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4760
4761        return self.func("LOG", this, expr)
4762
4763    def use_sql(self, expression: exp.Use) -> str:
4764        kind = self.sql(expression, "kind")
4765        kind = f" {kind}" if kind else ""
4766        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4767        this = f" {this}" if this else ""
4768        return f"USE{kind}{this}"
4769
4770    def binary(self, expression: exp.Binary, op: str) -> str:
4771        sqls: list[str] = []
4772        stack: list[None | str | exp.Expr] = [expression]
4773        binary_type = type(expression)
4774
4775        while stack:
4776            node = stack.pop()
4777
4778            if type(node) is binary_type:
4779                op_func = node.args.get("operator")
4780                if op_func:
4781                    op = f"OPERATOR({self.sql(op_func)})"
4782
4783                stack.append(node.args.get("expression"))
4784                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4785                stack.append(node.args.get("this"))
4786            else:
4787                sqls.append(self.sql(node))
4788
4789        return "".join(sqls)
4790
4791    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4792        to_clause = self.sql(expression, "to")
4793        if to_clause:
4794            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4795
4796        return self.function_fallback_sql(expression)
4797
4798    def function_fallback_sql(self, expression: exp.Func) -> str:
4799        args = []
4800
4801        for key in expression.arg_types:
4802            arg_value = expression.args.get(key)
4803
4804            if isinstance(arg_value, list):
4805                for value in arg_value:
4806                    args.append(value)
4807            elif arg_value is not None:
4808                args.append(arg_value)
4809
4810        return self.func(self.function_name(expression), *args)
4811
4812    def function_name(self, expression: exp.Func) -> str:
4813        name_key = self.dialect.ORIGINAL_NAME_META_KEY
4814        return (expression.meta_get(name_key) if name_key else None) or expression.sql_name()
4815
4816    def func(
4817        self,
4818        name: str,
4819        *args: t.Any,
4820        prefix: str = "(",
4821        suffix: str = ")",
4822        normalize: bool = True,
4823    ) -> str:
4824        name = self.normalize_func(name) if normalize else name
4825        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4826
4827    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4828        arg_sqls = tuple(
4829            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4830        )
4831        if self.pretty and self.too_wide(arg_sqls):
4832            return self.indent(
4833                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4834            )
4835        return sep.join(arg_sqls)
4836
4837    def too_wide(self, args: t.Iterable) -> bool:
4838        return sum(len(arg) for arg in args) > self.max_text_width
4839
4840    def format_time(
4841        self,
4842        expression: exp.Expr,
4843        inverse_time_mapping: dict[str, str] | None = None,
4844        inverse_time_trie: dict | None = None,
4845    ) -> str | None:
4846        return format_time(
4847            self.sql(expression, "format"),
4848            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4849            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4850        )
4851
4852    def expressions(
4853        self,
4854        expression: exp.Expr | None = None,
4855        key: str | None = None,
4856        sqls: t.Collection[str | exp.Expr] | None = None,
4857        flat: bool = False,
4858        indent: bool = True,
4859        skip_first: bool = False,
4860        skip_last: bool = False,
4861        sep: str = ", ",
4862        prefix: str = "",
4863        dynamic: bool = False,
4864        new_line: bool = False,
4865    ) -> str:
4866        expressions = expression.args.get(key or "expressions") if expression else sqls
4867
4868        if not expressions:
4869            return ""
4870
4871        if flat:
4872            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4873
4874        num_sqls = len(expressions)
4875        result_sqls = []
4876
4877        for i, e in enumerate(expressions):
4878            sql = self.sql(e, comment=False)
4879            if not sql:
4880                continue
4881
4882            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4883
4884            if self.pretty:
4885                if self.leading_comma:
4886                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4887                else:
4888                    result_sqls.append(
4889                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4890                    )
4891            else:
4892                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4893
4894        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4895            if new_line:
4896                result_sqls.insert(0, "")
4897                result_sqls.append("")
4898            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4899        else:
4900            result_sql = "".join(result_sqls)
4901
4902        return (
4903            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4904            if indent
4905            else result_sql
4906        )
4907
4908    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4909        flat = flat or isinstance(expression.parent, exp.Properties)
4910        expressions_sql = self.expressions(expression, flat=flat)
4911        if flat:
4912            return f"{op} {expressions_sql}"
4913        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4914
4915    def naked_property(self, expression: exp.Property) -> str:
4916        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4917        if not property_name:
4918            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4919        return f"{property_name} {self.sql(expression, 'this')}"
4920
4921    def tag_sql(self, expression: exp.Tag) -> str:
4922        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4923
4924    def token_sql(self, token_type: TokenType) -> str:
4925        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4926
4927    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4928        this = self.sql(expression, "this")
4929        expressions = self.no_identify(self.expressions, expression)
4930        expressions = (
4931            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4932        )
4933        return f"{this}{expressions}" if expressions.strip() != "" else this
4934
4935    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
4936        return self.expressions(expression, flat=True)
4937
4938    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
4939        params = self.no_identify(self.expressions, expression, flat=True)
4940        body = self.sql(expression, "this")
4941        prefix = "TABLE " if expression.args.get("is_table") else ""
4942        return f"({params}) AS {prefix}{body}"
4943
4944    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4945        this = self.sql(expression, "this")
4946        expressions = self.expressions(expression, flat=True)
4947        return f"{this}({expressions})"
4948
4949    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4950        return self.binary(expression, "=>")
4951
4952    def when_sql(self, expression: exp.When) -> str:
4953        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4954        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4955        condition = self.sql(expression, "condition")
4956        condition = f" AND {condition}" if condition else ""
4957
4958        then_expression = expression.args.get("then")
4959        if isinstance(then_expression, exp.Insert):
4960            this = self.sql(then_expression, "this")
4961            this = f"INSERT {this}" if this else "INSERT"
4962            then = self.sql(then_expression, "expression")
4963            then = f"{this} VALUES {then}" if then else this
4964        elif isinstance(then_expression, exp.Update):
4965            if isinstance(then_expression.args.get("expressions"), exp.Star):
4966                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4967            else:
4968                expressions_sql = self.expressions(then_expression)
4969                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4970        else:
4971            then = self.sql(then_expression)
4972
4973        if isinstance(then_expression, (exp.Insert, exp.Update)):
4974            where = self.sql(then_expression, "where")
4975            if where and not self.SUPPORTS_MERGE_WHERE:
4976                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4977                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4978                where = ""
4979            then = f"{then}{where}"
4980        return f"WHEN {matched}{source}{condition} THEN {then}"
4981
4982    def whens_sql(self, expression: exp.Whens) -> str:
4983        return self.expressions(expression, sep=" ", indent=False)
4984
4985    def merge_sql(self, expression: exp.Merge) -> str:
4986        table = expression.this
4987        table_alias = ""
4988
4989        hints = table.args.get("hints")
4990        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4991            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4992            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4993
4994        this = self.sql(table)
4995        using = f"USING {self.sql(expression, 'using')}"
4996        whens = self.sql(expression, "whens")
4997
4998        on = self.sql(expression, "on")
4999        on = f"ON {on}" if on else ""
5000
5001        if not on:
5002            on = self.expressions(expression, key="using_cond")
5003            on = f"USING ({on})" if on else ""
5004
5005        returning = self.sql(expression, "returning")
5006        if returning:
5007            whens = f"{whens}{returning}"
5008
5009        sep = self.sep()
5010
5011        return self.prepend_ctes(
5012            expression,
5013            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
5014        )
5015
5016    @unsupported_args("format")
5017    def tochar_sql(self, expression: exp.ToChar) -> str:
5018        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
5019
5020    @unsupported_args("default")
5021    def tonumber_sql(self, expression: exp.ToNumber) -> str:
5022        if not self.SUPPORTS_TO_NUMBER:
5023            self.unsupported("Unsupported TO_NUMBER function")
5024            return self._tonumber_cast_sql(expression)
5025
5026        fmt = expression.args.get("format")
5027        if not fmt:
5028            self.unsupported("Conversion format is required for TO_NUMBER")
5029            return self._tonumber_cast_sql(expression)
5030
5031        return self.func("TO_NUMBER", expression.this, fmt)
5032
5033    def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str:
5034        if expression.args.get("safe"):
5035            return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE")))
5036        return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
5037
5038    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
5039        this = self.sql(expression, "this")
5040        kind = self.sql(expression, "kind")
5041        settings_sql = self.expressions(expression, key="settings", sep=" ")
5042        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
5043        return f"{this}({kind}{args})"
5044
5045    def dictrange_sql(self, expression: exp.DictRange) -> str:
5046        this = self.sql(expression, "this")
5047        max = self.sql(expression, "max")
5048        min = self.sql(expression, "min")
5049        return f"{this}(MIN {min} MAX {max})"
5050
5051    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
5052        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
5053
5054    def aggregatekeyproperty_sql(self, expression: exp.AggregateKeyProperty) -> str:
5055        return f"AGGREGATE KEY ({self.expressions(expression, flat=True)})"
5056
5057    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
5058        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
5059
5060    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
5061    def uniquekeyproperty_sql(
5062        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
5063    ) -> str:
5064        return f"{prefix} ({self.expressions(expression, flat=True)})"
5065
5066    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
5067    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
5068        expressions = self.expressions(expression, flat=True)
5069        expressions = f" {self.wrap(expressions)}" if expressions else ""
5070        buckets = self.sql(expression, "buckets")
5071        kind = self.sql(expression, "kind")
5072        buckets = f" BUCKETS {buckets}" if buckets else ""
5073        order = self.sql(expression, "order")
5074        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
5075
5076    def oncluster_sql(self, expression: exp.OnCluster) -> str:
5077        return ""
5078
5079    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
5080        expressions = self.expressions(expression, key="expressions", flat=True)
5081        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
5082        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
5083        buckets = self.sql(expression, "buckets")
5084        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
5085
5086    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
5087        this = self.sql(expression, "this")
5088        having = self.sql(expression, "having")
5089
5090        if having:
5091            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
5092
5093        return self.func("ANY_VALUE", this)
5094
5095    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
5096        transform = self.func("TRANSFORM", *expression.expressions)
5097        row_format_before = self.sql(expression, "row_format_before")
5098        row_format_before = f" {row_format_before}" if row_format_before else ""
5099        record_writer = self.sql(expression, "record_writer")
5100        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
5101        using = f" USING {self.sql(expression, 'command_script')}"
5102        schema = self.sql(expression, "schema")
5103        schema = f" AS {schema}" if schema else ""
5104        row_format_after = self.sql(expression, "row_format_after")
5105        row_format_after = f" {row_format_after}" if row_format_after else ""
5106        record_reader = self.sql(expression, "record_reader")
5107        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
5108        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
5109
5110    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
5111        key_block_size = self.sql(expression, "key_block_size")
5112        if key_block_size:
5113            return f"KEY_BLOCK_SIZE = {key_block_size}"
5114
5115        using = self.sql(expression, "using")
5116        if using:
5117            return f"USING {using}"
5118
5119        parser = self.sql(expression, "parser")
5120        if parser:
5121            return f"WITH PARSER {parser}"
5122
5123        comment = self.sql(expression, "comment")
5124        if comment:
5125            return f"COMMENT {comment}"
5126
5127        visible = expression.args.get("visible")
5128        if visible is not None:
5129            return "VISIBLE" if visible else "INVISIBLE"
5130
5131        engine_attr = self.sql(expression, "engine_attr")
5132        if engine_attr:
5133            return f"ENGINE_ATTRIBUTE = {engine_attr}"
5134
5135        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
5136        if secondary_engine_attr:
5137            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
5138
5139        self.unsupported("Unsupported index constraint option.")
5140        return ""
5141
5142    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
5143        enforced = " ENFORCED" if expression.args.get("enforced") else ""
5144        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
5145
5146    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
5147        kind = self.sql(expression, "kind")
5148        kind = f"{kind} INDEX" if kind else "INDEX"
5149        this = self.sql(expression, "this")
5150        this = f" {this}" if this else ""
5151        index_type = self.sql(expression, "index_type")
5152        index_type = f" USING {index_type}" if index_type else ""
5153        expressions = self.expressions(expression, flat=True)
5154        expressions = f" ({expressions})" if expressions else ""
5155        options = self.expressions(expression, key="options", sep=" ")
5156        options = f" {options}" if options else ""
5157        return f"{kind}{this}{index_type}{expressions}{options}"
5158
5159    def nvl2_sql(self, expression: exp.Nvl2) -> str:
5160        if self.NVL2_SUPPORTED:
5161            return self.function_fallback_sql(expression)
5162
5163        case = exp.Case().when(
5164            expression.this.is_(exp.null()).not_(copy=False),
5165            expression.args["true"],
5166            copy=False,
5167        )
5168        else_cond = expression.args.get("false")
5169        if else_cond:
5170            case.else_(else_cond, copy=False)
5171
5172        return self.sql(case)
5173
5174    def nthvalue_sql(self, expression: exp.NthValue) -> str:
5175        if expression.args.get("from_first") is False:
5176            self.unsupported("NTH_VALUE FROM LAST is not supported")
5177
5178        return self.function_fallback_sql(expression)
5179
5180    def comprehension_sql(self, expression: exp.Comprehension) -> str:
5181        this = self.sql(expression, "this")
5182        expr = self.sql(expression, "expression")
5183        position = self.sql(expression, "position")
5184        position = f", {position}" if position else ""
5185        iterator = self.sql(expression, "iterator")
5186        condition = self.sql(expression, "condition")
5187        condition = f" IF {condition}" if condition else ""
5188        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
5189
5190    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
5191        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
5192
5193    def opclass_sql(self, expression: exp.Opclass) -> str:
5194        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
5195
5196    def _ml_sql(self, expression: exp.Func, name: str) -> str:
5197        model = self.sql(expression, "this")
5198        model = f"MODEL {model}"
5199        expr = expression.expression
5200        if expr:
5201            expr_sql = self.sql(expression, "expression")
5202            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
5203        else:
5204            expr_sql = None
5205
5206        parameters = self.sql(expression, "params_struct") or None
5207
5208        return self.func(name, model, expr_sql, parameters)
5209
5210    def predict_sql(self, expression: exp.Predict) -> str:
5211        return self._ml_sql(expression, "PREDICT")
5212
5213    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
5214        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
5215        return self._ml_sql(expression, name)
5216
5217    def generatetext_sql(self, expression: exp.GenerateText) -> str:
5218        return self._ml_sql(expression, "GENERATE_TEXT")
5219
5220    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
5221        return self._ml_sql(expression, "GENERATE_TABLE")
5222
5223    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
5224        return self._ml_sql(expression, "GENERATE_BOOL")
5225
5226    def generateint_sql(self, expression: exp.GenerateInt) -> str:
5227        return self._ml_sql(expression, "GENERATE_INT")
5228
5229    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
5230        return self._ml_sql(expression, "GENERATE_DOUBLE")
5231
5232    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
5233        return self._ml_sql(expression, "TRANSLATE")
5234
5235    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
5236        return self._ml_sql(expression, "FORECAST")
5237
5238    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
5239        this_sql = self.sql(expression, "this")
5240        if isinstance(expression.this, exp.Table):
5241            this_sql = f"TABLE {this_sql}"
5242
5243        return self.func(
5244            "FORECAST",
5245            this_sql,
5246            expression.args.get("data_col"),
5247            expression.args.get("timestamp_col"),
5248            expression.args.get("model"),
5249            expression.args.get("id_cols"),
5250            expression.args.get("horizon"),
5251            expression.args.get("forecast_end_timestamp"),
5252            expression.args.get("confidence_level"),
5253            expression.args.get("output_historical_time_series"),
5254            expression.args.get("context_window"),
5255        )
5256
5257    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
5258        this_sql = self.sql(expression, "this")
5259        if isinstance(expression.this, exp.Table):
5260            this_sql = f"TABLE {this_sql}"
5261
5262        return self.func(
5263            "FEATURES_AT_TIME",
5264            this_sql,
5265            expression.args.get("time"),
5266            expression.args.get("num_rows"),
5267            expression.args.get("ignore_feature_nulls"),
5268        )
5269
5270    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5271        this_sql = self.sql(expression, "this")
5272        if isinstance(expression.this, exp.Table):
5273            this_sql = f"TABLE {this_sql}"
5274
5275        query_table = self.sql(expression, "query_table")
5276        if isinstance(expression.args["query_table"], exp.Table):
5277            query_table = f"TABLE {query_table}"
5278
5279        return self.func(
5280            "VECTOR_SEARCH",
5281            this_sql,
5282            expression.args.get("column_to_search"),
5283            query_table,
5284            expression.args.get("query_column_to_search"),
5285            expression.args.get("top_k"),
5286            expression.args.get("distance_type"),
5287            expression.args.get("options"),
5288        )
5289
5290    def forin_sql(self, expression: exp.ForIn) -> str:
5291        this = self.sql(expression, "this")
5292        expression_sql = self.sql(expression, "expression")
5293        return f"FOR {this} DO {expression_sql}"
5294
5295    @unsupported_args("partition_start", "partition_end", "force", "mode")
5296    def refresh_sql(self, expression: exp.Refresh) -> str:
5297        this = self.sql(expression, "this")
5298        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5299        return f"REFRESH {kind}{this}"
5300
5301    def toarray_sql(self, expression: exp.ToArray) -> str:
5302        arg = expression.this
5303        if not arg.type:
5304            import sqlglot.optimizer.annotate_types
5305
5306            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5307
5308        if arg.is_type(exp.DType.ARRAY):
5309            return self.sql(arg)
5310
5311        cond_for_null = arg.is_(exp.null())
5312        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5313
5314    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5315        this = expression.this
5316        time_format = self.format_time(expression)
5317
5318        if time_format:
5319            return self.sql(
5320                exp.cast(
5321                    exp.StrToTime(this=this, format=expression.args["format"]),
5322                    exp.DType.TIME,
5323                )
5324            )
5325
5326        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5327            return self.sql(this)
5328
5329        return self.sql(exp.cast(this, exp.DType.TIME))
5330
5331    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5332        this = expression.this
5333        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5334            return self.sql(this)
5335
5336        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5337
5338    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5339        this = expression.this
5340        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5341            return self.sql(this)
5342
5343        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5344
5345    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5346        this = expression.this
5347        time_format = self.format_time(expression)
5348        safe = expression.args.get("safe")
5349        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5350            return self.sql(
5351                exp.cast(
5352                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5353                    exp.DType.DATE,
5354                )
5355            )
5356
5357        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5358            return self.sql(this)
5359
5360        if safe:
5361            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5362
5363        return self.sql(exp.cast(this, exp.DType.DATE))
5364
5365    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5366        return self.sql(
5367            exp.func(
5368                "DATEDIFF",
5369                expression.this,
5370                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5371                "day",
5372            )
5373        )
5374
5375    def lastday_sql(self, expression: exp.LastDay) -> str:
5376        if self.LAST_DAY_SUPPORTS_DATE_PART:
5377            return self.function_fallback_sql(expression)
5378
5379        unit = expression.args.get("unit")
5380        if unit and unit.name.upper() != "MONTH":
5381            self.unsupported("Date parts are not supported in LAST_DAY.")
5382
5383        return self.func("LAST_DAY", expression.this)
5384
5385    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5386        import sqlglot.dialects.dialect
5387
5388        return self.func(
5389            "DATE_ADD",
5390            expression.this,
5391            expression.expression,
5392            sqlglot.dialects.dialect.unit_to_str(expression),
5393        )
5394
5395    def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str:
5396        this = expression.this
5397        position = expression.args["position"]
5398        offset = index_offset - (expression.args.get("offset") or 0)
5399
5400        if offset:
5401            if position.is_int:
5402                value = position.to_py()
5403                if value >= 0:
5404                    position = exp.Literal.number(value + offset)
5405                elif offset < 0 and value == -1:
5406                    # 1-based -1 appends, which a 0-based position can only express as the size
5407                    position = exp.ArraySize(this=this.copy())
5408                else:
5409                    # Negative positions count from the end, so they shift in the opposite
5410                    # direction, e.g. 0-based -1 (before the last element) is 1-based -2
5411                    position = exp.Literal.number(value - offset)
5412            else:
5413                self.unsupported("ARRAY_INSERT position can only be converted if it's a literal")
5414
5415        return self.func("ARRAY_INSERT", this, position, expression.expression)
5416
5417    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5418        if self.CAN_IMPLEMENT_ARRAY_ANY:
5419            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5420            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5421            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5422            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5423
5424        import sqlglot.dialects.dialect
5425
5426        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5427        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5428            self.unsupported("ARRAY_ANY is unsupported")
5429
5430        return self.function_fallback_sql(expression)
5431
5432    def struct_sql(self, expression: exp.Struct) -> str:
5433        expression.set(
5434            "expressions",
5435            [
5436                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5437                if isinstance(e, exp.PropertyEQ)
5438                else e
5439                for e in expression.expressions
5440            ],
5441        )
5442
5443        return self.function_fallback_sql(expression)
5444
5445    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5446        low = self.sql(expression, "this")
5447        high = self.sql(expression, "expression")
5448
5449        return f"{low} TO {high}"
5450
5451    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5452        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5453        tables = f" {self.expressions(expression)}"
5454
5455        exists = " IF EXISTS" if expression.args.get("exists") else ""
5456
5457        on_cluster = self.sql(expression, "cluster")
5458        on_cluster = f" {on_cluster}" if on_cluster else ""
5459
5460        identity = self.sql(expression, "identity")
5461        identity = f" {identity} IDENTITY" if identity else ""
5462
5463        option = self.sql(expression, "option")
5464        option = f" {option}" if option else ""
5465
5466        partition = self.sql(expression, "partition")
5467        partition = f" {partition}" if partition else ""
5468
5469        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5470
5471    # This transpiles T-SQL's CONVERT function
5472    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
5473    def convert_sql(self, expression: exp.Convert) -> str:
5474        to = expression.this
5475        value = expression.expression
5476        style = expression.args.get("style")
5477        safe = expression.args.get("safe")
5478        strict = expression.args.get("strict")
5479
5480        if not to or not value:
5481            return ""
5482
5483        # Retrieve length of datatype and override to default if not specified
5484        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5485            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5486
5487        transformed: exp.Expr | None = None
5488        cast = exp.Cast if strict else exp.TryCast
5489
5490        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5491        if isinstance(style, exp.Literal) and style.is_int:
5492            import sqlglot.dialects.tsql
5493
5494            style_value = style.name
5495            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5496            if not converted_style:
5497                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5498
5499            fmt = exp.Literal.string(converted_style)
5500
5501            if to.this == exp.DType.DATE:
5502                transformed = exp.StrToDate(this=value, format=fmt)
5503            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5504                transformed = exp.StrToTime(this=value, format=fmt)
5505            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5506                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5507            elif to.this == exp.DType.TEXT:
5508                transformed = exp.TimeToStr(this=value, format=fmt)
5509
5510        if not transformed:
5511            transformed = cast(this=value, to=to, safe=safe)
5512
5513        return self.sql(transformed)
5514
5515    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
5516        this = expression.this
5517        if isinstance(this, exp.JSONPathWildcard):
5518            this = self.json_path_part(this)
5519            return f".{this}" if this else ""
5520
5521        quoted = expression.args.get("quoted")
5522        if not (
5523            quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
5524        ) and self.SAFE_JSON_PATH_KEY_RE.match(this):
5525            return f".{this}"
5526
5527        this = self.json_path_part(this)
5528
5529        return (
5530            f"[{this}]"
5531            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5532            else f".{this}"
5533        )
5534
5535    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5536        this = self.json_path_part(expression.this)
5537        return f"[{this}]" if this else ""
5538
5539    def _simplify_unless_literal(self, expression: E) -> E:
5540        if not isinstance(expression, exp.Literal):
5541            import sqlglot.optimizer.simplify
5542
5543            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5544
5545        return expression
5546
5547    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5548        this = expression.this
5549        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5550            self.unsupported(
5551                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5552            )
5553            return self.sql(this)
5554
5555        if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5556            if self.IGNORE_NULLS_BEFORE_ORDER:
5557                from sqlglot.optimizer.scope import find_all_in_scope
5558
5559                # The first modifier here will be the one closest to the AggFunc's arg
5560                mods = sorted(
5561                    find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit),
5562                    key=lambda x: (
5563                        0
5564                        if isinstance(x, exp.HavingMax)
5565                        else (1 if isinstance(x, exp.Order) else 2)
5566                    ),
5567                )
5568
5569                if mods:
5570                    mod = mods[0]
5571                    this = expression.__class__(this=mod.this.copy())
5572                    this.meta["inline"] = True
5573                    mod.this.replace(this)
5574                    return self.sql(expression.this)
5575
5576            agg_func = expression.find(exp.AggFunc)
5577
5578            if agg_func:
5579                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5580                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5581
5582        return f"{self.sql(expression, 'this')} {text}"
5583
5584    def _replace_line_breaks(self, string: str) -> str:
5585        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5586        if self.pretty:
5587            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5588        return string
5589
5590    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5591        option = self.sql(expression, "this")
5592
5593        if expression.expressions:
5594            upper = option.upper()
5595
5596            # Snowflake FILE_FORMAT options are separated by whitespace
5597            sep = " " if upper == "FILE_FORMAT" else ", "
5598
5599            # Databricks copy/format options do not set their list of values with EQ
5600            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5601            values = self.expressions(expression, flat=True, sep=sep)
5602            return f"{option}{op}({values})"
5603
5604        value = self.sql(expression, "expression")
5605
5606        if not value:
5607            return option
5608
5609        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5610
5611        return f"{option}{op}{value}"
5612
5613    def credentials_sql(self, expression: exp.Credentials) -> str:
5614        cred_expr = expression.args.get("credentials")
5615        if isinstance(cred_expr, exp.Literal):
5616            # Redshift case: CREDENTIALS <string>
5617            credentials = self.sql(expression, "credentials")
5618            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5619        else:
5620            # Snowflake case: CREDENTIALS = (...)
5621            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5622            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5623
5624        storage = self.sql(expression, "storage")
5625        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5626
5627        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5628        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5629
5630        iam_role = self.sql(expression, "iam_role")
5631        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5632
5633        region = self.sql(expression, "region")
5634        region = f" REGION {region}" if region else ""
5635
5636        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5637
5638    def copy_sql(self, expression: exp.Copy) -> str:
5639        this = self.sql(expression, "this")
5640        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5641
5642        credentials = self.sql(expression, "credentials")
5643        credentials = self.seg(credentials) if credentials else ""
5644        files = self.expressions(expression, key="files", flat=True)
5645        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5646
5647        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5648        params = self.expressions(
5649            expression,
5650            key="params",
5651            sep=sep,
5652            new_line=True,
5653            skip_last=True,
5654            skip_first=True,
5655            indent=self.COPY_PARAMS_ARE_WRAPPED,
5656        )
5657
5658        if params:
5659            if self.COPY_PARAMS_ARE_WRAPPED:
5660                params = f" WITH ({params})"
5661            elif not self.pretty and (files or credentials):
5662                params = f" {params}"
5663
5664        return f"COPY{this}{kind} {files}{credentials}{params}"
5665
5666    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5667        return ""
5668
5669    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5670        on_sql = "ON" if expression.args.get("on") else "OFF"
5671        filter_col: str | None = self.sql(expression, "filter_column")
5672        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5673        retention_period: str | None = self.sql(expression, "retention_period")
5674        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5675
5676        if filter_col or retention_period:
5677            on_sql = self.func("ON", filter_col, retention_period)
5678
5679        return f"DATA_DELETION={on_sql}"
5680
5681    def maskingpolicycolumnconstraint_sql(
5682        self, expression: exp.MaskingPolicyColumnConstraint
5683    ) -> str:
5684        this = self.sql(expression, "this")
5685        expressions = self.expressions(expression, flat=True)
5686        expressions = f" USING ({expressions})" if expressions else ""
5687        return f"MASKING POLICY {this}{expressions}"
5688
5689    def gapfill_sql(self, expression: exp.GapFill) -> str:
5690        this = self.sql(expression, "this")
5691        this = f"TABLE {this}"
5692        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5693
5694    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5695        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5696
5697    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5698        this = self.sql(expression, "this")
5699        expr = expression.expression
5700
5701        if isinstance(expr, exp.Func):
5702            # T-SQL's CLR functions are case sensitive
5703            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5704        else:
5705            expr = self.sql(expression, "expression")
5706
5707        return self.scope_resolution(expr, this)
5708
5709    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5710        if self.PARSE_JSON_NAME is None:
5711            return self.sql(expression.this)
5712
5713        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5714
5715    def rand_sql(self, expression: exp.Rand) -> str:
5716        lower = self.sql(expression, "lower")
5717        upper = self.sql(expression, "upper")
5718
5719        if lower and upper:
5720            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5721        return self.func("RAND", expression.this)
5722
5723    def changes_sql(self, expression: exp.Changes) -> str:
5724        information = self.sql(expression, "information")
5725        information = f"INFORMATION => {information}"
5726        at_before = self.sql(expression, "at_before")
5727        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5728        end = self.sql(expression, "end")
5729        end = f"{self.seg('')}{end}" if end else ""
5730
5731        return f"CHANGES ({information}){at_before}{end}"
5732
5733    def pad_sql(self, expression: exp.Pad) -> str:
5734        prefix = "L" if expression.args.get("is_left") else "R"
5735
5736        fill_pattern = self.sql(expression, "fill_pattern") or None
5737        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5738            fill_pattern = "' '"
5739
5740        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5741
5742    def summarize_sql(self, expression: exp.Summarize) -> str:
5743        table = " TABLE" if expression.args.get("table") else ""
5744        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5745
5746    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5747        generate_series = exp.GenerateSeries(**expression.args)
5748
5749        parent = expression.parent
5750        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5751            parent = parent.parent
5752
5753        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5754            return self.sql(exp.Unnest(expressions=[generate_series]))
5755
5756        if isinstance(parent, exp.Select):
5757            self.unsupported("GenerateSeries projection unnesting is not supported.")
5758
5759        return self.sql(generate_series)
5760
5761    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5762        if self.SUPPORTS_CONVERT_TIMEZONE:
5763            return self.function_fallback_sql(expression)
5764
5765        source_tz = expression.args.get("source_tz")
5766        target_tz = expression.args.get("target_tz")
5767        timestamp = expression.args.get("timestamp")
5768
5769        if source_tz and timestamp:
5770            timestamp = exp.AtTimeZone(
5771                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5772            )
5773
5774        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5775
5776        return self.sql(expr)
5777
5778    def json_sql(self, expression: exp.JSON) -> str:
5779        this = self.sql(expression, "this")
5780        this = f" {this}" if this else ""
5781
5782        _with = expression.args.get("with_")
5783
5784        if _with is None:
5785            with_sql = ""
5786        elif not _with:
5787            with_sql = " WITHOUT"
5788        else:
5789            with_sql = " WITH"
5790
5791        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5792
5793        return f"JSON{this}{with_sql}{unique_sql}"
5794
5795    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5796        path = self.sql(expression, "path")
5797        returning = self.sql(expression, "returning")
5798        returning = f" RETURNING {returning}" if returning else ""
5799
5800        on_condition = self.sql(expression, "on_condition")
5801        on_condition = f" {on_condition}" if on_condition else ""
5802
5803        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5804
5805    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5806        regexp = " REGEXP" if expression.args.get("regexp") else ""
5807        return f"SKIP{regexp} {self.sql(expression.expression)}"
5808
5809    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5810        else_ = "ELSE " if expression.args.get("else_") else ""
5811        condition = self.sql(expression, "expression")
5812        condition = f"WHEN {condition} THEN " if condition else else_
5813        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5814        return f"{condition}{insert}"
5815
5816    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5817        kind = self.sql(expression, "kind")
5818        source = self.sql(expression, "source")
5819
5820        if kind:
5821            expressions = self.seg(self.expressions(expression, sep=" "))
5822            sql = f"INSERT {kind}{expressions}{self.seg(source)}"
5823        else:
5824            # If kind isn't present, we have Hive's FROM-first form:
5825            # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...]
5826            inserts = self.sep().join(self.sql(e) for e in expression.expressions)
5827            sql = f"FROM {source}{self.seg(inserts)}"
5828
5829        return self.prepend_ctes(expression, sql)
5830
5831    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5832        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5833        empty = expression.args.get("empty")
5834        empty = (
5835            f"DEFAULT {empty} ON EMPTY"
5836            if isinstance(empty, exp.Expr)
5837            else self.sql(expression, "empty")
5838        )
5839
5840        error = expression.args.get("error")
5841        error = (
5842            f"DEFAULT {error} ON ERROR"
5843            if isinstance(error, exp.Expr)
5844            else self.sql(expression, "error")
5845        )
5846
5847        if error and empty:
5848            error = (
5849                f"{empty} {error}"
5850                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5851                else f"{error} {empty}"
5852            )
5853            empty = ""
5854
5855        null = self.sql(expression, "null")
5856
5857        return f"{empty}{error}{null}"
5858
5859    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5860        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5861        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5862
5863    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5864        this = self.sql(expression, "this")
5865        path = self.sql(expression, "path")
5866
5867        passing = self.expressions(expression, "passing")
5868        passing = f" PASSING {passing}" if passing else ""
5869
5870        on_condition = self.sql(expression, "on_condition")
5871        on_condition = f" {on_condition}" if on_condition else ""
5872
5873        path = f"{path}{passing}{on_condition}"
5874
5875        return self.func("JSON_EXISTS", this, path)
5876
5877    def _add_arrayagg_null_filter(
5878        self,
5879        array_agg_sql: str,
5880        array_agg_expr: exp.ArrayAgg,
5881        column_expr: exp.Expr,
5882    ) -> str:
5883        """
5884        Add NULL filter to ARRAY_AGG if dialect requires it.
5885
5886        Args:
5887            array_agg_sql: The generated ARRAY_AGG SQL string
5888            array_agg_expr: The ArrayAgg expression node
5889            column_expr: The column/expression to filter (before ORDER BY wrapping)
5890
5891        Returns:
5892            SQL string with FILTER clause added if needed
5893        """
5894        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5895        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5896        if not (
5897            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5898        ):
5899            return array_agg_sql
5900
5901        parent = array_agg_expr.parent
5902        if isinstance(parent, exp.Filter):
5903            parent_cond = parent.expression.this
5904            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5905        elif column_expr.find(exp.Column):
5906            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5907            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5908            this_sql = (
5909                self.expressions(column_expr)
5910                if isinstance(column_expr, exp.Distinct)
5911                else self.sql(column_expr)
5912            )
5913            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5914
5915        return array_agg_sql
5916
5917    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5918        array_agg = self.function_fallback_sql(expression)
5919        column_expr = expression.this
5920        if isinstance(column_expr, exp.Order):
5921            column_expr = column_expr.this
5922
5923        return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
5924
5925    def slice_sql(self, expression: exp.Slice) -> str:
5926        step = self.sql(expression, "step")
5927        end = self.sql(expression.expression)
5928        begin = self.sql(expression.this)
5929
5930        sql = f"{end}:{step}" if step else end
5931        return f"{begin}:{sql}" if sql else f"{begin}:"
5932
5933    def apply_sql(self, expression: exp.Apply) -> str:
5934        this = self.sql(expression, "this")
5935        expr = self.sql(expression, "expression")
5936
5937        return f"{this} APPLY({expr})"
5938
5939    def _grant_or_revoke_sql(
5940        self,
5941        expression: exp.Grant | exp.Revoke,
5942        keyword: str,
5943        preposition: str,
5944        grant_option_prefix: str = "",
5945        grant_option_suffix: str = "",
5946    ) -> str:
5947        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5948
5949        kind = self.sql(expression, "kind")
5950        kind = f" {kind}" if kind else ""
5951
5952        securable = self.sql(expression, "securable")
5953        securable = f" {securable}" if securable else ""
5954
5955        principals = self.expressions(expression, key="principals", flat=True)
5956
5957        if not expression.args.get("grant_option"):
5958            grant_option_prefix = grant_option_suffix = ""
5959
5960        # cascade for revoke only
5961        cascade = self.sql(expression, "cascade")
5962        cascade = f" {cascade}" if cascade else ""
5963
5964        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5965
5966    def grant_sql(self, expression: exp.Grant) -> str:
5967        return self._grant_or_revoke_sql(
5968            expression,
5969            keyword="GRANT",
5970            preposition="TO",
5971            grant_option_suffix=" WITH GRANT OPTION",
5972        )
5973
5974    def revoke_sql(self, expression: exp.Revoke) -> str:
5975        return self._grant_or_revoke_sql(
5976            expression,
5977            keyword="REVOKE",
5978            preposition="FROM",
5979            grant_option_prefix="GRANT OPTION FOR ",
5980        )
5981
5982    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5983        this = self.sql(expression, "this")
5984        columns = self.expressions(expression, flat=True)
5985        columns = f"({columns})" if columns else ""
5986
5987        return f"{this}{columns}"
5988
5989    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5990        this = self.sql(expression, "this")
5991
5992        kind = self.sql(expression, "kind")
5993        kind = f"{kind} " if kind else ""
5994
5995        return f"{kind}{this}"
5996
5997    def columns_sql(self, expression: exp.Columns) -> str:
5998        func = self.function_fallback_sql(expression)
5999        if expression.args.get("unpack"):
6000            func = f"*{func}"
6001
6002        return func
6003
6004    def overlay_sql(self, expression: exp.Overlay) -> str:
6005        this = self.sql(expression, "this")
6006        expr = self.sql(expression, "expression")
6007        from_sql = self.sql(expression, "from_")
6008        for_sql = self.sql(expression, "for_")
6009        for_sql = f" FOR {for_sql}" if for_sql else ""
6010
6011        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
6012
6013    @unsupported_args("format")
6014    def todouble_sql(self, expression: exp.ToDouble) -> str:
6015        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
6016        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
6017
6018    def string_sql(self, expression: exp.String) -> str:
6019        this = expression.this
6020        zone = expression.args.get("zone")
6021
6022        if zone:
6023            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
6024            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
6025            # set for source_tz to transpile the time conversion before the STRING cast
6026            this = exp.ConvertTimezone(
6027                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
6028            )
6029
6030        return self.sql(exp.cast(this, exp.DType.VARCHAR))
6031
6032    def median_sql(self, expression: exp.Median) -> str:
6033        if not self.SUPPORTS_MEDIAN:
6034            return self.sql(
6035                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
6036            )
6037
6038        return self.function_fallback_sql(expression)
6039
6040    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
6041        filler = self.sql(expression, "this")
6042        filler = f" {filler}" if filler else ""
6043        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
6044        return f"TRUNCATE{filler} {with_count}"
6045
6046    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
6047        if self.SUPPORTS_UNIX_SECONDS:
6048            return self.function_fallback_sql(expression)
6049
6050        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
6051
6052        return self.sql(
6053            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
6054        )
6055
6056    def arraysize_sql(self, expression: exp.ArraySize) -> str:
6057        dim = expression.expression
6058
6059        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
6060        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
6061            if not (dim.is_int and dim.name == "1"):
6062                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
6063            dim = None
6064
6065        # If dimension is required but not specified, default initialize it
6066        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
6067            dim = exp.Literal.number(1)
6068
6069        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
6070
6071    def attach_sql(self, expression: exp.Attach) -> str:
6072        this = self.sql(expression, "this")
6073        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
6074        expressions = self.expressions(expression)
6075        expressions = f" ({expressions})" if expressions else ""
6076
6077        return f"ATTACH{exists_sql} {this}{expressions}"
6078
6079    def detach_sql(self, expression: exp.Detach) -> str:
6080        kind = self.sql(expression, "kind")
6081        kind = f" {kind}" if kind else ""
6082        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
6083        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
6084        exists = " IF EXISTS" if expression.args.get("exists") else ""
6085        if exists:
6086            kind = kind or " DATABASE"
6087
6088        this = self.sql(expression, "this")
6089        this = f" {this}" if this else ""
6090        cluster = self.sql(expression, "cluster")
6091        cluster = f" {cluster}" if cluster else ""
6092        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
6093        sync = " SYNC" if expression.args.get("sync") else ""
6094        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
6095
6096    def attachoption_sql(self, expression: exp.AttachOption) -> str:
6097        this = self.sql(expression, "this")
6098        value = self.sql(expression, "expression")
6099        value = f" {value}" if value else ""
6100        return f"{this}{value}"
6101
6102    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
6103        return (
6104            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
6105        )
6106
6107    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
6108        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
6109        encode = f"{encode} {self.sql(expression, 'this')}"
6110
6111        properties = expression.args.get("properties")
6112        if properties:
6113            encode = f"{encode} {self.properties(properties)}"
6114
6115        return encode
6116
6117    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
6118        this = self.sql(expression, "this")
6119        include = f"INCLUDE {this}"
6120
6121        column_def = self.sql(expression, "column_def")
6122        if column_def:
6123            include = f"{include} {column_def}"
6124
6125        alias = self.sql(expression, "alias")
6126        if alias:
6127            include = f"{include} AS {alias}"
6128
6129        return include
6130
6131    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
6132        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
6133        name = f"{prefix} {self.sql(expression, 'this')}"
6134        return self.func("XMLELEMENT", name, *expression.expressions)
6135
6136    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
6137        this = self.sql(expression, "this")
6138        expr = self.sql(expression, "expression")
6139        expr = f"({expr})" if expr else ""
6140        return f"{this}{expr}"
6141
6142    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
6143        partitions = self.expressions(expression, "partition_expressions")
6144        create = self.expressions(expression, "create_expressions")
6145        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
6146
6147    def partitionbyrangepropertydynamic_sql(
6148        self, expression: exp.PartitionByRangePropertyDynamic
6149    ) -> str:
6150        start = self.sql(expression, "start")
6151        end = self.sql(expression, "end")
6152
6153        every = expression.args["every"]
6154        if isinstance(every, exp.Interval) and every.this.is_string:
6155            every.this.replace(exp.Literal.number(every.name))
6156
6157        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6158
6159    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
6160        name = self.sql(expression, "this")
6161        values = self.expressions(expression, flat=True)
6162
6163        return f"NAME {name} VALUE {values}"
6164
6165    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
6166        kind = self.sql(expression, "kind")
6167        sample = self.sql(expression, "sample")
6168        return f"SAMPLE {sample} {kind}"
6169
6170    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
6171        kind = self.sql(expression, "kind")
6172        option = self.sql(expression, "option")
6173        option = f" {option}" if option else ""
6174        this = self.sql(expression, "this")
6175        this = f" {this}" if this else ""
6176        columns = self.expressions(expression)
6177        columns = f" {columns}" if columns else ""
6178        return f"{kind}{option} STATISTICS{this}{columns}"
6179
6180    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
6181        this = self.sql(expression, "this")
6182        columns = self.expressions(expression)
6183        inner_expression = self.sql(expression, "expression")
6184        inner_expression = f" {inner_expression}" if inner_expression else ""
6185        update_options = self.sql(expression, "update_options")
6186        update_options = f" {update_options} UPDATE" if update_options else ""
6187        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
6188
6189    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
6190        kind = self.sql(expression, "kind")
6191        kind = f" {kind}" if kind else ""
6192        return f"DELETE{kind} STATISTICS"
6193
6194    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
6195        inner_expression = self.sql(expression, "expression")
6196        return f"LIST CHAINED ROWS{inner_expression}"
6197
6198    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
6199        kind = self.sql(expression, "kind")
6200        this = self.sql(expression, "this")
6201        this = f" {this}" if this else ""
6202        inner_expression = self.sql(expression, "expression")
6203        return f"VALIDATE {kind}{this}{inner_expression}"
6204
6205    def analyze_sql(self, expression: exp.Analyze) -> str:
6206        options = self.expressions(expression, key="options", sep=" ")
6207        options = f" {options}" if options else ""
6208        kind = self.sql(expression, "kind")
6209        kind = f" {kind}" if kind else ""
6210        tables = self.expressions(expression, key="tables", flat=True)
6211        tables = f" {tables}" if tables else ""
6212        mode = self.sql(expression, "mode")
6213        mode = f" {mode}" if mode else ""
6214        properties = self.sql(expression, "properties")
6215        properties = f" {properties}" if properties else ""
6216        partition = self.sql(expression, "partition")
6217        partition = f" {partition}" if partition else ""
6218        inner_expression = self.sql(expression, "expression")
6219        inner_expression = f" {inner_expression}" if inner_expression else ""
6220        return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6221
6222    def xmltable_sql(self, expression: exp.XMLTable) -> str:
6223        this = self.sql(expression, "this")
6224        namespaces = self.expressions(expression, key="namespaces")
6225        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
6226        passing = self.expressions(expression, key="passing")
6227        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
6228        columns = self.expressions(expression, key="columns")
6229        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
6230        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
6231        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6232
6233    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
6234        this = self.sql(expression, "this")
6235        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
6236
6237    def export_sql(self, expression: exp.Export) -> str:
6238        this = self.sql(expression, "this")
6239        connection = self.sql(expression, "connection")
6240        connection = f"WITH CONNECTION {connection} " if connection else ""
6241        options = self.sql(expression, "options")
6242        return f"EXPORT DATA {connection}{options} AS {this}"
6243
6244    def declare_sql(self, expression: exp.Declare) -> str:
6245        replace = "OR REPLACE " if expression.args.get("replace") else ""
6246        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
6247
6248    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
6249        variables = self.expressions(expression, "this")
6250        default = self.sql(expression, "default")
6251        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
6252
6253        kind = self.sql(expression, "kind")
6254        if isinstance(expression.args.get("kind"), exp.Schema):
6255            kind = f"TABLE {kind}"
6256
6257        kind = f" {kind}" if kind else ""
6258
6259        return f"{variables}{kind}{default}"
6260
6261    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
6262        kind = self.sql(expression, "kind")
6263        this = self.sql(expression, "this")
6264        set = self.sql(expression, "expression")
6265        to = self.sql(expression, "to")
6266        to = f" TO {to}" if to else ""
6267        default = self.sql(expression, "default")
6268        default = f" DEFAULT {default}" if default else ""
6269        using = self.sql(expression, "using")
6270        using = f" USING {using}" if using else ""
6271
6272        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
6273
6274        return f"{kind_sql} {this} SET {set}{to}{default}{using}"
6275
6276    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
6277        params = self.expressions(expression, key="params", flat=True)
6278        return self.func(expression.name, *expression.expressions) + f"({params})"
6279
6280    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
6281        return self.func(expression.name, *expression.expressions)
6282
6283    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
6284        return self.anonymousaggfunc_sql(expression)
6285
6286    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
6287        return self.parameterizedagg_sql(expression)
6288
6289    def show_sql(self, expression: exp.Show) -> str:
6290        self.unsupported("Unsupported SHOW statement")
6291        return ""
6292
6293    def install_sql(self, expression: exp.Install) -> str:
6294        self.unsupported("Unsupported INSTALL statement")
6295        return ""
6296
6297    def liststage_sql(self, expression: exp.ListStage) -> str:
6298        pattern = self.sql(expression, "pattern")
6299        pattern = f" PATTERN = {pattern}" if pattern else ""
6300        return f"LIST {self.sql(expression, 'this')}{pattern}"
6301
6302    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
6303        # Snowflake GET/PUT statements:
6304        #   PUT <file> <internalStage> <properties>
6305        #   GET <internalStage> <file> <properties>
6306        props = expression.args.get("properties")
6307        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
6308        this = self.sql(expression, "this")
6309        target = self.sql(expression, "target")
6310
6311        if isinstance(expression, exp.Put):
6312            return f"PUT {this} {target}{props_sql}"
6313        else:
6314            return f"GET {target} {this}{props_sql}"
6315
6316    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
6317        this = self.sql(expression, "this")
6318        expr = self.sql(expression, "expression")
6319        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6320        return f"TRANSLATE({this} USING {expr}{with_error})"
6321
6322    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6323        if self.SUPPORTS_DECODE_CASE:
6324            return self.func("DECODE", *expression.expressions)
6325
6326        decode_expr, *expressions = expression.expressions
6327
6328        ifs = []
6329        for search, result in zip(expressions[::2], expressions[1::2]):
6330            if isinstance(search, exp.Literal):
6331                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6332            elif isinstance(search, exp.Null):
6333                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6334            else:
6335                if isinstance(search, exp.Binary):
6336                    search = exp.paren(search)
6337
6338                cond = exp.or_(
6339                    decode_expr.eq(search),
6340                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6341                    copy=False,
6342                )
6343                ifs.append(exp.If(this=cond, true=result))
6344
6345        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6346        return self.sql(case)
6347
6348    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6349        this = self.sql(expression, "this")
6350        this = self.seg(this, sep="")
6351        dimensions = self.expressions(
6352            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6353        )
6354        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6355        metrics = self.expressions(
6356            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6357        )
6358        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6359        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6360        facts = self.seg(f"FACTS {facts}") if facts else ""
6361        where = self.sql(expression, "where")
6362        where = self.seg(f"WHERE {where}") if where else ""
6363        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6364        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6365
6366    def getextract_sql(self, expression: exp.GetExtract) -> str:
6367        this = expression.this
6368        expr = expression.expression
6369
6370        if not this.type or not expression.type:
6371            import sqlglot.optimizer.annotate_types
6372
6373            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6374
6375        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6376            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6377
6378        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
6379
6380    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6381        return self.sql(
6382            exp.DateAdd(
6383                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6384                expression=expression.this,
6385                unit=exp.var("DAY"),
6386            )
6387        )
6388
6389    def space_sql(self: Generator, expression: exp.Space) -> str:
6390        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
6391
6392    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6393        return f"BUILD {self.sql(expression, 'this')}"
6394
6395    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6396        method = self.sql(expression, "method")
6397        kind = expression.args.get("kind")
6398        if not kind:
6399            return f"REFRESH {method}"
6400
6401        every = self.sql(expression, "every")
6402        unit = self.sql(expression, "unit")
6403        every = f" EVERY {every} {unit}" if every else ""
6404        starts = self.sql(expression, "starts")
6405        starts = f" STARTS {starts}" if starts else ""
6406
6407        return f"REFRESH {method} ON {kind}{every}{starts}"
6408
6409    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6410        self.unsupported("The model!attribute syntax is not supported")
6411        return ""
6412
6413    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6414        return self.func("DIRECTORY", expression.this)
6415
6416    def uuid_sql(self, expression: exp.Uuid) -> str:
6417        is_string = expression.args.get("is_string", False)
6418        uuid_func_sql = self.func("UUID")
6419
6420        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6421            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6422
6423        return uuid_func_sql
6424
6425    def initcap_sql(self, expression: exp.Initcap) -> str:
6426        delimiters = expression.expression
6427
6428        if delimiters:
6429            # do not generate delimiters arg if we are round-tripping from default delimiters
6430            if (
6431                delimiters.is_string
6432                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6433            ):
6434                delimiters = None
6435            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6436                self.unsupported("INITCAP does not support custom delimiters")
6437                delimiters = None
6438
6439        return self.func("INITCAP", expression.this, delimiters)
6440
6441    def localtime_sql(self, expression: exp.Localtime) -> str:
6442        this = expression.this
6443        return self.func("LOCALTIME", this) if this else "LOCALTIME"
6444
6445    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6446        this = expression.this
6447        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
6448
6449    def weekstart_name(self, expression: exp.WeekStart) -> str:
6450        import sqlglot.dialects.dialect
6451
6452        # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit
6453        this = expression.this.name.upper()
6454
6455        dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this)
6456        dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET)
6457
6458        if dow_from_week_start_day != dow_from_week_offset:
6459            self.unsupported(
6460                f"WEEK({this}) is not supported; falling back to the default week start day"
6461            )
6462
6463        return "WEEK"
6464
6465    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6466        name = self.weekstart_name(expression)
6467
6468        # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords
6469        if isinstance(expression.parent, exp.DateTrunc):
6470            return self.sql(exp.Literal.string(name))
6471
6472        return name
6473
6474    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6475        this = self.expressions(expression)
6476        charset = self.sql(expression, "charset")
6477        using = f" USING {charset}" if charset else ""
6478        return self.func(name, this + using)
6479
6480    def weightstring_sql(self, expression: exp.WeightString) -> str:
6481        to = self.sql(expression, "to")
6482        to = f" AS {to}" if to else ""
6483        return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}")
6484
6485    def block_sql(self, expression: exp.Block) -> str:
6486        expressions = self.expressions(expression, sep="; ", flat=True)
6487        begin = "BEGIN " if expression.args.get("begin") else ""
6488        return f"{begin}{expressions}" if expressions else ""
6489
6490    def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str:
6491        self.unsupported("Unsupported Inline UDFs syntax")
6492        return ""
6493
6494    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6495        self.unsupported("Unsupported Stored Procedure syntax")
6496        return ""
6497
6498    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6499        self.unsupported("Unsupported If block syntax")
6500        return ""
6501
6502    def casestatement_sql(self, expression: exp.CaseStatement) -> str:
6503        self.unsupported("Unsupported Case statement syntax")
6504        return ""
6505
6506    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6507        self.unsupported("Unsupported While block syntax")
6508        return ""
6509
6510    def loopblock_sql(self, expression: exp.LoopBlock) -> str:
6511        self.unsupported("Unsupported Loop block syntax")
6512        return ""
6513
6514    def repeatblock_sql(self, expression: exp.RepeatBlock) -> str:
6515        self.unsupported("Unsupported Repeat block syntax")
6516        return ""
6517
6518    def leave_sql(self, expression: exp.Leave) -> str:
6519        self.unsupported("Unsupported Leave syntax")
6520        return ""
6521
6522    def iterate_sql(self, expression: exp.Iterate) -> str:
6523        self.unsupported("Unsupported Iterate syntax")
6524        return ""
6525
6526    def execute_sql(self, expression: exp.Execute) -> str:
6527        self.unsupported("Unsupported Execute syntax")
6528        return ""
6529
6530    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6531        self.unsupported("Unsupported Execute syntax")
6532        return ""
6533
6534    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6535        props = self.expressions(expression, sep=" ")
6536        return f"MODIFY {props}"
6537
6538    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6539        kind = expression.args.get("kind")
6540        return f"USING {kind} {self.sql(expression, 'this')}"
6541
6542    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6543        this = self.sql(expression, "this")
6544        to = self.sql(expression, "to")
6545        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]]:
35def unsupported_args(
36    *args: str | tuple[str, str],
37) -> t.Callable[[GeneratorMethod], GeneratorMethod]:
38    """
39    Decorator that can be used to mark certain args of an `Expr` subclass as unsupported.
40    It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
41    """
42    diagnostic_by_arg: dict[str, str | None] = {}
43    for arg in args:
44        if isinstance(arg, str):
45            diagnostic_by_arg[arg] = None
46        else:
47            diagnostic_by_arg[arg[0]] = arg[1]
48
49    def decorator(func: GeneratorMethod) -> GeneratorMethod:
50        @wraps(func)
51        def _func(generator: G, expression: E) -> str:
52            expression_name = expression.__class__.__name__
53            dialect_name = generator.dialect.__class__.__name__
54
55            for arg_name, diagnostic in diagnostic_by_arg.items():
56                if expression.args.get(arg_name):
57                    diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format(
58                        arg_name, expression_name, dialect_name
59                    )
60                    generator.unsupported(diagnostic)
61
62            return func(generator, expression)
63
64        return _func
65
66    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:
 100class Generator:
 101    """
 102    Generator converts a given syntax tree to the corresponding SQL string.
 103
 104    Args:
 105        pretty: Whether to format the produced SQL string.
 106            Default: False.
 107        identify: Determines when an identifier should be quoted. Possible values are:
 108            False (default): Never quote, except in cases where it's mandatory by the dialect.
 109            True: Always quote except for specials cases.
 110            'safe': Only quote identifiers that are case insensitive.
 111        normalize: Whether to normalize identifiers to lowercase.
 112            Default: False.
 113        pad: The pad size in a formatted string. For example, this affects the indentation of
 114            a projection in a query, relative to its nesting level.
 115            Default: 2.
 116        indent: The indentation size in a formatted string. For example, this affects the
 117            indentation of subqueries and filters under a `WHERE` clause.
 118            Default: 2.
 119        normalize_functions: How to normalize function names. Possible values are:
 120            "upper" or True (default): Convert names to uppercase.
 121            "lower": Convert names to lowercase.
 122            False: Disables function name normalization.
 123        unsupported_level: Determines the generator's behavior when it encounters unsupported expressions.
 124            Default ErrorLevel.WARN.
 125        max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.
 126            This is only relevant if unsupported_level is ErrorLevel.RAISE.
 127            Default: 3
 128        leading_comma: Whether the comma is leading or trailing in select expressions.
 129            This is only relevant when generating in pretty mode.
 130            Default: False
 131        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
 132            The default is on the smaller end because the length only represents a segment and not the true
 133            line length.
 134            Default: 80
 135        comments: Whether to preserve comments in the output SQL code.
 136            Default: True
 137    """
 138
 139    TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = {
 140        **JSON_PATH_PART_TRANSFORMS,
 141        exp.Adjacent: lambda self, e: self.binary(e, "-|-"),
 142        exp.AllowedValuesProperty: lambda self, e: (
 143            f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
 144        ),
 145        exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
 146        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
 147        exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"),
 148        exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
 149        exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
 150        exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})",
 151        exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
 152        exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
 153        exp.BinaryColumnConstraint: lambda *_: "BINARY",
 154        exp.CaseSpecificColumnConstraint: lambda _, e: (
 155            f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC"
 156        ),
 157        exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT",
 158        exp.Ceil: lambda self, e: self.ceil_floor(e),
 159        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
 160        exp.CharacterSetProperty: lambda self, e: (
 161            f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}"
 162        ),
 163        exp.ClusteredColumnConstraint: lambda self, e: (
 164            f"CLUSTERED ({self.expressions(e, 'this', indent=False)})"
 165        ),
 166        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
 167        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
 168        exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}",
 169        exp.ConvertToCharset: lambda self, e: self.func(
 170            "CONVERT", e.this, e.args["dest"], e.args.get("source")
 171        ),
 172        exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
 173        exp.CredentialsProperty: lambda self, e: (
 174            f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})"
 175        ),
 176        exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG",
 177        exp.SessionUser: lambda *_: "SESSION_USER",
 178        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 179        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 180        exp.ApiProperty: lambda *_: "API",
 181        exp.ApplicationProperty: lambda *_: "APPLICATION",
 182        exp.CatalogProperty: lambda *_: "CATALOG",
 183        exp.ComputeProperty: lambda *_: "COMPUTE",
 184        exp.DatabaseProperty: lambda *_: "DATABASE",
 185        exp.DynamicProperty: lambda *_: "DYNAMIC",
 186        exp.EmptyProperty: lambda *_: "EMPTY",
 187        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 188        exp.EndStatement: lambda *_: "END",
 189        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 190        exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}",
 191        exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}",
 192        exp.EphemeralColumnConstraint: lambda self, e: (
 193            f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}"
 194        ),
 195        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 196        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 197        exp.Except: lambda self, e: self.set_operations(e),
 198        exp.ExternalProperty: lambda *_: "EXTERNAL",
 199        exp.Floor: lambda self, e: self.ceil_floor(e),
 200        exp.Get: lambda self, e: self.get_put_sql(e),
 201        exp.GlobalProperty: lambda *_: "GLOBAL",
 202        exp.HeapProperty: lambda *_: "HEAP",
 203        exp.HybridProperty: lambda *_: "HYBRID",
 204        exp.IcebergProperty: lambda *_: "ICEBERG",
 205        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 206        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 207        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 208        exp.Intersect: lambda self, e: self.set_operations(e),
 209        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 210        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)),
 211        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 212        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 213        exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"),
 214        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 215        exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"),
 216        exp.JSONObject: lambda self, e: self._jsonobject_sql(e),
 217        exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e),
 218        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 219        exp.LocationProperty: lambda self, e: self.naked_property(e),
 220        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 221        exp.MaskingProperty: lambda *_: "MASKING",
 222        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 223        exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
 224        exp.NetworkProperty: lambda *_: "NETWORK",
 225        exp.NonClusteredColumnConstraint: lambda self, e: (
 226            f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})"
 227        ),
 228        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 229        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 230        exp.OnCommitProperty: lambda _, e: (
 231            f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS"
 232        ),
 233        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 234        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 235        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 236        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 237        exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
 238        exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
 239        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 240        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 241        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 242        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 243        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 244        exp.ProjectionPolicyColumnConstraint: lambda self, e: (
 245            f"PROJECTION POLICY {self.sql(e, 'this')}"
 246        ),
 247        exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE",
 248        exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
 249        exp.Put: lambda self, e: self.get_put_sql(e),
 250        exp.RemoteWithConnectionModelProperty: lambda self, e: (
 251            f"REMOTE WITH CONNECTION {self.sql(e, 'this')}"
 252        ),
 253        exp.ReturnsProperty: lambda self, e: (
 254            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 255        ),
 256        exp.RowAccessProperty: lambda *_: "ROW ACCESS",
 257        exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
 258        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 259        exp.SecureProperty: lambda *_: "SECURE",
 260        exp.SecurityIntegrationProperty: lambda *_: "SECURITY",
 261        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 262        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 263        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 264        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 265        exp.SqlReadWriteProperty: lambda _, e: e.name,
 266        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
 267        exp.StabilityProperty: lambda _, e: e.name,
 268        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 269        exp.StreamingTableProperty: lambda *_: "STREAMING",
 270        exp.StrictProperty: lambda *_: "STRICT",
 271        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 272        exp.TableColumn: lambda self, e: self.sql(e.this),
 273        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 274        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 275        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 276        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 277        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 278        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 279        exp.TransientProperty: lambda *_: "TRANSIENT",
 280        exp.VirtualProperty: lambda *_: "VIRTUAL",
 281        exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}",
 282        exp.Union: lambda self, e: self.set_operations(e),
 283        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 284        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 285        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 286        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 287        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 288        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 289        exp.UtcTimestamp: lambda self, e: self.sql(
 290            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 291        ),
 292        exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
 293        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 294        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 295        exp.VolatileProperty: lambda *_: "VOLATILE",
 296        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 297        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 298        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 299        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 300        exp.ForceProperty: lambda *_: "FORCE",
 301    }
 302
 303    # Whether null ordering is supported in order by
 304    # True: Full Support, None: No support, False: No support for certain cases
 305    # such as window specifications, aggregate functions etc
 306    NULL_ORDERING_SUPPORTED: bool | None = True
 307
 308    # Window functions that support NULLS FIRST/LAST
 309    WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = ()
 310
 311    # Whether ignore nulls is inside the agg or outside.
 312    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 313    IGNORE_NULLS_IN_FUNC = False
 314
 315    # Whether IGNORE NULLS is placed before ORDER BY in the agg.
 316    # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS)
 317    IGNORE_NULLS_BEFORE_ORDER = True
 318
 319    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 320    LOCKING_READS_SUPPORTED = False
 321
 322    # Whether the EXCEPT and INTERSECT operations can return duplicates
 323    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 324
 325    # Wrap derived values in parens, usually standard but spark doesn't support it
 326    WRAP_DERIVED_VALUES = True
 327
 328    # Whether create function uses an AS before the RETURN
 329    CREATE_FUNCTION_RETURN_AS = True
 330
 331    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 332    MATCHED_BY_SOURCE = True
 333
 334    # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported
 335    SUPPORTS_MERGE_WHERE = False
 336
 337    # Whether the INTERVAL expression works only with values like '1 day'
 338    SINGLE_STRING_INTERVAL = False
 339
 340    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 341    INTERVAL_ALLOWS_PLURAL_FORM = True
 342
 343    # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the
 344    # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND
 345    AUTO_REFRESH_BARE_INTERVALS = False
 346
 347    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 348    LIMIT_FETCH = "ALL"
 349
 350    # Whether limit and fetch allows expresions or just limits
 351    LIMIT_ONLY_LITERALS = False
 352
 353    # Whether a table is allowed to be renamed with a db
 354    RENAME_TABLE_WITH_DB = True
 355
 356    # The separator for grouping sets and rollups
 357    GROUPINGS_SEP = ","
 358
 359    # Whether GROUPING SETS can follow GROUP BY expressions without a comma
 360    SUPPORTS_GROUPING_SETS_AS_SUFFIX = False
 361
 362    # The string used for creating an index on a table
 363    INDEX_ON = "ON"
 364
 365    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 366    INOUT_SEPARATOR = " "
 367
 368    # Whether join hints should be generated
 369    JOIN_HINTS = True
 370
 371    # Whether directed joins are supported
 372    DIRECTED_JOINS = False
 373
 374    # Whether table hints should be generated
 375    TABLE_HINTS = True
 376
 377    # Whether query hints should be generated
 378    QUERY_HINTS = True
 379
 380    # What kind of separator to use for query hints
 381    QUERY_HINT_SEP = ", "
 382
 383    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 384    IS_BOOL_ALLOWED = True
 385
 386    # Whether casting a fractional number to an integer rounds it instead of truncating it
 387    CAST_TO_INT_ROUNDS = False
 388
 389    # Whether the native integer division operator returns NULL for a zero divisor instead of raising
 390    SAFE_INT_DIVISION = False
 391
 392    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 393    DUPLICATE_KEY_UPDATE_WITH_SET = True
 394
 395    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 396    LIMIT_IS_TOP = False
 397
 398    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 399    RETURNING_END = True
 400
 401    # Whether to generate an unquoted value for EXTRACT's date part argument
 402    EXTRACT_ALLOWS_QUOTES = True
 403
 404    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 405    TZ_TO_WITH_TIME_ZONE = False
 406
 407    # Whether the NVL2 function is supported
 408    NVL2_SUPPORTED = True
 409
 410    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 411    SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE")
 412
 413    # Whether VALUES statements can be used as derived tables.
 414    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 415    # SELECT * VALUES into SELECT UNION
 416    VALUES_AS_TABLE = True
 417
 418    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 419    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 420
 421    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 422    UNNEST_WITH_ORDINALITY = True
 423
 424    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 425    SEMI_ANTI_JOIN_WITH_SIDE = True
 426
 427    # Whether to include the type of a computed column in the CREATE DDL
 428    COMPUTED_COLUMN_WITH_TYPE = True
 429
 430    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 431    SUPPORTS_TABLE_COPY = True
 432
 433    # Whether parentheses are required around the table sample's expression
 434    TABLESAMPLE_REQUIRES_PARENS = True
 435
 436    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 437    TABLESAMPLE_SIZE_IS_ROWS = True
 438
 439    # The keyword(s) to use when generating a sample clause
 440    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 441
 442    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 443    TABLESAMPLE_WITH_METHOD = True
 444
 445    # The keyword to use when specifying the seed of a sample clause
 446    TABLESAMPLE_SEED_KEYWORD = "SEED"
 447
 448    # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias
 449    HISTORICAL_DATA_POST_ALIAS = False
 450
 451    # Whether COLLATE is a function instead of a binary operator
 452    COLLATE_IS_FUNC = False
 453
 454    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 455    DATA_TYPE_SPECIFIERS_ALLOWED = False
 456
 457    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 458    ENSURE_BOOLS = False
 459
 460    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 461    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 462
 463    # Whether CONCAT requires >1 arguments
 464    SUPPORTS_SINGLE_ARG_CONCAT = True
 465
 466    # Whether LAST_DAY function supports a date part argument
 467    LAST_DAY_SUPPORTS_DATE_PART = True
 468
 469    # Whether named columns are allowed in table aliases
 470    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 471
 472    # Whether named columns are allowed in CTE definitions
 473    SUPPORTS_NAMED_CTE_COLUMNS = True
 474
 475    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 476    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 477
 478    # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048)
 479    PIVOT_ALIAS_WITH_AS = True
 480
 481    # What delimiter to use for separating JSON key/value pairs
 482    JSON_KEY_VALUE_PAIR_SEP = ":"
 483
 484    # INSERT OVERWRITE TABLE x override
 485    INSERT_OVERWRITE = " OVERWRITE TABLE"
 486
 487    # Whether the SELECT .. INTO syntax is used instead of CTAS
 488    SUPPORTS_SELECT_INTO = False
 489
 490    # Whether UNLOGGED tables can be created
 491    SUPPORTS_UNLOGGED_TABLES = False
 492
 493    # Whether the CREATE TABLE LIKE statement is supported
 494    SUPPORTS_CREATE_TABLE_LIKE = True
 495
 496    # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 497    SUPPORTS_MODIFY_COLUMN = False
 498
 499    # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported
 500    SUPPORTS_CHANGE_COLUMN = False
 501
 502    # Whether ALTER COLUMN can set a column's nullability together with its type
 503    SUPPORTS_ALTER_COLUMN_NULLABILITY = False
 504
 505    # Whether ALTER COLUMN IF EXISTS is supported
 506    SUPPORTS_ALTER_COLUMN_IF_EXISTS = False
 507
 508    # Whether the LikeProperty needs to be specified inside of the schema clause
 509    LIKE_PROPERTY_INSIDE_SCHEMA = False
 510
 511    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 512    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 513    MULTI_ARG_DISTINCT = True
 514
 515    # Whether the JSON extraction operators expect a value of type JSON
 516    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 517
 518    # Whether bracketed keys like ["foo"] are supported in JSON paths
 519    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 520
 521    # Whether to escape keys using single quotes in JSON paths
 522    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 523
 524    # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be
 525    # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match
 526    # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks
 527    # where a bare colon key is case-insensitive but a bracketed key is case-sensitive.
 528    JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False
 529
 530    # The JSONPathPart expressions supported by this dialect
 531    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 532
 533    # Whether any(f(x) for x in array) can be implemented by this dialect
 534    CAN_IMPLEMENT_ARRAY_ANY = False
 535
 536    # Whether the function TO_NUMBER is supported
 537    SUPPORTS_TO_NUMBER = True
 538
 539    # Whether EXCLUDE in window specification is supported
 540    SUPPORTS_WINDOW_EXCLUDE = False
 541
 542    # Whether or not set op modifiers apply to the outer set op or select.
 543    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 544    # True means limit 1 happens after the set op, False means it it happens on y.
 545    SET_OP_MODIFIERS = True
 546
 547    # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses.
 548    SET_OP_LIMITS = False
 549
 550    # Whether set operation operands can be parenthesized without a SELECT wrapper.
 551    SET_OP_PARENTHESIZED_OPERANDS = True
 552
 553    # Whether parameters from COPY statement are wrapped in parentheses
 554    COPY_PARAMS_ARE_WRAPPED = True
 555
 556    # Whether values of params are set with "=" token or empty space
 557    COPY_PARAMS_EQ_REQUIRED = False
 558
 559    # Whether COPY statement has INTO keyword
 560    COPY_HAS_INTO_KEYWORD = True
 561
 562    # Whether the conditional TRY(expression) function is supported
 563    TRY_SUPPORTED = True
 564
 565    # Whether the UESCAPE syntax in unicode strings is supported
 566    SUPPORTS_UESCAPE = True
 567
 568    # Function used to replace escaped unicode codes in unicode strings
 569    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 570
 571    # The keyword to use when generating a star projection with excluded columns
 572    STAR_EXCEPT = "EXCEPT"
 573
 574    # The HEX function name
 575    HEX_FUNC = "HEX"
 576
 577    # Function that constructs a binary value from a hex-encoded byte string.
 578    BYTE_STRING_FUNCTION = ""
 579
 580    # The keywords to use when prefixing & separating WITH based properties
 581    WITH_PROPERTIES_PREFIX = "WITH"
 582
 583    # Whether to quote the generated expression of exp.JsonPath
 584    QUOTE_JSON_PATH = True
 585
 586    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 587    PAD_FILL_PATTERN_IS_REQUIRED = False
 588
 589    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 590    SUPPORTS_EXPLODING_PROJECTIONS = True
 591
 592    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 593    ARRAY_CONCAT_IS_VAR_LEN = True
 594
 595    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 596    SUPPORTS_CONVERT_TIMEZONE = False
 597
 598    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 599    SUPPORTS_MEDIAN = True
 600
 601    # Whether UNIX_SECONDS(timestamp) is supported
 602    SUPPORTS_UNIX_SECONDS = False
 603
 604    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 605    ALTER_SET_WRAPPED = False
 606
 607    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 608    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 609    # TODO: The normalization should be done by default once we've tested it across all dialects.
 610    NORMALIZE_EXTRACT_DATE_PARTS = False
 611
 612    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 613    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 614
 615    # The function name of the exp.ArraySize expression
 616    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 617
 618    # The syntax to use when altering the type of a column
 619    ALTER_SET_TYPE = "SET DATA TYPE"
 620
 621    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 622    # None -> Doesn't support it at all
 623    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 624    # True (Postgres) -> Explicitly requires it
 625    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 626
 627    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 628    SUPPORTS_DECODE_CASE = True
 629
 630    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 631    SUPPORTS_BETWEEN_FLAGS = False
 632
 633    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 634    SUPPORTS_LIKE_QUANTIFIERS = True
 635
 636    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 637    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 638
 639    # Whether to include the VARIABLE keyword for SET assignments
 640    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 641
 642    # The keyword to use for default value assignment in DECLARE statements
 643    DECLARE_DEFAULT_ASSIGNMENT = "="
 644
 645    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 646    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 647    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 648    UPDATE_STATEMENT_SUPPORTS_FROM = True
 649
 650    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 651    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 652
 653    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 654    # - Snowflake: DROP ICEBERG TABLE a.b;
 655    # - DuckDB:    DROP TABLE a.b;
 656    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 657
 658    TYPE_MAPPING: t.ClassVar = {
 659        exp.DType.DATETIME2: "TIMESTAMP",
 660        exp.DType.NCHAR: "CHAR",
 661        exp.DType.NVARCHAR: "VARCHAR",
 662        exp.DType.MEDIUMTEXT: "TEXT",
 663        exp.DType.LONGTEXT: "TEXT",
 664        exp.DType.TINYTEXT: "TEXT",
 665        exp.DType.BLOB: "VARBINARY",
 666        exp.DType.MEDIUMBLOB: "BLOB",
 667        exp.DType.LONGBLOB: "BLOB",
 668        exp.DType.TINYBLOB: "BLOB",
 669        exp.DType.INET: "INET",
 670        exp.DType.ROWVERSION: "VARBINARY",
 671        exp.DType.SMALLDATETIME: "TIMESTAMP",
 672    }
 673
 674    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 675
 676    # mapping of DType to its default parameters, bounds
 677    TYPE_PARAM_SETTINGS: t.ClassVar[
 678        dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]
 679    ] = {}
 680
 681    TIME_PART_SINGULARS: t.ClassVar = {
 682        "MICROSECONDS": "MICROSECOND",
 683        "SECONDS": "SECOND",
 684        "MINUTES": "MINUTE",
 685        "HOURS": "HOUR",
 686        "DAYS": "DAY",
 687        "WEEKS": "WEEK",
 688        "MONTHS": "MONTH",
 689        "QUARTERS": "QUARTER",
 690        "YEARS": "YEAR",
 691    }
 692
 693    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 694        "cluster": lambda self, e: self.sql(e, "cluster"),
 695        "distribute": lambda self, e: self.sql(e, "distribute"),
 696        "sort": lambda self, e: self.sql(e, "sort"),
 697        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 698    }
 699
 700    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 701
 702    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 703
 704    PARAMETER_TOKEN = "@"
 705    NAMED_PLACEHOLDER_TOKEN = ":"
 706
 707    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 708
 709    PROPERTIES_LOCATION: t.ClassVar = {
 710        exp.AggregateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 711        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 712        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 713        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 714        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 715        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 716        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 717        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 718        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 719        exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA,
 720        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 721        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 722        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 723        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 724        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 725        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 726        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 727        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 728        exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA,
 729        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 730        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 731        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 732        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 733        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 734        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 735        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 736        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 737        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 738        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 739        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 740        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 741        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 742        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 743        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 744        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 745        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 746        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 747        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 748        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 749        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 750        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 751        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 752        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 753        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 754        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 755        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 756        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 757        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 758        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 759        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 760        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 761        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 762        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 763        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 764        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 765        exp.LogProperty: exp.Properties.Location.POST_NAME,
 766        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 767        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 768        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 769        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 770        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 771        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 772        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 773        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 774        exp.Order: exp.Properties.Location.POST_SCHEMA,
 775        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 776        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 777        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 778        exp.PhysicalProperty: exp.Properties.Location.UNSUPPORTED,
 779        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 780        exp.Property: exp.Properties.Location.POST_WITH,
 781        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 782        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 783        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 784        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 785        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 786        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 787        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 788        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 789        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 790        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 791        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 792        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 793        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 794        exp.Set: exp.Properties.Location.POST_SCHEMA,
 795        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 796        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 797        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 798        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 799        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 800        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 801        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 802        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 803        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 804        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 805        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 806        exp.StorageProperty: exp.Properties.Location.UNSUPPORTED,
 807        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 808        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 809        exp.Tags: exp.Properties.Location.POST_WITH,
 810        exp.TableSpaceProperty: exp.Properties.Location.UNSUPPORTED,
 811        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 812        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 813        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 814        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 815        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 816        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 817        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 818        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 819        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 820        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 821        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 822        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 823        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 824        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 825        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 826        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 827        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 828    }
 829
 830    # Keywords that can't be used as unquoted identifier names
 831    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 832
 833    # Exprs whose comments are separated from them for better formatting
 834    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 835        exp.Command,
 836        exp.Create,
 837        exp.Describe,
 838        exp.Delete,
 839        exp.Drop,
 840        exp.From,
 841        exp.Insert,
 842        exp.Join,
 843        exp.MultitableInserts,
 844        exp.Order,
 845        exp.Group,
 846        exp.Having,
 847        exp.Select,
 848        exp.SetOperation,
 849        exp.Update,
 850        exp.Where,
 851        exp.With,
 852    )
 853
 854    # Exprs that should not have their comments generated in maybe_comment
 855    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 856        exp.Binary,
 857        exp.SetOperation,
 858    )
 859
 860    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 861    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 862        exp.Column,
 863        exp.Literal,
 864        exp.Neg,
 865        exp.Paren,
 866    )
 867
 868    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 869        exp.DType.NVARCHAR,
 870        exp.DType.VARCHAR,
 871        exp.DType.CHAR,
 872        exp.DType.NCHAR,
 873    }
 874
 875    # Exprs that need to have all CTEs under them bubbled up to them
 876    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 877
 878    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 879
 880    MOD_OPERATOR = "%"
 881
 882    # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses
 883    MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 884        exp.Mul,
 885        exp.Div,
 886        exp.IntDiv,
 887        exp.Mod,
 888    )
 889
 890    # ALTER options that follow the others without a comma, e.g. MySQL's
 891    # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ...
 892    ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 893
 894    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 895
 896    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 897
 898    __slots__ = (
 899        "pretty",
 900        "identify",
 901        "normalize",
 902        "pad",
 903        "_indent",
 904        "normalize_functions",
 905        "unsupported_level",
 906        "max_unsupported",
 907        "leading_comma",
 908        "max_text_width",
 909        "comments",
 910        "dialect",
 911        "unsupported_messages",
 912        "_escaped_quote_end",
 913        "_escaped_byte_quote_end",
 914        "_escaped_identifier_end",
 915        "_identifier_escaped_sequences",
 916        "_next_name",
 917        "_identifier_start",
 918        "_identifier_end",
 919        "_quote_json_path_key_using_brackets",
 920        "_dispatch",
 921    )
 922
 923    def __init__(
 924        self,
 925        pretty: bool | int | None = None,
 926        identify: str | bool = False,
 927        normalize: bool = False,
 928        pad: int = 2,
 929        indent: int = 2,
 930        normalize_functions: str | bool | None = None,
 931        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 932        max_unsupported: int = 3,
 933        leading_comma: bool = False,
 934        max_text_width: int = 80,
 935        comments: bool = True,
 936        dialect: DialectType = None,
 937    ):
 938        import sqlglot
 939        import sqlglot.dialects.dialect
 940
 941        self.pretty = pretty if pretty is not None else sqlglot.pretty
 942        self.identify = identify
 943        self.normalize = normalize
 944        self.pad = pad
 945        self._indent = indent
 946        self.unsupported_level = unsupported_level
 947        self.max_unsupported = max_unsupported
 948        self.leading_comma = leading_comma
 949        self.max_text_width = max_text_width
 950        self.comments = comments
 951        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 952
 953        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 954        self.normalize_functions = (
 955            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 956        )
 957
 958        self.unsupported_messages: list[str] = []
 959        self._escaped_quote_end: str = (
 960            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 961        )
 962        self._escaped_byte_quote_end: str = (
 963            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 964            if self.dialect.BYTE_END
 965            else ""
 966        )
 967        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 968        self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES
 969
 970        self._next_name = name_sequence("_t")
 971
 972        self._identifier_start = self.dialect.IDENTIFIER_START
 973        self._identifier_end = self.dialect.IDENTIFIER_END
 974
 975        self._quote_json_path_key_using_brackets = True
 976
 977        cls = type(self)
 978        dispatch = _DISPATCH_CACHE.get(cls)
 979        if dispatch is None:
 980            dispatch = _build_dispatch(cls)
 981            _DISPATCH_CACHE[cls] = dispatch
 982        self._dispatch = dispatch
 983
 984    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 985        """
 986        Generates the SQL string corresponding to the given syntax tree.
 987
 988        Args:
 989            expression: The syntax tree.
 990            copy: Whether to copy the expression. The generator performs mutations so
 991                it is safer to copy.
 992
 993        Returns:
 994            The SQL string corresponding to `expression`.
 995        """
 996        if copy:
 997            expression = expression.copy()
 998
 999        expression = self.preprocess(expression)
1000
1001        self.unsupported_messages = []
1002        sql = self.sql(expression).strip()
1003
1004        if self.pretty:
1005            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
1006
1007        if self.unsupported_level == ErrorLevel.IGNORE:
1008            return sql
1009
1010        if self.unsupported_level == ErrorLevel.WARN:
1011            for msg in self.unsupported_messages:
1012                logger.warning(msg)
1013        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
1014            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
1015
1016        return sql
1017
1018    def preprocess(self, expression: exp.Expr) -> exp.Expr:
1019        """Apply generic preprocessing transformations to a given expression."""
1020        expression = self._move_ctes_to_top_level(expression)
1021
1022        if self.ENSURE_BOOLS:
1023            import sqlglot.transforms
1024
1025            expression = sqlglot.transforms.ensure_bools(expression)
1026
1027        return expression
1028
1029    def _move_ctes_to_top_level(self, expression: E) -> E:
1030        if (
1031            not expression.parent
1032            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
1033            and any(node.parent is not expression for node in expression.find_all(exp.With))
1034        ):
1035            import sqlglot.transforms
1036
1037            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
1038        return expression
1039
1040    def unsupported(self, message: str) -> None:
1041        if self.unsupported_level == ErrorLevel.IMMEDIATE:
1042            raise UnsupportedError(message)
1043        self.unsupported_messages.append(message)
1044
1045    def sep(self, sep: str = " ") -> str:
1046        return f"{sep.strip()}\n" if self.pretty else sep
1047
1048    def seg(self, sql: str, sep: str = " ") -> str:
1049        return f"{self.sep(sep)}{sql}"
1050
1051    def sanitize_comment(self, comment: str) -> str:
1052        comment = " " + comment if comment[0].strip() else comment
1053        comment = comment + " " if comment[-1].strip() else comment
1054
1055        # Escape block comment markers to prevent premature closure or unintended nesting.
1056        # This is necessary because single-line comments (--) are converted to block comments
1057        # (/* */) on output, and any */ in the original text would close the comment early.
1058        comment = comment.replace("*/", "* /").replace("/*", "/ *")
1059
1060        return comment
1061
1062    def maybe_comment(
1063        self,
1064        sql: str,
1065        expression: exp.Expr | None = None,
1066        comments: list[str] | None = None,
1067        separated: bool = False,
1068    ) -> str:
1069        comments = (
1070            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1071            if self.comments
1072            else None
1073        )
1074
1075        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1076            return sql
1077
1078        comments_list = [
1079            f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/"
1080            for comment in comments
1081            if comment
1082        ]
1083
1084        if not comments_list:
1085            return sql
1086
1087        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1088            comments_sql = self.sep().join(comments_list)
1089            return (
1090                f"{self.sep()}{comments_sql}{sql}"
1091                if not sql or sql[0].isspace()
1092                else f"{comments_sql}{self.sep()}{sql}"
1093            )
1094
1095        return f"{sql} {' '.join(comments_list)}"
1096
1097    def wrap(self, expression: exp.Expr | str) -> str:
1098        this_sql = (
1099            self.sql(expression)
1100            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1101            else self.sql(expression, "this")
1102        )
1103        if not this_sql:
1104            return "()"
1105
1106        this_sql = self.indent(this_sql, level=1, pad=0)
1107        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1108
1109    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1110        original = self.identify
1111        self.identify = False
1112        result = func(*args, **kwargs)
1113        self.identify = original
1114        return result
1115
1116    def normalize_func(self, name: str) -> str:
1117        if self.normalize_functions == "upper" or self.normalize_functions is True:
1118            return name.upper()
1119        if self.normalize_functions == "lower":
1120            return name.lower()
1121        return name
1122
1123    def indent(
1124        self,
1125        sql: str,
1126        level: int = 0,
1127        pad: int | None = None,
1128        skip_first: bool = False,
1129        skip_last: bool = False,
1130    ) -> str:
1131        if not self.pretty or not sql:
1132            return sql
1133
1134        pad = self.pad if pad is None else pad
1135        lines = sql.split("\n")
1136
1137        return "\n".join(
1138            (
1139                line
1140                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1141                else f"{' ' * (level * self._indent + pad)}{line}"
1142            )
1143            for i, line in enumerate(lines)
1144        )
1145
1146    def sql(
1147        self,
1148        expression: str | exp.Expr | None,
1149        key: str | None = None,
1150        comment: bool = True,
1151    ) -> str:
1152        if not expression:
1153            return ""
1154
1155        if isinstance(expression, str):
1156            return expression
1157
1158        if key:
1159            value = expression.args.get(key)
1160            if value:
1161                return self.sql(value)
1162            return ""
1163
1164        handler = self._dispatch.get(expression.__class__)
1165
1166        if handler:
1167            sql = handler(self, expression)
1168        elif isinstance(expression, exp.Func):
1169            sql = self.function_fallback_sql(expression)
1170        elif isinstance(expression, exp.Property):
1171            sql = self.property_sql(expression)
1172        else:
1173            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1174
1175        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1176
1177    def uncache_sql(self, expression: exp.Uncache) -> str:
1178        table = self.sql(expression, "this")
1179        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1180        return f"UNCACHE TABLE{exists_sql} {table}"
1181
1182    def cache_sql(self, expression: exp.Cache) -> str:
1183        lazy = " LAZY" if expression.args.get("lazy") else ""
1184        table = self.sql(expression, "this")
1185        options = expression.args.get("options")
1186        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1187        sql = self.sql(expression, "expression")
1188        sql = f" AS{self.sep()}{sql}" if sql else ""
1189        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1190        return self.prepend_ctes(expression, sql)
1191
1192    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1193        default = "DEFAULT " if expression.args.get("default") else ""
1194        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1195
1196    def column_parts(self, expression: exp.Column) -> str:
1197        if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES:
1198            # The qualifier would be captured by a colliding projection alias (see qualify_columns)
1199            return self.sql(expression, "this")
1200
1201        return ".".join(
1202            self.sql(part)
1203            for part in (
1204                expression.args.get("catalog"),
1205                expression.args.get("db"),
1206                expression.args.get("table"),
1207                expression.args.get("this"),
1208            )
1209            if part
1210        )
1211
1212    def column_sql(self, expression: exp.Column) -> str:
1213        join_mark = " (+)" if expression.args.get("join_mark") else ""
1214
1215        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1216            join_mark = ""
1217            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1218
1219        return f"{self.column_parts(expression)}{join_mark}"
1220
1221    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1222        return self.column_sql(expression)
1223
1224    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1225        this = self.sql(expression, "this")
1226        this = f" {this}" if this else ""
1227        position = self.sql(expression, "position")
1228        return f"{position}{this}"
1229
1230    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1231        column = self.sql(expression, "this")
1232        kind = self.sql(expression, "kind")
1233        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1234        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1235        kind = f"{sep}{kind}" if kind else ""
1236        constraints = f" {constraints}" if constraints else ""
1237        position = self.sql(expression, "position")
1238        position = f" {position}" if position else ""
1239
1240        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1241            kind = ""
1242
1243        return f"{exists}{column}{kind}{constraints}{position}"
1244
1245    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1246        this = self.sql(expression, "this")
1247        kind_sql = self.sql(expression, "kind").strip()
1248        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1249
1250    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1251        this = self.sql(expression, "this")
1252        if expression.args.get("not_null"):
1253            persisted = " PERSISTED NOT NULL"
1254        elif expression.args.get("persisted"):
1255            persisted = " PERSISTED"
1256        else:
1257            persisted = ""
1258
1259        return f"AS {this}{persisted}"
1260
1261    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1262        return self.token_sql(TokenType.AUTO_INCREMENT)
1263
1264    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1265        if isinstance(expression.this, list):
1266            this = self.wrap(self.expressions(expression, key="this", flat=True))
1267        else:
1268            this = self.sql(expression, "this")
1269
1270        return f"COMPRESS {this}"
1271
1272    def generatedasidentitycolumnconstraint_sql(
1273        self, expression: exp.GeneratedAsIdentityColumnConstraint
1274    ) -> str:
1275        this = ""
1276        if expression.this is not None:
1277            on_null = " ON NULL" if expression.args.get("on_null") else ""
1278            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1279
1280        start = expression.args.get("start")
1281        start = f"START WITH {start}" if start else ""
1282        increment = expression.args.get("increment")
1283        increment = f" INCREMENT BY {increment}" if increment else ""
1284        minvalue = expression.args.get("minvalue")
1285        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1286        maxvalue = expression.args.get("maxvalue")
1287        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1288        cycle = expression.args.get("cycle")
1289        cycle_sql = ""
1290
1291        if cycle is not None:
1292            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1293            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1294
1295        sequence_opts = ""
1296        if start or increment or cycle_sql:
1297            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1298            sequence_opts = f" ({sequence_opts.strip()})"
1299
1300        expr = self.sql(expression, "expression")
1301        expr = f"({expr})" if expr else "IDENTITY"
1302
1303        return f"GENERATED{this} AS {expr}{sequence_opts}"
1304
1305    def generatedasrowcolumnconstraint_sql(
1306        self, expression: exp.GeneratedAsRowColumnConstraint
1307    ) -> str:
1308        start = "START" if expression.args.get("start") else "END"
1309        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1310        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1311
1312    def periodforsystemtimeconstraint_sql(
1313        self, expression: exp.PeriodForSystemTimeConstraint
1314    ) -> str:
1315        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1316
1317    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1318        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1319
1320    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1321        desc = expression.args.get("desc")
1322        if desc is not None:
1323            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1324        options = self.expressions(expression, key="options", flat=True, sep=" ")
1325        options = f" {options}" if options else ""
1326        return f"PRIMARY KEY{options}"
1327
1328    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1329        this = self.sql(expression, "this")
1330        this = f" {this}" if this else ""
1331        index_type = expression.args.get("index_type")
1332        index_type = f" USING {index_type}" if index_type else ""
1333        on_conflict = self.sql(expression, "on_conflict")
1334        on_conflict = f" {on_conflict}" if on_conflict else ""
1335        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1336        options = self.expressions(expression, key="options", flat=True, sep=" ")
1337        options = f" {options}" if options else ""
1338        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1339
1340    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1341        input_ = expression.args.get("input_")
1342        output = expression.args.get("output")
1343        variadic = expression.args.get("variadic")
1344
1345        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1346        if variadic:
1347            return "VARIADIC"
1348
1349        if input_ and output:
1350            return f"IN{self.INOUT_SEPARATOR}OUT"
1351        if input_:
1352            return "IN"
1353        if output:
1354            return "OUT"
1355
1356        return ""
1357
1358    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1359        return self.sql(expression, "this")
1360
1361    def create_sql(self, expression: exp.Create) -> str:
1362        kind = self.sql(expression, "kind")
1363        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1364
1365        properties = expression.args.get("properties")
1366
1367        if (
1368            kind == "TRIGGER"
1369            and properties
1370            and properties.expressions
1371            and isinstance(properties.expressions[0], exp.TriggerProperties)
1372            and properties.expressions[0].args.get("constraint")
1373        ):
1374            kind = f"CONSTRAINT {kind}"
1375
1376        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1377
1378        this = self.createable_sql(expression, properties_locs)
1379
1380        properties_sql = ""
1381        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1382            exp.Properties.Location.POST_WITH
1383        ):
1384            props_ast = exp.Properties(
1385                expressions=[
1386                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1387                    *properties_locs[exp.Properties.Location.POST_WITH],
1388                ]
1389            )
1390            props_ast.parent = expression
1391            properties_sql = self.sql(props_ast)
1392
1393            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1394                properties_sql = self.sep() + properties_sql
1395            elif not self.pretty:
1396                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1397                properties_sql = f" {properties_sql}"
1398
1399        begin = " BEGIN" if expression.args.get("begin") else ""
1400
1401        expression_sql = self.sql(expression, "expression")
1402        if expression_sql:
1403            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1404
1405            if not isinstance(expression.expression, exp.MacroOverloads) and (
1406                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1407            ):
1408                postalias_props_sql = ""
1409                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1410                    postalias_props_sql = self.properties(
1411                        exp.Properties(
1412                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1413                        ),
1414                        wrapped=False,
1415                    )
1416                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1417                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1418
1419        postindex_props_sql = ""
1420        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1421            postindex_props_sql = self.properties(
1422                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1423                wrapped=False,
1424                prefix=" ",
1425            )
1426
1427        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1428        indexes = f" {indexes}" if indexes else ""
1429        index_sql = indexes + postindex_props_sql
1430
1431        replace = " OR REPLACE" if expression.args.get("replace") else ""
1432        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1433        unique = " UNIQUE" if expression.args.get("unique") else ""
1434
1435        clustered = expression.args.get("clustered")
1436        if clustered is None:
1437            clustered_sql = ""
1438        elif clustered:
1439            clustered_sql = " CLUSTERED COLUMNSTORE"
1440        else:
1441            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1442
1443        postcreate_props_sql = ""
1444        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1445            postcreate_props_sql = self.properties(
1446                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1447                sep=" ",
1448                prefix=" ",
1449                wrapped=False,
1450            )
1451
1452        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1453
1454        postexpression_props_sql = ""
1455        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1456            postexpression_props_sql = self.properties(
1457                exp.Properties(
1458                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1459                ),
1460                sep=" ",
1461                prefix=" ",
1462                wrapped=False,
1463            )
1464
1465        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1466        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1467        no_schema_binding = (
1468            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1469        )
1470
1471        clone = self.sql(expression, "clone")
1472        clone = f" {clone}" if clone else ""
1473
1474        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1475            properties_expression = f"{expression_sql}{properties_sql}"
1476        else:
1477            properties_expression = f"{properties_sql}{expression_sql}"
1478
1479        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1480        return self.prepend_ctes(expression, expression_sql)
1481
1482    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1483        start = self.sql(expression, "start")
1484        start = f"START WITH {start}" if start else ""
1485        increment = self.sql(expression, "increment")
1486        increment = f" INCREMENT BY {increment}" if increment else ""
1487        minvalue = self.sql(expression, "minvalue")
1488        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1489        maxvalue = self.sql(expression, "maxvalue")
1490        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1491        owned = self.sql(expression, "owned")
1492        owned = f" OWNED BY {owned}" if owned else ""
1493
1494        cache = expression.args.get("cache")
1495        if cache is None:
1496            cache_str = ""
1497        elif cache is True:
1498            cache_str = " CACHE"
1499        else:
1500            cache_str = f" CACHE {cache}"
1501
1502        options = self.expressions(expression, key="options", flat=True, sep=" ")
1503        options = f" {options}" if options else ""
1504
1505        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1506
1507    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1508        timing = expression.args.get("timing", "")
1509        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1510        timing_events = f"{timing} {events}".strip() if timing or events else ""
1511
1512        parts = [timing_events, "ON", self.sql(expression, "table")]
1513
1514        if referenced_table := expression.args.get("referenced_table"):
1515            parts.extend(["FROM", self.sql(referenced_table)])
1516
1517        if deferrable := expression.args.get("deferrable"):
1518            parts.append(deferrable)
1519
1520        if initially := expression.args.get("initially"):
1521            parts.append(f"INITIALLY {initially}")
1522
1523        if referencing := expression.args.get("referencing"):
1524            parts.append(self.sql(referencing))
1525
1526        if for_each := expression.args.get("for_each"):
1527            parts.append(f"FOR EACH {for_each}")
1528
1529        if when := expression.args.get("when"):
1530            parts.append(f"WHEN ({self.sql(when)})")
1531
1532        parts.append(self.sql(expression, "execute"))
1533
1534        return self.sep().join(parts)
1535
1536    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1537        parts = []
1538
1539        if old_alias := expression.args.get("old"):
1540            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1541
1542        if new_alias := expression.args.get("new"):
1543            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1544
1545        return f"REFERENCING {' '.join(parts)}"
1546
1547    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1548        columns = expression.args.get("columns")
1549        if columns:
1550            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1551
1552        return self.sql(expression, "this")
1553
1554    def clone_sql(self, expression: exp.Clone) -> str:
1555        this = self.sql(expression, "this")
1556        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1557        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1558        return f"{shallow}{keyword} {this}"
1559
1560    def describe_sql(self, expression: exp.Describe) -> str:
1561        style = expression.args.get("style")
1562        style = f" {style}" if style else ""
1563        partition = self.sql(expression, "partition")
1564        partition = f" {partition}" if partition else ""
1565        format = self.sql(expression, "format")
1566        format = f" {format}" if format else ""
1567        as_json = " AS JSON" if expression.args.get("as_json") else ""
1568
1569        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1570
1571    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1572        tag = self.sql(expression, "tag")
1573        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1574
1575    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1576        with_ = self.sql(expression, "with_")
1577        if with_:
1578            sql = f"{with_}{self.sep()}{sql}"
1579        return sql
1580
1581    def with_sql(self, expression: exp.With) -> str:
1582        udfs = self.expressions(expression, key="udfs", flat=True)
1583        udfs = f"WITH {udfs}" if udfs else ""
1584
1585        sql = self.expressions(expression, flat=True)
1586
1587        recursive = (
1588            "RECURSIVE "
1589            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1590            else ""
1591        )
1592        search = self.sql(expression, "search")
1593        search = f" {search}" if search else ""
1594        cycle = self.sql(expression, "cycle")
1595        cycle = f" {cycle}" if cycle else ""
1596
1597        sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else ""
1598        return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1599
1600    def cte_sql(self, expression: exp.CTE) -> str:
1601        alias = expression.args.get("alias")
1602        if alias:
1603            alias.add_comments(expression.pop_comments())
1604
1605        alias_sql = self.sql(expression, "alias")
1606
1607        materialized = expression.args.get("materialized")
1608        if materialized is False:
1609            materialized = "NOT MATERIALIZED "
1610        elif materialized:
1611            materialized = "MATERIALIZED "
1612
1613        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1614        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1615
1616        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1617
1618    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1619        alias = self.sql(expression, "this")
1620        columns = self.expressions(expression, key="columns", flat=True)
1621        columns = f"({columns})" if columns else ""
1622
1623        if (
1624            columns
1625            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1626            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1627        ):
1628            columns = ""
1629            self.unsupported("Named columns are not supported in table alias.")
1630
1631        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1632            alias = self._next_name()
1633
1634        return f"{alias}{columns}"
1635
1636    def bitstring_sql(self, expression: exp.BitString) -> str:
1637        this = self.sql(expression, "this")
1638        if self.dialect.BIT_START:
1639            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1640        return f"{int(this, 2)}"
1641
1642    def hexstring_sql(
1643        self, expression: exp.HexString, binary_function_repr: str | None = None
1644    ) -> str:
1645        this = self.sql(expression, "this")
1646        is_integer_type = expression.args.get("is_integer")
1647
1648        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1649            not self.dialect.HEX_START and not binary_function_repr
1650        ):
1651            # Integer representation will be returned if:
1652            # - The read dialect treats the hex value as integer literal but not the write
1653            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1654            return f"{int(this, 16)}"
1655
1656        if not is_integer_type:
1657            # Read dialect treats the hex value as BINARY/BLOB
1658            if binary_function_repr:
1659                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1660                return self.func(binary_function_repr, exp.Literal.string(this))
1661            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1662                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1663                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1664
1665        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1666
1667    def bytestring_sql(self, expression: exp.ByteString) -> str:
1668        this = self.sql(expression, "this")
1669        is_bytes = bool(expression.args.get("is_bytes"))
1670        if is_bytes and self.BYTE_STRING_FUNCTION:
1671            # Hex digits can be quoted directly without escaping.
1672            return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'")
1673
1674        if self.dialect.BYTE_START:
1675            escaped_byte_string = self.escape_str(
1676                this,
1677                escape_backslash=bool(
1678                    self.dialect.tokenizer_class.NUMERIC_ESCAPES
1679                    or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES
1680                ),
1681                delimiter=self.dialect.BYTE_END,
1682                escaped_delimiter=self._escaped_byte_quote_end,
1683                is_byte_string=True,
1684                is_bytes=is_bytes,
1685            )
1686            delimited_byte_string = (
1687                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1688            )
1689            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1690                return self.sql(
1691                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1692                )
1693            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1694                return self.sql(
1695                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1696                )
1697
1698            return delimited_byte_string
1699
1700        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1701            return self.sql(exp.Literal.string(this))
1702
1703        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1704        return ""
1705
1706    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1707        this = self.sql(expression, "this")
1708        escape = expression.args.get("escape")
1709        unicode_start = self.dialect.UNICODE_START
1710
1711        if unicode_start:
1712            escape_substitute = r"\\\1"
1713            left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or ""
1714        else:
1715            escape_substitute = r"\\u\1"
1716            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1717
1718        if escape:
1719            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1720            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1721        else:
1722            escape_pattern = ESCAPED_UNICODE_RE
1723            escape_sql = ""
1724
1725        if not unicode_start or (escape and not self.SUPPORTS_UESCAPE):
1726            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1727
1728        if unicode_start:
1729            # A Unicode literal only escapes its delimiter by doubling it; the escape character
1730            # introduces a code point, so the dialect's ordinary string escapes don't apply here
1731            this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2)
1732        else:
1733            this = self.escape_str(this, escape_backslash=False)
1734
1735        return f"{left_quote}{this}{right_quote}{escape_sql}"
1736
1737    def rawstring_sql(self, expression: exp.RawString) -> str:
1738        string = expression.this
1739        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1740            string = string.replace("\\", "\\\\")
1741
1742        string = self.escape_str(string, escape_backslash=False)
1743        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1744
1745    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1746        this = self.sql(expression, "this")
1747        specifier = self.sql(expression, "expression")
1748        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1749        return f"{this}{specifier}"
1750
1751    def datatype_param_bound_limiter(
1752        self,
1753        expression: exp.DataType,
1754        type_value: exp.DType,
1755        defaults: tuple[int, ...],
1756        bounds: tuple[int | None, ...],
1757    ) -> exp.DataType:
1758        params = expression.expressions
1759
1760        if not params:
1761            if defaults:
1762                expression.set(
1763                    "expressions",
1764                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1765                )
1766            return expression
1767
1768        if not bounds:
1769            return expression
1770
1771        for i, param in enumerate(params):
1772            bound = bounds[i] if i < len(bounds) else None
1773            if bound is None:
1774                continue
1775
1776            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1777            value = (
1778                param_value.to_py()
1779                if isinstance(param_value, exp.Literal) and param_value.is_number
1780                else None
1781            )
1782            if isinstance(value, (int, Decimal)) and value > bound:
1783                self.unsupported(
1784                    f"{type_value.value} parameter {param_value.name} exceeds "
1785                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1786                )
1787                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1788
1789        return expression
1790
1791    def datatype_sql(self, expression: exp.DataType) -> str:
1792        nested = ""
1793        values = ""
1794
1795        expr_nested = expression.args.get("nested")
1796        type_value = expression.this
1797
1798        if (
1799            not expr_nested
1800            and isinstance(type_value, exp.DType)
1801            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1802        ):
1803            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1804
1805        interior = (
1806            self.expressions(
1807                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1808            )
1809            if expr_nested and self.pretty
1810            else self.expressions(expression, flat=True)
1811        )
1812
1813        if type_value in self.UNSUPPORTED_TYPES:
1814            self.unsupported(
1815                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1816            )
1817
1818        type_sql: t.Any = ""
1819        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1820            type_sql = self.sql(expression, "kind")
1821        elif type_value == exp.DType.CHARACTER_SET:
1822            char = f"CHAR({interior})" if interior else "CHAR"
1823            return f"{char} CHARACTER SET {self.sql(expression, 'kind')}"
1824        else:
1825            type_sql = (
1826                self.TYPE_MAPPING.get(type_value, type_value.value)
1827                if isinstance(type_value, exp.DType)
1828                else type_value
1829            )
1830
1831        if interior:
1832            if expr_nested:
1833                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1834                if expression.args.get("values") is not None:
1835                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1836                    values = self.expressions(expression, key="values", flat=True)
1837                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1838            elif type_value == exp.DType.INTERVAL:
1839                nested = f" {interior}"
1840            else:
1841                nested = f"({interior})"
1842
1843        type_sql = f"{type_sql}{nested}{values}"
1844        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1845            exp.DType.TIMETZ,
1846            exp.DType.TIMESTAMPTZ,
1847        ):
1848            type_sql = f"{type_sql} WITH TIME ZONE"
1849
1850        collate = self.sql(expression, "collate")
1851        if collate:
1852            type_sql = f"{type_sql} COLLATE {collate}"
1853
1854        return type_sql
1855
1856    def directory_sql(self, expression: exp.Directory) -> str:
1857        local = "LOCAL " if expression.args.get("local") else ""
1858        row_format = self.sql(expression, "row_format")
1859        row_format = f" {row_format}" if row_format else ""
1860        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1861
1862    def delete_sql(self, expression: exp.Delete) -> str:
1863        hint = self.sql(expression, "hint")
1864        this = self.sql(expression, "this")
1865        this = f" FROM {this}" if this else ""
1866        using = self.expressions(expression, key="using")
1867        using = f" USING {using}" if using else ""
1868        cluster = self.sql(expression, "cluster")
1869        cluster = f" {cluster}" if cluster else ""
1870        where = self.sql(expression, "where")
1871        returning = self.sql(expression, "returning")
1872        order = self.sql(expression, "order")
1873        limit = self.sql(expression, "limit")
1874        tables = self.expressions(expression, key="tables")
1875        tables = f" {tables}" if tables else ""
1876        if self.RETURNING_END:
1877            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1878        else:
1879            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1880        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1881
1882    def drop_sql(self, expression: exp.Drop) -> str:
1883        tables = self.expressions(expression, key="tables", flat=True)
1884        expressions = self.expressions(expression, flat=True)
1885        expressions = f" ({expressions})" if expressions else ""
1886        kind = expression.args["kind"]
1887        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1888        iceberg = (
1889            " ICEBERG"
1890            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1891            else ""
1892        )
1893        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1894        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1895        on_cluster = self.sql(expression, "cluster")
1896        on_cluster = f" {on_cluster}" if on_cluster else ""
1897        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1898        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1899        cascade = " CASCADE" if expression.args.get("cascade") else ""
1900        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1901        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1902        purge = " PURGE" if expression.args.get("purge") else ""
1903        sync = " SYNC" if expression.args.get("sync") else ""
1904        force = " FORCE" if expression.args.get("force") else ""
1905        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1906
1907    def set_operation(self, expression: exp.SetOperation) -> str:
1908        op_type = type(expression)
1909        op_name = op_type.key.upper()
1910
1911        distinct = expression.args.get("distinct")
1912        if (
1913            distinct is False
1914            and op_type in (exp.Except, exp.Intersect)
1915            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1916        ):
1917            self.unsupported(f"{op_name} ALL is not supported")
1918
1919        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1920
1921        if distinct is None:
1922            distinct = default_distinct
1923            if distinct is None:
1924                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1925
1926        if distinct is default_distinct:
1927            distinct_or_all = ""
1928        else:
1929            distinct_or_all = " DISTINCT" if distinct else " ALL"
1930
1931        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1932        side_kind = f"{side_kind} " if side_kind else ""
1933
1934        by_name = " BY NAME" if expression.args.get("by_name") else ""
1935        on = self.expressions(expression, key="on", flat=True)
1936        on = f" ON ({on})" if on else ""
1937
1938        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1939
1940    def set_operations(self, expression: exp.SetOperation) -> str:
1941        if not self.SET_OP_MODIFIERS:
1942            limit = expression.args.get("limit")
1943            order = expression.args.get("order")
1944            offset = expression.args.get("offset")
1945
1946            if limit or order or offset:
1947                select = self._move_ctes_to_top_level(
1948                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1949                )
1950
1951                for arg in ("limit", "order", "offset"):
1952                    if value := expression.args.get(arg):
1953                        select.set(arg, value.pop())
1954                return self.sql(select)
1955
1956        sqls: list[str] = []
1957        stack: list[str | exp.Expr] = [expression]
1958
1959        while stack:
1960            node = stack.pop()
1961
1962            if isinstance(node, exp.SetOperation):
1963                stack.append(node.expression)
1964                stack.append(
1965                    self.maybe_comment(
1966                        self.set_operation(node), comments=node.comments, separated=True
1967                    )
1968                )
1969                stack.append(node.this)
1970            else:
1971                if (
1972                    not self.SET_OP_LIMITS
1973                    and isinstance(node, exp.Select)
1974                    and node.args.get("limit")
1975                ):
1976                    node = node.subquery(copy=False)
1977                    if not self.SET_OP_PARENTHESIZED_OPERANDS:
1978                        node = exp.select("*").from_(node, copy=False)
1979                sqls.append(self.sql(node))
1980
1981        this = self.sep().join(sqls)
1982        this = self.query_modifiers(expression, this)
1983        return self.prepend_ctes(expression, this)
1984
1985    def fetch_sql(self, expression: exp.Fetch) -> str:
1986        direction = expression.args.get("direction")
1987        direction = f" {direction}" if direction else ""
1988        count = self.sql(expression, "count")
1989        count = f" {count}" if count else ""
1990        limit_options = self.sql(expression, "limit_options")
1991        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1992        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1993
1994    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1995        percent = " PERCENT" if expression.args.get("percent") else ""
1996        rows = " ROWS" if expression.args.get("rows") else ""
1997        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1998        if not with_ties and rows:
1999            with_ties = " ONLY"
2000        return f"{percent}{rows}{with_ties}"
2001
2002    def filter_sql(self, expression: exp.Filter) -> str:
2003        this = self.sql(expression, "this")
2004        where = self.sql(expression, "expression").strip()
2005        return f"{this} FILTER({where})"
2006
2007    def hint_sql(self, expression: exp.Hint) -> str:
2008        if not self.QUERY_HINTS:
2009            self.unsupported("Hints are not supported")
2010            return ""
2011
2012        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
2013
2014    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
2015        using = self.sql(expression, "using")
2016        using = f" USING {using}" if using else ""
2017        columns = self.expressions(expression, key="columns", flat=True)
2018        columns = f"({columns})" if columns else ""
2019        partition_by = self.expressions(expression, key="partition_by", flat=True)
2020        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
2021        where = self.sql(expression, "where")
2022        include = self.expressions(expression, key="include", flat=True)
2023        if include:
2024            include = f" INCLUDE ({include})"
2025        with_storage = self.expressions(expression, key="with_storage", flat=True)
2026        with_storage = f" WITH ({with_storage})" if with_storage else ""
2027        tablespace = self.sql(expression, "tablespace")
2028        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
2029        on = self.sql(expression, "on")
2030        on = f" ON {on}" if on else ""
2031
2032        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
2033
2034    def index_sql(self, expression: exp.Index) -> str:
2035        unique = "UNIQUE " if expression.args.get("unique") else ""
2036        primary = "PRIMARY " if expression.args.get("primary") else ""
2037        amp = "AMP " if expression.args.get("amp") else ""
2038        name = self.sql(expression, "this")
2039        name = f"{name} " if name else ""
2040        table = self.sql(expression, "table")
2041        table = f"{self.INDEX_ON} {table}" if table else ""
2042
2043        index = "INDEX " if not table else ""
2044
2045        params = self.sql(expression, "params")
2046        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
2047
2048    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
2049        this = expression.this
2050        if this and this.is_string:
2051            resolved = maybe_parse(this.name).sql(self.dialect)
2052            if "expressions" in expression.args:
2053                # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
2054                # We can't safely emit the call to other dialects since name/arg semantics may differ
2055                self.unsupported(
2056                    "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported"
2057                )
2058            return resolved
2059        self.unsupported("IDENTIFIER() with non-literal arguments is not supported")
2060        return self.func("IDENTIFIER", this)
2061
2062    def identifier_sql(self, expression: exp.Identifier) -> str:
2063        text = expression.name
2064        lower = text.lower()
2065        quoted = expression.quoted
2066        text = lower if self.normalize and not quoted else text
2067        if self._identifier_escaped_sequences:
2068            for char, escaped in self._identifier_escaped_sequences.items():
2069                text = text.replace(char, escaped)
2070        text = text.replace(self._identifier_end, self._escaped_identifier_end)
2071        if (
2072            quoted
2073            or self.dialect.can_quote(expression, self.identify)
2074            or lower in self.RESERVED_KEYWORDS
2075            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
2076        ):
2077            text = (
2078                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
2079            )
2080        return text
2081
2082    def hex_sql(self, expression: exp.Hex) -> str:
2083        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2084        if self.dialect.HEX_LOWERCASE:
2085            text = self.func("LOWER", text)
2086
2087        return text
2088
2089    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
2090        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2091        if not self.dialect.HEX_LOWERCASE:
2092            text = self.func("LOWER", text)
2093        return text
2094
2095    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
2096        input_format = self.sql(expression, "input_format")
2097        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
2098        output_format = self.sql(expression, "output_format")
2099        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
2100        return self.sep().join((input_format, output_format))
2101
2102    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
2103        string = self.sql(exp.Literal.string(expression.name))
2104        return f"{prefix}{string}"
2105
2106    def partition_sql(self, expression: exp.Partition) -> str:
2107        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
2108        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
2109
2110    def properties_sql(self, expression: exp.Properties) -> str:
2111        root_properties = []
2112        with_properties = []
2113
2114        for p in expression.expressions:
2115            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2116            if p_loc == exp.Properties.Location.POST_WITH:
2117                with_properties.append(p)
2118            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2119                root_properties.append(p)
2120
2121        root_props_ast = exp.Properties(expressions=root_properties)
2122        root_props_ast.parent = expression.parent
2123
2124        with_props_ast = exp.Properties(expressions=with_properties)
2125        with_props_ast.parent = expression.parent
2126
2127        root_props = self.root_properties(root_props_ast)
2128        with_props = self.with_properties(with_props_ast)
2129
2130        if root_props and with_props and not self.pretty:
2131            with_props = " " + with_props
2132
2133        return root_props + with_props
2134
2135    def root_properties(self, properties: exp.Properties) -> str:
2136        if properties.expressions:
2137            return self.expressions(properties, indent=False, sep=" ")
2138        return ""
2139
2140    def properties(
2141        self,
2142        properties: exp.Properties,
2143        prefix: str = "",
2144        sep: str = ", ",
2145        suffix: str = "",
2146        wrapped: bool = True,
2147    ) -> str:
2148        if properties.expressions:
2149            expressions = self.expressions(properties, sep=sep, indent=False)
2150            if expressions:
2151                expressions = self.wrap(expressions) if wrapped else expressions
2152                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2153        return ""
2154
2155    def with_properties(self, properties: exp.Properties) -> str:
2156        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
2157
2158    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2159        properties_locs = defaultdict(list)
2160        for p in properties.expressions:
2161            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2162            if p_loc != exp.Properties.Location.UNSUPPORTED:
2163                properties_locs[p_loc].append(p)
2164            else:
2165                self.unsupported(f"Unsupported property {p.key}")
2166
2167        return properties_locs
2168
2169    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2170        if isinstance(expression.this, exp.Dot):
2171            return self.sql(expression, "this")
2172        return f"'{expression.name}'" if string_key else expression.name
2173
2174    def property_sql(self, expression: exp.Property) -> str:
2175        property_cls = expression.__class__
2176        if property_cls == exp.Property:
2177            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2178
2179        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2180        if not property_name:
2181            self.unsupported(f"Unsupported property {expression.key}")
2182
2183        return f"{property_name}={self.sql(expression, 'this')}"
2184
2185    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2186        return f"UUID {self.sql(expression, 'this')}"
2187
2188    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2189        if self.SUPPORTS_CREATE_TABLE_LIKE:
2190            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2191            options = f" {options}" if options else ""
2192
2193            like = f"LIKE {self.sql(expression, 'this')}{options}"
2194            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2195                like = f"({like})"
2196
2197            return like
2198
2199        if expression.expressions:
2200            self.unsupported("Transpilation of LIKE property options is unsupported")
2201
2202        select = exp.select("*").from_(expression.this).limit(0)
2203        return f"AS {self.sql(select)}"
2204
2205    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2206        no = "NO " if expression.args.get("no") else ""
2207        protection = " PROTECTION" if expression.args.get("protection") else ""
2208        return f"{no}FALLBACK{protection}"
2209
2210    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2211        no = "NO " if expression.args.get("no") else ""
2212        local = expression.args.get("local")
2213        local = f"{local} " if local else ""
2214        dual = "DUAL " if expression.args.get("dual") else ""
2215        before = "BEFORE " if expression.args.get("before") else ""
2216        after = "AFTER " if expression.args.get("after") else ""
2217        return f"{no}{local}{dual}{before}{after}JOURNAL"
2218
2219    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2220        freespace = self.sql(expression, "this")
2221        percent = " PERCENT" if expression.args.get("percent") else ""
2222        return f"FREESPACE={freespace}{percent}"
2223
2224    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2225        if expression.args.get("default"):
2226            property = "DEFAULT"
2227        elif expression.args.get("on"):
2228            property = "ON"
2229        else:
2230            property = "OFF"
2231        return f"CHECKSUM={property}"
2232
2233    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2234        if expression.args.get("no"):
2235            return "NO MERGEBLOCKRATIO"
2236        if expression.args.get("default"):
2237            return "DEFAULT MERGEBLOCKRATIO"
2238
2239        percent = " PERCENT" if expression.args.get("percent") else ""
2240        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2241
2242    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2243        expressions = self.expressions(expression, flat=True)
2244        expressions = f"({expressions})" if expressions else ""
2245        return f"USING {self.sql(expression, 'this')}{expressions}"
2246
2247    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2248        default = expression.args.get("default")
2249        minimum = expression.args.get("minimum")
2250        maximum = expression.args.get("maximum")
2251        if default or minimum or maximum:
2252            if default:
2253                prop = "DEFAULT"
2254            elif minimum:
2255                prop = "MINIMUM"
2256            else:
2257                prop = "MAXIMUM"
2258            return f"{prop} DATABLOCKSIZE"
2259        units = expression.args.get("units")
2260        units = f" {units}" if units else ""
2261        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2262
2263    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2264        autotemp = expression.args.get("autotemp")
2265        always = expression.args.get("always")
2266        default = expression.args.get("default")
2267        manual = expression.args.get("manual")
2268        never = expression.args.get("never")
2269
2270        if autotemp is not None:
2271            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2272        elif always:
2273            prop = "ALWAYS"
2274        elif default:
2275            prop = "DEFAULT"
2276        elif manual:
2277            prop = "MANUAL"
2278        elif never:
2279            prop = "NEVER"
2280        return f"BLOCKCOMPRESSION={prop}"
2281
2282    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2283        no = expression.args.get("no")
2284        no = " NO" if no else ""
2285        concurrent = expression.args.get("concurrent")
2286        concurrent = " CONCURRENT" if concurrent else ""
2287        target = self.sql(expression, "target")
2288        target = f" {target}" if target else ""
2289        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2290
2291    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2292        if isinstance(expression.this, list):
2293            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2294        if expression.this:
2295            modulus = self.sql(expression, "this")
2296            remainder = self.sql(expression, "expression")
2297            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2298
2299        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2300        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2301        return f"FROM ({from_expressions}) TO ({to_expressions})"
2302
2303    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2304        this = self.sql(expression, "this")
2305
2306        for_values_or_default = expression.expression
2307        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2308            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2309        else:
2310            for_values_or_default = " DEFAULT"
2311
2312        return f"PARTITION OF {this}{for_values_or_default}"
2313
2314    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2315        kind = expression.args.get("kind")
2316        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2317        for_or_in = expression.args.get("for_or_in")
2318        for_or_in = f" {for_or_in}" if for_or_in else ""
2319        lock_type = expression.args.get("lock_type")
2320        override = " OVERRIDE" if expression.args.get("override") else ""
2321        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2322
2323    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2324        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2325        statistics = expression.args.get("statistics")
2326        statistics_sql = ""
2327        if statistics is not None:
2328            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2329        return f"{data_sql}{statistics_sql}"
2330
2331    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2332        this = self.sql(expression, "this")
2333        this = f"HISTORY_TABLE={this}" if this else ""
2334        data_consistency: str | None = self.sql(expression, "data_consistency")
2335        data_consistency = (
2336            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2337        )
2338        retention_period: str | None = self.sql(expression, "retention_period")
2339        retention_period = (
2340            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2341        )
2342
2343        if this:
2344            on_sql = self.func("ON", this, data_consistency, retention_period)
2345        else:
2346            on_sql = "ON" if expression.args.get("on") else "OFF"
2347
2348        sql = f"SYSTEM_VERSIONING={on_sql}"
2349
2350        return f"WITH({sql})" if expression.args.get("with_") else sql
2351
2352    def insert_sql(self, expression: exp.Insert) -> str:
2353        hint = self.sql(expression, "hint")
2354        overwrite = expression.args.get("overwrite")
2355
2356        if isinstance(expression.this, exp.Directory):
2357            this = " OVERWRITE" if overwrite else " INTO"
2358        else:
2359            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2360
2361        stored = self.sql(expression, "stored")
2362        stored = f" {stored}" if stored else ""
2363        alternative = expression.args.get("alternative")
2364        alternative = f" OR {alternative}" if alternative else ""
2365        ignore = " IGNORE" if expression.args.get("ignore") else ""
2366        is_function = expression.args.get("is_function")
2367        if is_function:
2368            this = f"{this} FUNCTION"
2369        this = f"{this} {self.sql(expression, 'this')}"
2370
2371        exists = " IF EXISTS" if expression.args.get("exists") else ""
2372        where = self.sql(expression, "where")
2373        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2374        using = self.expressions(expression, key="using", flat=True)
2375        using = f"{self.sep()}REPLACE USING ({using})" if using else ""
2376        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2377        on_conflict = self.sql(expression, "conflict")
2378        on_conflict = f" {on_conflict}" if on_conflict else ""
2379        by_name = " BY NAME" if expression.args.get("by_name") else ""
2380        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2381        returning = self.sql(expression, "returning")
2382
2383        if self.RETURNING_END:
2384            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2385        else:
2386            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2387
2388        partition_by = self.sql(expression, "partition")
2389        partition_by = f" {partition_by}" if partition_by else ""
2390        settings = self.sql(expression, "settings")
2391        settings = f" {settings}" if settings else ""
2392
2393        source = self.sql(expression, "source")
2394        source = f"TABLE {source}" if source else ""
2395
2396        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}"
2397        return self.prepend_ctes(expression, sql)
2398
2399    def introducer_sql(self, expression: exp.Introducer) -> str:
2400        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2401
2402    def kill_sql(self, expression: exp.Kill) -> str:
2403        kind = self.sql(expression, "kind")
2404        kind = f" {kind}" if kind else ""
2405        this = self.sql(expression, "this")
2406        this = f" {this}" if this else ""
2407        return f"KILL{kind}{this}"
2408
2409    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2410        return expression.name
2411
2412    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2413        return expression.name
2414
2415    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2416        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2417
2418        constraint = self.sql(expression, "constraint")
2419        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2420
2421        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2422        if conflict_keys:
2423            conflict_keys = f"({conflict_keys})"
2424
2425        index_predicate = self.sql(expression, "index_predicate")
2426        conflict_keys = f"{conflict_keys}{index_predicate} "
2427
2428        action = self.sql(expression, "action")
2429
2430        expressions = self.expressions(expression, flat=True)
2431        if expressions:
2432            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2433            expressions = f" {set_keyword}{expressions}"
2434
2435        where = self.sql(expression, "where")
2436        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2437
2438    def returning_sql(self, expression: exp.Returning) -> str:
2439        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2440
2441    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2442        fields = self.sql(expression, "fields")
2443        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2444        escaped = self.sql(expression, "escaped")
2445        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2446        items = self.sql(expression, "collection_items")
2447        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2448        keys = self.sql(expression, "map_keys")
2449        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2450        lines = self.sql(expression, "lines")
2451        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2452        null = self.sql(expression, "null")
2453        null = f" NULL DEFINED AS {null}" if null else ""
2454        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2455
2456    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2457        return f"WITH ({self.expressions(expression, flat=True)})"
2458
2459    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2460        this = f"{self.sql(expression, 'this')} INDEX"
2461        target = self.sql(expression, "target")
2462        target = f" FOR {target}" if target else ""
2463        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2464
2465    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2466        this = self.sql(expression, "this")
2467        kind = self.sql(expression, "kind")
2468        expr = self.sql(expression, "expression")
2469        return f"{this} ({kind} => {expr})"
2470
2471    def table_parts(self, expression: exp.Table) -> str:
2472        return ".".join(
2473            self.sql(part)
2474            for part in (
2475                expression.args.get("catalog"),
2476                expression.args.get("db"),
2477                expression.args.get("this"),
2478            )
2479            if part is not None
2480        )
2481
2482    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2483        table = self.table_parts(expression)
2484        only = "ONLY " if expression.args.get("only") else ""
2485        partition = self.sql(expression, "partition")
2486        partition = f" {partition}" if partition else ""
2487        version = self.sql(expression, "version")
2488        version = f" {version}" if version else ""
2489        alias = self.sql(expression, "alias")
2490        alias = f"{sep}{alias}" if alias else ""
2491
2492        sample = self.sql(expression, "sample")
2493        post_alias = ""
2494        pre_alias = ""
2495
2496        if self.dialect.ALIAS_POST_TABLESAMPLE:
2497            pre_alias = sample
2498        else:
2499            post_alias = sample
2500
2501        if self.dialect.ALIAS_POST_VERSION:
2502            pre_alias = f"{pre_alias}{version}"
2503        else:
2504            post_alias = f"{post_alias}{version}"
2505
2506        hints = self.expressions(expression, key="hints", sep=" ")
2507        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2508        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2509        joins = self.indent(
2510            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2511        )
2512        laterals = self.expressions(expression, key="laterals", sep="")
2513
2514        file_format = self.sql(expression, "format")
2515        pattern = self.sql(expression, "pattern")
2516        if file_format:
2517            pattern = f", PATTERN => {pattern}" if pattern else ""
2518            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2519        elif pattern:
2520            file_format = f" (PATTERN => {pattern})"
2521
2522        ordinality = expression.args.get("ordinality") or ""
2523        if ordinality:
2524            ordinality = f" WITH ORDINALITY{alias}"
2525            alias = ""
2526
2527        when = self.sql(expression, "when")
2528        if when:
2529            if self.HISTORICAL_DATA_POST_ALIAS:
2530                alias = f"{alias} {when}"
2531            else:
2532                table = f"{table} {when}"
2533
2534        changes = self.sql(expression, "changes")
2535        changes = f" {changes}" if changes else ""
2536
2537        rows_from = self.expressions(expression, key="rows_from")
2538        if rows_from:
2539            table = f"ROWS FROM {self.wrap(rows_from)}"
2540
2541        indexed = expression.args.get("indexed")
2542        if indexed is not None:
2543            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2544        else:
2545            indexed = ""
2546
2547        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2548
2549    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2550        table = self.func("TABLE", expression.this)
2551        alias = self.sql(expression, "alias")
2552        alias = f" AS {alias}" if alias else ""
2553        sample = self.sql(expression, "sample")
2554        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2555        joins = self.indent(
2556            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2557        )
2558        return f"{table}{alias}{pivots}{sample}{joins}"
2559
2560    def tablesample_sql(
2561        self,
2562        expression: exp.TableSample,
2563        tablesample_keyword: str | None = None,
2564    ) -> str:
2565        method = self.sql(expression, "method")
2566        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2567        numerator = self.sql(expression, "bucket_numerator")
2568        denominator = self.sql(expression, "bucket_denominator")
2569        field = self.sql(expression, "bucket_field")
2570        field = f" ON {field}" if field else ""
2571        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2572        seed = self.sql(expression, "seed")
2573        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2574
2575        size = self.sql(expression, "size")
2576        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2577            size = f"{size} ROWS"
2578
2579        percent = self.sql(expression, "percent")
2580        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2581            percent = f"{percent} PERCENT"
2582
2583        expr = f"{bucket}{percent}{size}"
2584        if self.TABLESAMPLE_REQUIRES_PARENS:
2585            expr = f"({expr})"
2586
2587        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2588
2589    def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None:
2590        # Returns the rewritten field.expressions list with PivotAlias wrappers injected where
2591        # the stored column name differs from the target dialect's natural output.
2592        columns = expression.args.get("columns")
2593        if not columns or len(expression.fields) != 1:
2594            return None
2595
2596        args = expression.args
2597        parser_cls = self.dialect.parser_class
2598
2599        tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS
2600        tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS
2601        tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING
2602
2603        src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings)
2604        src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns)
2605        src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming)
2606
2607        if (
2608            src_identify_pivot_strings == tgt_identify_pivot_strings
2609            and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns
2610            and src_pivot_column_naming == tgt_pivot_column_naming
2611        ):
2612            return None
2613
2614        in_exprs = expression.fields[0].expressions
2615        step = len(columns) // len(in_exprs)
2616
2617        # Derive the per-value suffix from the first stored column vs the first IN-list value.
2618        # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases.
2619        first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name
2620        first_stored = columns[0].name
2621
2622        # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed)
2623        if not first_stored.startswith(first_base):
2624            return None
2625
2626        suffix = first_stored[len(first_base) :]
2627
2628        # Whether the target dialect would append an agg-name suffix for this pivot.
2629        # Spark single-agg uniquely drops the agg alias entirely.
2630        target_has_suffix = (
2631            len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple"
2632        ) and any(a.alias for a in expression.expressions)
2633        source_has_suffix = suffix != ""
2634
2635        new_exprs: list[exp.Expression] = []
2636        modified = False
2637        for val_idx, e in enumerate(in_exprs):
2638            if isinstance(e, exp.PivotAlias):
2639                new_exprs.append(e)
2640                continue
2641
2642            i = val_idx * step
2643            stored_full = columns[i].name
2644            stored_value = stored_full[: -len(suffix)] if suffix else stored_full
2645            target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name
2646
2647            # Source had a suffix, but target won't apply one
2648            if source_has_suffix and not target_has_suffix:
2649                new_exprs.append(
2650                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True))
2651                )
2652                modified = True
2653            # Value-part mismatch (e.g. Snowflake's literal-style values vs others).
2654            elif stored_value != target_value:
2655                new_exprs.append(
2656                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True))
2657                )
2658                modified = True
2659            else:
2660                new_exprs.append(e)
2661
2662        return new_exprs if modified else None
2663
2664    def pivot_sql(self, expression: exp.Pivot) -> str:
2665        expressions = self.expressions(expression, flat=True)
2666        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2667
2668        group = self.sql(expression, "group")
2669
2670        if expression.this:
2671            this = self.sql(expression, "this")
2672            if not expressions:
2673                sql = f"UNPIVOT {this}"
2674            else:
2675                on = f"{self.seg('ON')} {expressions}"
2676                into = self.sql(expression, "into")
2677                into = f"{self.seg('INTO')} {into}" if into else ""
2678                using = self.expressions(expression, key="using", flat=True)
2679                using = f"{self.seg('USING')} {using}" if using else ""
2680                sql = f"{direction} {this}{on}{into}{using}{group}"
2681            return self.prepend_ctes(expression, sql)
2682
2683        if not expression.unpivot:
2684            # Wrap IN-list values with explicit aliases where the target dialect would differ
2685            new_field_exprs = self._pivot_in_value_aliases(expression)
2686            if new_field_exprs is not None:
2687                expression.fields[0].set("expressions", new_field_exprs)
2688
2689        alias = self.sql(expression, "alias")
2690        if alias:
2691            alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}"
2692
2693        fields = self.expressions(
2694            expression,
2695            "fields",
2696            sep=" ",
2697            dynamic=True,
2698            new_line=True,
2699            skip_first=True,
2700            skip_last=True,
2701        )
2702
2703        include_nulls = expression.args.get("include_nulls")
2704        if include_nulls is not None:
2705            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2706        else:
2707            nulls = ""
2708
2709        default_on_null = self.sql(expression, "default_on_null")
2710        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2711        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2712        return self.prepend_ctes(expression, sql)
2713
2714    def version_sql(self, expression: exp.Version) -> str:
2715        this = f"FOR {expression.name}"
2716        kind = expression.text("kind")
2717        expr = self.sql(expression, "expression")
2718        return f"{this} {kind} {expr}"
2719
2720    def tuple_sql(self, expression: exp.Tuple) -> str:
2721        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2722
2723    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2724        """
2725        Returns (join_sql, from_sql) for UPDATE statements.
2726        - join_sql: placed after UPDATE table, before SET
2727        - from_sql: placed after SET clause (standard position)
2728        Dialects like MySQL need to convert FROM to JOIN syntax.
2729        """
2730        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2731            return ("", self.sql(expression, "from_"))
2732
2733        # Qualify unqualified columns in SET clause with the target table
2734        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2735        target_table = expression.this
2736        if isinstance(target_table, exp.Table):
2737            target_name = exp.to_identifier(target_table.alias_or_name)
2738            for eq in expression.expressions:
2739                col = eq.this
2740                if isinstance(col, exp.Column) and not col.table:
2741                    col.set("table", target_name)
2742
2743        table = from_expr.this
2744        if nested_joins := table.args.get("joins", []):
2745            table.set("joins", None)
2746
2747        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2748        for nested in nested_joins:
2749            if not nested.args.get("on") and not nested.args.get("using"):
2750                nested.set("on", exp.true())
2751            join_sql += self.sql(nested)
2752
2753        return (join_sql, "")
2754
2755    def update_sql(self, expression: exp.Update) -> str:
2756        hint = self.sql(expression, "hint")
2757        this = self.sql(expression, "this")
2758        join_sql, from_sql = self._update_from_joins_sql(expression)
2759        set_sql = self.expressions(expression, flat=True)
2760        where_sql = self.sql(expression, "where")
2761        returning = self.sql(expression, "returning")
2762        order = self.sql(expression, "order")
2763        limit = self.sql(expression, "limit")
2764        if self.RETURNING_END:
2765            expression_sql = f"{from_sql}{where_sql}{returning}"
2766        else:
2767            expression_sql = f"{returning}{from_sql}{where_sql}"
2768        options = self.expressions(expression, key="options")
2769        options = f" OPTION({options})" if options else ""
2770        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2771        return self.prepend_ctes(expression, sql)
2772
2773    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2774        values_as_table = values_as_table and self.VALUES_AS_TABLE
2775
2776        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2777        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2778            args = self.expressions(expression)
2779            alias = self.sql(expression, "alias")
2780            values = f"VALUES{self.seg('')}{args}"
2781            values = (
2782                f"({values})"
2783                if self.WRAP_DERIVED_VALUES
2784                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2785                else values
2786            )
2787            values = self.query_modifiers(expression, values)
2788            return f"{values} AS {alias}" if alias else values
2789
2790        # Converts `VALUES...` expression into a series of select unions.
2791        alias_node = expression.args.get("alias")
2792        column_names = alias_node and alias_node.columns
2793
2794        selects: list[exp.Query] = []
2795
2796        for i, tup in enumerate(expression.expressions):
2797            row = tup.expressions
2798
2799            if i == 0 and column_names:
2800                row = [
2801                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2802                ]
2803
2804            selects.append(exp.Select(expressions=row))
2805
2806        if self.pretty:
2807            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2808            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2809            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2810            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2811            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2812
2813        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2814        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2815        return f"({unions}){alias}"
2816
2817    def var_sql(self, expression: exp.Var) -> str:
2818        return self.sql(expression, "this")
2819
2820    @unsupported_args("expressions")
2821    def into_sql(self, expression: exp.Into) -> str:
2822        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2823        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2824        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2825
2826    def from_sql(self, expression: exp.From) -> str:
2827        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2828
2829    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2830        grouping_sets = self.expressions(expression, indent=False)
2831        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2832
2833    def rollup_sql(self, expression: exp.Rollup) -> str:
2834        expressions = self.expressions(expression, indent=False)
2835        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2836
2837    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2838        this = self.sql(expression, "this")
2839
2840        columns = self.expressions(expression, flat=True)
2841
2842        from_sql = self.sql(expression, "from_index")
2843        from_sql = f" FROM {from_sql}" if from_sql else ""
2844
2845        properties = expression.args.get("properties")
2846        properties_sql = (
2847            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2848        )
2849
2850        return f"{this}({columns}){from_sql}{properties_sql}"
2851
2852    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2853        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2854
2855    def cube_sql(self, expression: exp.Cube) -> str:
2856        expressions = self.expressions(expression, indent=False)
2857        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2858
2859    def group_sql(self, expression: exp.Group) -> str:
2860        group_by_all = expression.args.get("all")
2861        if group_by_all is True:
2862            modifier = " ALL"
2863        elif group_by_all is False:
2864            modifier = " DISTINCT"
2865        else:
2866            modifier = ""
2867
2868        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2869
2870        grouping_sets = self.expressions(expression, key="grouping_sets")
2871        cube = self.expressions(expression, key="cube")
2872        rollup = self.expressions(expression, key="rollup")
2873
2874        groupings = csv(
2875            self.seg(grouping_sets) if grouping_sets else "",
2876            self.seg(cube) if cube else "",
2877            self.seg(rollup) if rollup else "",
2878            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2879            sep=self.GROUPINGS_SEP,
2880        )
2881
2882        if (
2883            expression.expressions
2884            and groupings
2885            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2886        ):
2887            add_separator = True
2888
2889            if grouping_sets:
2890                if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX:
2891                    add_separator = False
2892                else:
2893                    self.unsupported(
2894                        "GROUPING SETS without a comma after GROUP BY expressions is not supported"
2895                    )
2896
2897            if add_separator:
2898                group_by = f"{group_by}{self.GROUPINGS_SEP}"
2899
2900        return f"{group_by}{groupings}"
2901
2902    def having_sql(self, expression: exp.Having) -> str:
2903        this = self.indent(self.sql(expression, "this"))
2904        return f"{self.seg('HAVING')}{self.sep()}{this}"
2905
2906    def connect_sql(self, expression: exp.Connect) -> str:
2907        start = self.sql(expression, "start")
2908        start = self.seg(f"START WITH {start}") if start else ""
2909        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2910        connect = self.sql(expression, "connect")
2911        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2912        return start + connect
2913
2914    def prior_sql(self, expression: exp.Prior) -> str:
2915        return f"PRIOR {self.sql(expression, 'this')}"
2916
2917    def join_sql(self, expression: exp.Join) -> str:
2918        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2919            side = None
2920        else:
2921            side = expression.side
2922
2923        op_sql = " ".join(
2924            op
2925            for op in (
2926                expression.method,
2927                "GLOBAL" if expression.args.get("global_") else None,
2928                side,
2929                expression.kind,
2930                expression.hint if self.JOIN_HINTS else None,
2931                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2932            )
2933            if op
2934        )
2935        match_cond = self.sql(expression, "match_condition")
2936        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2937        on_sql = self.sql(expression, "on")
2938        using = expression.args.get("using")
2939
2940        if not on_sql and using:
2941            on_sql = csv(*(self.sql(column) for column in using))
2942
2943        this = expression.this
2944        this_sql = self.sql(this)
2945
2946        exprs = self.expressions(expression)
2947        if exprs:
2948            this_sql = f"{this_sql},{self.seg(exprs)}"
2949
2950        if on_sql:
2951            on_sql = self.indent(on_sql, skip_first=True)
2952            space = self.seg(" " * self.pad) if self.pretty else " "
2953            if using:
2954                on_sql = f"{space}USING ({on_sql})"
2955            else:
2956                on_sql = f"{space}ON {on_sql}"
2957        elif not op_sql:
2958            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2959                return f" {this_sql}"
2960
2961            return f", {this_sql}"
2962
2963        if op_sql != "STRAIGHT_JOIN":
2964            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2965
2966        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2967        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2968
2969    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2970        args = self.expressions(expression, flat=True)
2971        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2972        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2973
2974    def lateral_op(self, expression: exp.Lateral) -> str:
2975        cross_apply = expression.args.get("cross_apply")
2976
2977        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2978        if cross_apply is True:
2979            op = "INNER JOIN "
2980        elif cross_apply is False:
2981            op = "LEFT JOIN "
2982        else:
2983            op = ""
2984
2985        return f"{op}LATERAL"
2986
2987    def lateral_sql(self, expression: exp.Lateral) -> str:
2988        this = self.sql(expression, "this")
2989
2990        if expression.args.get("view"):
2991            alias = expression.args["alias"]
2992            columns = self.expressions(alias, key="columns", flat=True)
2993            table = f" {alias.name}" if alias.name else ""
2994            columns = f" AS {columns}" if columns else ""
2995            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2996            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2997
2998        table_alias = expression.args.get("alias")
2999        offset = expression.this.args.get("offset")
3000
3001        if (
3002            self.UNNEST_WITH_ORDINALITY
3003            and table_alias
3004            and isinstance(expression.this, exp.Unnest)
3005            and isinstance(offset, exp.Identifier)
3006        ):
3007            # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset
3008            table_alias = table_alias.copy()
3009            table_alias.append("columns", offset.copy())
3010
3011        alias = self.sql(table_alias)
3012        alias = f" AS {alias}" if alias else ""
3013
3014        ordinality = expression.args.get("ordinality") or ""
3015        if ordinality:
3016            ordinality = f" WITH ORDINALITY{alias}"
3017            alias = ""
3018
3019        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
3020
3021    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
3022        this = self.sql(expression, "this")
3023
3024        if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL:
3025            return this
3026
3027        args = [
3028            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
3029            for e in (expression.args.get(k) for k in ("offset", "expression"))
3030            if e
3031        ]
3032
3033        args_sql = ", ".join(self.sql(e) for e in args)
3034        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
3035        expressions = self.expressions(expression, flat=True)
3036        limit_options = self.sql(expression, "limit_options")
3037        expressions = f" BY {expressions}" if expressions else ""
3038
3039        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
3040
3041    def offset_sql(self, expression: exp.Offset) -> str:
3042        this = self.sql(expression, "this")
3043        value = expression.expression
3044        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
3045        expressions = self.expressions(expression, flat=True)
3046        expressions = f" BY {expressions}" if expressions else ""
3047        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
3048
3049    def setitem_sql(self, expression: exp.SetItem) -> str:
3050        kind = self.sql(expression, "kind")
3051        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
3052            kind = ""
3053        else:
3054            kind = f"{kind} " if kind else ""
3055        this = self.sql(expression, "this")
3056        expressions = self.expressions(expression)
3057        collate = self.sql(expression, "collate")
3058        collate = f" COLLATE {collate}" if collate else ""
3059        global_ = "GLOBAL " if expression.args.get("global_") else ""
3060        return f"{global_}{kind}{this}{expressions}{collate}"
3061
3062    def set_sql(self, expression: exp.Set) -> str:
3063        expressions = f" {self.expressions(expression, flat=True)}"
3064        tag = " TAG" if expression.args.get("tag") else ""
3065        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
3066
3067    def queryband_sql(self, expression: exp.QueryBand) -> str:
3068        this = self.sql(expression, "this")
3069        update = " UPDATE" if expression.args.get("update") else ""
3070        scope = self.sql(expression, "scope")
3071        scope = f" FOR {scope}" if scope else ""
3072
3073        return f"QUERY_BAND = {this}{update}{scope}"
3074
3075    def pragma_sql(self, expression: exp.Pragma) -> str:
3076        return f"PRAGMA {self.sql(expression, 'this')}"
3077
3078    def lock_sql(self, expression: exp.Lock) -> str:
3079        if not self.LOCKING_READS_SUPPORTED:
3080            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
3081            return ""
3082
3083        update = expression.args["update"]
3084        key = expression.args.get("key")
3085        if update:
3086            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
3087        else:
3088            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
3089        expressions = self.expressions(expression, flat=True)
3090        expressions = f" OF {expressions}" if expressions else ""
3091        wait = expression.args.get("wait")
3092
3093        if wait is not None:
3094            if isinstance(wait, exp.Literal):
3095                wait = f" WAIT {self.sql(wait)}"
3096            else:
3097                wait = " NOWAIT" if wait else " SKIP LOCKED"
3098
3099        return f"{lock_type}{expressions}{wait or ''}"
3100
3101    def literal_sql(self, expression: exp.Literal) -> str:
3102        text = expression.this or ""
3103        if expression.is_string:
3104            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
3105        return text
3106
3107    def escape_str(
3108        self,
3109        text: str,
3110        escape_backslash: bool = True,
3111        delimiter: str | None = None,
3112        escaped_delimiter: str | None = None,
3113        is_byte_string: bool = False,
3114        is_bytes: bool = False,
3115    ) -> str:
3116        if is_byte_string:
3117            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
3118        else:
3119            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
3120
3121        if supports_escape_sequences:
3122            text = "".join(
3123                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
3124                for ch in text
3125            )
3126
3127            if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES:
3128                # Escape high bytes so they aren't re-encoded as UTF-8 characters.
3129                text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text)
3130
3131        delimiter = delimiter or self.dialect.QUOTE_END
3132        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
3133
3134        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3135
3136    def loaddata_sql(self, expression: exp.LoadData) -> str:
3137        is_overwrite = expression.args.get("overwrite")
3138        overwrite = " OVERWRITE" if is_overwrite else ""
3139        this = self.sql(expression, "this")
3140
3141        files = expression.args.get("files")
3142        if files:
3143            files_sql = self.expressions(files, flat=True)
3144            files_sql = f"FILES{self.wrap(files_sql)}"
3145            if is_overwrite:
3146                this = f" {this}"
3147            elif expression.args.get("temp"):
3148                this = f" INTO TEMP TABLE {this}"
3149            else:
3150                this = f" INTO TABLE {this}"
3151            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
3152
3153        local = " LOCAL" if expression.args.get("local") else ""
3154        inpath = f" INPATH {self.sql(expression, 'inpath')}"
3155        this = f" INTO TABLE {this}"
3156        partition = self.sql(expression, "partition")
3157        partition = f" {partition}" if partition else ""
3158        input_format = self.sql(expression, "input_format")
3159        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3160        serde = self.sql(expression, "serde")
3161        serde = f" SERDE {serde}" if serde else ""
3162        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3163
3164    def null_sql(self, *_) -> str:
3165        return "NULL"
3166
3167    def boolean_sql(self, expression: exp.Boolean) -> str:
3168        return "TRUE" if expression.this else "FALSE"
3169
3170    def booland_sql(self, expression: exp.Booland) -> str:
3171        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
3172
3173    def boolor_sql(self, expression: exp.Boolor) -> str:
3174        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
3175
3176    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3177        this = self.sql(expression, "this")
3178        this = f"{this} " if this else this
3179        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3180        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3181
3182    def withfill_sql(self, expression: exp.WithFill) -> str:
3183        from_sql = self.sql(expression, "from_")
3184        from_sql = f" FROM {from_sql}" if from_sql else ""
3185        to_sql = self.sql(expression, "to")
3186        to_sql = f" TO {to_sql}" if to_sql else ""
3187        step_sql = self.sql(expression, "step")
3188        step_sql = f" STEP {step_sql}" if step_sql else ""
3189        interpolated_values = [
3190            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3191            if isinstance(e, exp.Alias)
3192            else self.sql(e, "this")
3193            for e in expression.args.get("interpolate") or []
3194        ]
3195        interpolate = (
3196            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3197        )
3198        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3199
3200    def cluster_sql(self, expression: exp.Cluster) -> str:
3201        return self.op_expressions("CLUSTER BY", expression)
3202
3203    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3204        if expression.this:
3205            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3206            return ""
3207        expressions = self.expressions(expression, flat=True)
3208        return f"CLUSTER BY ({expressions})"
3209
3210    def distribute_sql(self, expression: exp.Distribute) -> str:
3211        return self.op_expressions("DISTRIBUTE BY", expression)
3212
3213    def sort_sql(self, expression: exp.Sort) -> str:
3214        return self.op_expressions("SORT BY", expression)
3215
3216    def _resolve_ordered_for_null_ordering_simulation(
3217        self, expression: exp.Ordered
3218    ) -> exp.Expr | None:
3219        """Resolve a bare ORDER BY name against the enclosing SELECT projection.
3220
3221        Returns the underlying expression of the uniquely-matching projection
3222        (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE
3223        simulation, since the CASE is evaluated in FROM-clause scope rather
3224        than alias scope (MySQL error 1052). Returns None if no safe
3225        substitution applies, leaving the original behaviour unchanged.
3226        """
3227        this = expression.this
3228        if not (isinstance(this, exp.Column) and not this.table):
3229            return None
3230
3231        # Only a query's own ORDER BY can reference its projection aliases
3232        order = expression.parent
3233        ancestor = order.parent if isinstance(order, exp.Order) else None
3234        if not isinstance(ancestor, exp.Select):
3235            return None
3236
3237        column_name = this.name
3238        matched: list[exp.Expr] = [
3239            p.this if isinstance(p, exp.Alias) else p
3240            for p in ancestor.selects
3241            if p.output_name == column_name
3242        ]
3243        match = matched[0] if len(matched) == 1 else None
3244
3245        # Skip the substitution when it would be identical to the existing
3246        # reference (e.g. ``SELECT col FROM t ORDER BY col``).
3247        if isinstance(match, exp.Column) and not match.table and match.name == column_name:
3248            return None
3249
3250        return match
3251
3252    def ordered_sql(self, expression: exp.Ordered) -> str:
3253        desc = expression.args.get("desc")
3254        asc = not desc
3255
3256        nulls_first = expression.args.get("nulls_first")
3257        nulls_last = not nulls_first
3258        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3259        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3260        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3261
3262        this = self.sql(expression, "this")
3263
3264        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3265        nulls_sort_change = ""
3266        if nulls_first and (
3267            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3268        ):
3269            nulls_sort_change = " NULLS FIRST"
3270        elif (
3271            nulls_last
3272            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3273            and not nulls_are_last
3274        ):
3275            nulls_sort_change = " NULLS LAST"
3276
3277        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3278        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3279            window = expression.find_ancestor(exp.Window, exp.Select)
3280
3281            if isinstance(window, exp.Window):
3282                window_this = window.this
3283                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3284                    window_this = window_this.this
3285                spec = window.args.get("spec")
3286            else:
3287                window_this = None
3288                spec = None
3289
3290            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3291            # without a spec or with a ROWS spec, but not with RANGE
3292            if not (
3293                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3294                and (not spec or spec.text("kind").upper() == "ROWS")
3295            ):
3296                if window_this and spec:
3297                    self.unsupported(
3298                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3299                    )
3300                    nulls_sort_change = ""
3301                elif self.NULL_ORDERING_SUPPORTED is False and (
3302                    (asc and nulls_sort_change == " NULLS LAST")
3303                    or (desc and nulls_sort_change == " NULLS FIRST")
3304                ):
3305                    # BigQuery does not allow these ordering/nulls combinations when used under
3306                    # an aggregation func or under a window containing one
3307                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3308
3309                    if isinstance(ancestor, exp.Window):
3310                        ancestor = ancestor.this
3311                    if isinstance(ancestor, exp.AggFunc):
3312                        self.unsupported(
3313                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3314                        )
3315                        nulls_sort_change = ""
3316                elif self.NULL_ORDERING_SUPPORTED is None:
3317                    if expression.this.is_int:
3318                        self.unsupported(
3319                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3320                        )
3321                    elif not isinstance(expression.this, exp.Rand):
3322                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3323                        target = self.sql(resolved) if resolved is not None else this
3324                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3325                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3326                    nulls_sort_change = ""
3327
3328        with_fill = self.sql(expression, "with_fill")
3329        with_fill = f" {with_fill}" if with_fill else ""
3330
3331        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
3332
3333    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3334        window_frame = self.sql(expression, "window_frame")
3335        window_frame = f"{window_frame} " if window_frame else ""
3336
3337        this = self.sql(expression, "this")
3338
3339        return f"{window_frame}{this}"
3340
3341    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3342        partition = self.partition_by_sql(expression)
3343        order = self.sql(expression, "order")
3344        measures = self.expressions(expression, key="measures")
3345        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3346        rows = self.sql(expression, "rows")
3347        rows = self.seg(rows) if rows else ""
3348        after = self.sql(expression, "after")
3349        after = self.seg(after) if after else ""
3350        pattern = self.sql(expression, "pattern")
3351        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3352        definition_sqls = [
3353            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3354            for definition in expression.args.get("define", [])
3355        ]
3356        definitions = self.expressions(sqls=definition_sqls)
3357        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3358        body = "".join(
3359            (
3360                partition,
3361                order,
3362                measures,
3363                rows,
3364                after,
3365                pattern,
3366                define,
3367            )
3368        )
3369        alias = self.sql(expression, "alias")
3370        alias = f" {alias}" if alias else ""
3371        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3372
3373    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3374        limit = expression.args.get("limit")
3375
3376        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3377            count = limit.args.get("count")
3378            # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL
3379            # standard; emitting a bare "LIMIT" here would produce invalid SQL.
3380            limit = exp.Limit(
3381                expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1)
3382            )
3383        elif (
3384            self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all
3385        ):
3386            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3387
3388        return csv(
3389            *sqls,
3390            *[self.sql(join) for join in expression.args.get("joins") or []],
3391            self.sql(expression, "match"),
3392            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3393            self.sql(expression, "prewhere"),
3394            self.sql(expression, "where"),
3395            self.sql(expression, "connect"),
3396            self.sql(expression, "group"),
3397            self.sql(expression, "having"),
3398            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3399            self.sql(expression, "order"),
3400            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3401            *self.after_limit_modifiers(expression),
3402            self.sql(expression, "for_"),
3403            self.options_modifier(expression),
3404            sep="",
3405        )
3406
3407    def options_modifier(self, expression: exp.Expr) -> str:
3408        options = self.expressions(expression, key="options")
3409        return f" {options}" if options else ""
3410
3411    def forclause_sql(self, expression: exp.ForClause) -> str:
3412        kind = expression.args["kind"]
3413        if kind == "BROWSE":
3414            return f"{self.sep()}FOR BROWSE"
3415        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3416        # the target dialect doesn't support QueryOption, so we drop the clause.
3417        options = self.expressions(expression, key="expressions")
3418        if not options:
3419            return ""
3420        return f"{self.sep()}FOR {kind}{self.seg(options)}"
3421
3422    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3423        self.unsupported("Unsupported query option.")
3424        return ""
3425
3426    def offset_limit_modifiers(
3427        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3428    ) -> list[str]:
3429        return [
3430            self.sql(expression, "offset") if fetch else self.sql(limit),
3431            self.sql(limit) if fetch else self.sql(expression, "offset"),
3432        ]
3433
3434    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3435        locks = self.expressions(expression, key="locks", sep=" ")
3436        locks = f" {locks}" if locks else ""
3437        return [locks, self.sql(expression, "sample")]
3438
3439    def select_sql(self, expression: exp.Select) -> str:
3440        into = expression.args.get("into")
3441        if not self.SUPPORTS_SELECT_INTO and into:
3442            into.pop()
3443
3444        hint = self.sql(expression, "hint")
3445        distinct = self.sql(expression, "distinct")
3446        distinct = f" {distinct}" if distinct else ""
3447        kind = self.sql(expression, "kind")
3448
3449        limit = expression.args.get("limit")
3450        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3451            top = self.limit_sql(limit, top=True)
3452            limit.pop()
3453        else:
3454            top = ""
3455
3456        expressions = self.expressions(expression)
3457
3458        if kind:
3459            if kind in self.SELECT_KINDS:
3460                kind = f" AS {kind}"
3461            else:
3462                if kind == "STRUCT":
3463                    expressions = self.expressions(
3464                        sqls=[
3465                            self.sql(
3466                                exp.Struct(
3467                                    expressions=[
3468                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3469                                        if isinstance(e, exp.Alias)
3470                                        else e
3471                                        for e in expression.expressions
3472                                    ]
3473                                )
3474                            )
3475                        ]
3476                    )
3477                kind = ""
3478
3479        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3480        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3481
3482        exclude = expression.args.get("exclude")
3483
3484        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3485            exclude_sql = self.expressions(sqls=exclude, flat=True)
3486            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3487
3488        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3489        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3490        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3491        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3492        sql = self.query_modifiers(
3493            expression,
3494            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3495            self.sql(expression, "into", comment=False),
3496            self.sql(expression, "from_", comment=False),
3497        )
3498
3499        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3500        if expression.args.get("with_"):
3501            sql = self.maybe_comment(sql, expression)
3502            expression.pop_comments()
3503
3504        sql = self.prepend_ctes(expression, sql)
3505
3506        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3507            expression.set("exclude", None)
3508            subquery = expression.subquery(copy=False)
3509            star = exp.Star(except_=exclude)
3510            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3511
3512        if not self.SUPPORTS_SELECT_INTO and into:
3513            if into.args.get("temporary"):
3514                table_kind = " TEMPORARY"
3515            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3516                table_kind = " UNLOGGED"
3517            else:
3518                table_kind = ""
3519            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3520
3521        return sql
3522
3523    def schema_sql(self, expression: exp.Schema) -> str:
3524        this = self.sql(expression, "this")
3525        sql = self.schema_columns_sql(expression)
3526        return f"{this} {sql}" if this and sql else this or sql
3527
3528    def schema_columns_sql(self, expression: exp.Expr) -> str:
3529        if expression.expressions:
3530            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3531        return ""
3532
3533    def star_sql(self, expression: exp.Star) -> str:
3534        except_ = self.expressions(expression, key="except_", flat=True)
3535        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3536        replace = self.expressions(expression, key="replace", flat=True)
3537        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3538        rename = self.expressions(expression, key="rename", flat=True)
3539        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3540        ilike = self.sql(expression, "ilike")
3541        ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else ""
3542        return f"*{ilike}{except_}{replace}{rename}"
3543
3544    def parameter_sql(self, expression: exp.Parameter) -> str:
3545        this = self.sql(expression, "this")
3546        return f"{self.PARAMETER_TOKEN}{this}"
3547
3548    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3549        this = self.sql(expression, "this")
3550        kind = expression.text("kind")
3551        if kind:
3552            kind = f"{kind}."
3553        return f"@@{kind}{this}"
3554
3555    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3556        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3557
3558    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3559        alias = self.sql(expression, "alias")
3560        alias = f"{sep}{alias}" if alias else ""
3561        sample = self.sql(expression, "sample")
3562        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3563            alias = f"{sample}{alias}"
3564
3565            # Set to None so it's not generated again by self.query_modifiers()
3566            expression.set("sample", None)
3567
3568        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3569        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3570        return self.prepend_ctes(expression, sql)
3571
3572    def qualify_sql(self, expression: exp.Qualify) -> str:
3573        this = self.indent(self.sql(expression, "this"))
3574        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3575
3576    def unnest_sql(self, expression: exp.Unnest) -> str:
3577        args = self.expressions(expression, flat=True)
3578
3579        alias = expression.args.get("alias")
3580        offset = expression.args.get("offset")
3581
3582        if self.UNNEST_WITH_ORDINALITY:
3583            if alias and isinstance(offset, exp.Expr):
3584                alias.append("columns", offset)
3585                expression.set("offset", None)
3586
3587        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3588            columns = alias.columns
3589            alias = self.sql(columns[0]) if columns else ""
3590        else:
3591            alias = self.sql(alias)
3592
3593        alias = f" AS {alias}" if alias else alias
3594        if self.UNNEST_WITH_ORDINALITY:
3595            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3596        else:
3597            if isinstance(offset, exp.Expr):
3598                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3599            elif offset:
3600                suffix = f"{alias} WITH OFFSET"
3601            else:
3602                suffix = alias
3603
3604        return f"UNNEST({args}){suffix}"
3605
3606    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3607        return ""
3608
3609    def where_sql(self, expression: exp.Where) -> str:
3610        this = self.indent(self.sql(expression, "this"))
3611        return f"{self.seg('WHERE')}{self.sep()}{this}"
3612
3613    def window_sql(self, expression: exp.Window) -> str:
3614        this = self.sql(expression, "this")
3615        partition = self.partition_by_sql(expression)
3616        order = expression.args.get("order")
3617        order = self.order_sql(order, flat=True) if order else ""
3618        spec = self.sql(expression, "spec")
3619        alias = self.sql(expression, "alias")
3620        over = self.sql(expression, "over") or "OVER"
3621
3622        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3623
3624        first = expression.args.get("first")
3625        if first is None:
3626            first = ""
3627        else:
3628            first = "FIRST" if first else "LAST"
3629
3630        if not partition and not order and not spec and alias:
3631            return f"{this} {alias}"
3632
3633        args = self.format_args(
3634            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3635        )
3636        return f"{this} ({args})"
3637
3638    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3639        partition = self.expressions(expression, key="partition_by", flat=True)
3640        return f"PARTITION BY {partition}" if partition else ""
3641
3642    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3643        kind = self.sql(expression, "kind")
3644        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3645        end = (
3646            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3647            or "CURRENT ROW"
3648        )
3649
3650        window_spec = f"{kind} BETWEEN {start} AND {end}"
3651
3652        exclude = self.sql(expression, "exclude")
3653        if exclude:
3654            if self.SUPPORTS_WINDOW_EXCLUDE:
3655                window_spec += f" EXCLUDE {exclude}"
3656            else:
3657                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3658
3659        return window_spec
3660
3661    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3662        this = self.sql(expression, "this")
3663        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3664        return f"{this} WITHIN GROUP ({expression_sql})"
3665
3666    def between_sql(self, expression: exp.Between) -> str:
3667        this = self.sql(expression, "this")
3668        low = self.sql(expression, "low")
3669        high = self.sql(expression, "high")
3670        symmetric = expression.args.get("symmetric")
3671
3672        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3673            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3674
3675        flag = (
3676            " SYMMETRIC"
3677            if symmetric
3678            else " ASYMMETRIC"
3679            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3680            else ""  # silently drop ASYMMETRIC – semantics identical
3681        )
3682        return f"{this} BETWEEN{flag} {low} AND {high}"
3683
3684    def bracket_offset_expressions(
3685        self, expression: exp.Bracket, index_offset: int | None = None
3686    ) -> list[exp.Expr]:
3687        if expression.args.get("json_access"):
3688            return expression.expressions
3689
3690        return apply_index_offset(
3691            expression.this,
3692            expression.expressions,
3693            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3694            dialect=self.dialect,
3695        )
3696
3697    def bracket_sql(self, expression: exp.Bracket) -> str:
3698        expressions = self.bracket_offset_expressions(expression)
3699        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3700        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3701
3702    def all_sql(self, expression: exp.All) -> str:
3703        this = self.sql(expression, "this")
3704        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3705            this = self.wrap(this)
3706        return f"ALL {this}"
3707
3708    def any_sql(self, expression: exp.Any) -> str:
3709        this = self.sql(expression, "this")
3710        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3711            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3712                this = self.wrap(this)
3713            return f"ANY{this}"
3714        return f"ANY {this}"
3715
3716    def exists_sql(self, expression: exp.Exists) -> str:
3717        return f"EXISTS{self.wrap(expression)}"
3718
3719    def case_sql(self, expression: exp.Case) -> str:
3720        this = self.sql(expression, "this")
3721        statements = [f"CASE {this}" if this else "CASE"]
3722
3723        for e in expression.args["ifs"]:
3724            statements.append(f"WHEN {self.sql(e, 'this')}")
3725            statements.append(f"THEN {self.sql(e, 'true')}")
3726
3727        default = self.sql(expression, "default")
3728
3729        if default:
3730            statements.append(f"ELSE {default}")
3731
3732        statements.append("END")
3733
3734        if self.pretty and self.too_wide(statements):
3735            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3736
3737        return " ".join(statements)
3738
3739    def constraint_sql(self, expression: exp.Constraint) -> str:
3740        this = self.sql(expression, "this")
3741        expressions = self.expressions(expression, flat=True)
3742        return f"CONSTRAINT {this} {expressions}"
3743
3744    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3745        order = expression.args.get("order")
3746        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3747        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3748
3749    def extract_sql(self, expression: exp.Extract) -> str:
3750        import sqlglot.dialects.dialect
3751
3752        this = (
3753            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3754            if self.NORMALIZE_EXTRACT_DATE_PARTS
3755            else expression.this
3756        )
3757        if self.EXTRACT_ALLOWS_QUOTES:
3758            this_sql = self.sql(this)
3759        elif isinstance(this, exp.WeekStart):
3760            this_sql = self.weekstart_name(this)
3761        else:
3762            this_sql = this.name
3763        expression_sql = self.sql(expression, "expression")
3764
3765        return f"EXTRACT({this_sql} FROM {expression_sql})"
3766
3767    def trim_sql(self, expression: exp.Trim) -> str:
3768        trim_type = self.sql(expression, "position")
3769
3770        if trim_type == "LEADING":
3771            func_name = "LTRIM"
3772        elif trim_type == "TRAILING":
3773            func_name = "RTRIM"
3774        else:
3775            func_name = "TRIM"
3776
3777        return self.func(func_name, expression.this, expression.expression)
3778
3779    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3780        args = expression.expressions
3781        if isinstance(expression, exp.ConcatWs):
3782            args = args[1:]  # Skip the delimiter
3783
3784        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3785            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3786
3787        concat_coalesce = (
3788            self.dialect.CONCAT_WS_COALESCE
3789            if isinstance(expression, exp.ConcatWs)
3790            else self.dialect.CONCAT_COALESCE
3791        )
3792
3793        if not concat_coalesce and expression.args.get("coalesce"):
3794
3795            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3796                if not e.type:
3797                    import sqlglot.optimizer.annotate_types
3798
3799                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3800
3801                if e.is_string or e.is_type(exp.DType.ARRAY):
3802                    return e
3803
3804                return exp.func("coalesce", e, exp.Literal.string(""))
3805
3806            args = [_wrap_with_coalesce(e) for e in args]
3807
3808        return args
3809
3810    def concat_sql(self, expression: exp.Concat) -> str:
3811        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3812            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3813            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3814            # instead of coalescing them to empty string.
3815            import sqlglot.dialects.dialect
3816
3817            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3818
3819        expressions = self.convert_concat_args(expression)
3820
3821        # Some dialects don't allow a single-argument CONCAT call
3822        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3823            return self.sql(expressions[0])
3824
3825        return self.func("CONCAT", *expressions)
3826
3827    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3828        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3829            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3830            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3831            all_args = expression.expressions
3832            expression.set("coalesce", True)
3833            return self.sql(
3834                exp.case()
3835                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3836                .else_(expression)
3837            )
3838
3839        return self.func(
3840            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3841        )
3842
3843    def check_sql(self, expression: exp.Check) -> str:
3844        this = self.sql(expression, key="this")
3845        return f"CHECK ({this})"
3846
3847    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3848        this = self.sql(expression, "this")
3849        this = f" {this}" if this else ""
3850        expressions = self.expressions(expression, flat=True)
3851        expressions = f" ({expressions})" if expressions else ""
3852        reference = self.sql(expression, "reference")
3853        reference = f" {reference}" if reference else ""
3854        delete = self.sql(expression, "delete")
3855        delete = f" ON DELETE {delete}" if delete else ""
3856        update = self.sql(expression, "update")
3857        update = f" ON UPDATE {update}" if update else ""
3858        options = self.expressions(expression, key="options", flat=True, sep=" ")
3859        options = f" {options}" if options else ""
3860        return f"FOREIGN KEY{this}{expressions}{reference}{delete}{update}{options}"
3861
3862    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3863        this = self.sql(expression, "this")
3864        this = f" {this}" if this else ""
3865        expressions = self.expressions(expression, flat=True)
3866        include = self.sql(expression, "include")
3867        options = self.expressions(expression, key="options", flat=True, sep=" ")
3868        options = f" {options}" if options else ""
3869        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3870
3871    def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str:
3872        self.unsupported("TIMESERIES primary key columns are not supported")
3873        return self.sql(expression, "this")
3874
3875    def if_sql(self, expression: exp.If) -> str:
3876        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3877
3878    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3879        if self.MATCH_AGAINST_TABLE_PREFIX:
3880            expressions = []
3881            for expr in expression.expressions:
3882                if isinstance(expr, exp.Table):
3883                    expressions.append(f"TABLE {self.sql(expr)}")
3884                else:
3885                    expressions.append(expr)
3886        else:
3887            expressions = expression.expressions
3888
3889        modifier = expression.args.get("modifier")
3890        modifier = f" {modifier}" if modifier else ""
3891        return (
3892            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3893        )
3894
3895    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3896        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3897
3898    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3899        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3900
3901        if self.QUOTE_JSON_PATH:
3902            path = self.escape_str(path)
3903            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3904
3905        return path
3906
3907    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3908        if isinstance(expression, exp.JSONPathPart):
3909            transform = self.TRANSFORMS.get(expression.__class__)
3910            if not callable(transform):
3911                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3912                return ""
3913
3914            return transform(self, expression)
3915
3916        if isinstance(expression, int):
3917            return str(expression)
3918
3919        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3920            escaped = expression.replace("'", "\\'")
3921            escaped = f"'{escaped}'"
3922        else:
3923            escaped = expression.replace('"', '\\"')
3924            escaped = f'"{escaped}"'
3925
3926        return escaped
3927
3928    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3929        return f"{self.sql(expression, 'this')} FORMAT JSON"
3930
3931    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3932        # Output the Teradata column FORMAT override.
3933        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3934        this = self.sql(expression, "this")
3935        fmt = self.sql(expression, "format")
3936        return f"{this} (FORMAT {fmt})"
3937
3938    def _jsonobject_sql(
3939        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
3940    ) -> str:
3941        null_handling = expression.args.get("null_handling")
3942        null_handling = f" {null_handling}" if null_handling else ""
3943
3944        unique_keys = expression.args.get("unique_keys")
3945        if unique_keys is not None:
3946            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3947        else:
3948            unique_keys = ""
3949
3950        return_type = self.sql(expression, "return_type")
3951        return_type = f" RETURNING {return_type}" if return_type else ""
3952        encoding = self.sql(expression, "encoding")
3953        encoding = f" ENCODING {encoding}" if encoding else ""
3954
3955        if not name:
3956            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
3957
3958        return self.func(
3959            name,
3960            *expression.expressions,
3961            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3962        )
3963
3964    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3965        null_handling = expression.args.get("null_handling")
3966        null_handling = f" {null_handling}" if null_handling else ""
3967        return_type = self.sql(expression, "return_type")
3968        return_type = f" RETURNING {return_type}" if return_type else ""
3969        strict = " STRICT" if expression.args.get("strict") else ""
3970        return self.func(
3971            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3972        )
3973
3974    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3975        this = self.sql(expression, "this")
3976        order = self.sql(expression, "order")
3977        null_handling = expression.args.get("null_handling")
3978        null_handling = f" {null_handling}" if null_handling else ""
3979        return_type = self.sql(expression, "return_type")
3980        return_type = f" RETURNING {return_type}" if return_type else ""
3981        strict = " STRICT" if expression.args.get("strict") else ""
3982        return self.func(
3983            "JSON_ARRAYAGG",
3984            this,
3985            suffix=f"{order}{null_handling}{return_type}{strict})",
3986        )
3987
3988    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3989        path = self.sql(expression, "path")
3990        path = f" PATH {path}" if path else ""
3991        nested_schema = self.sql(expression, "nested_schema")
3992
3993        if nested_schema:
3994            return f"NESTED{path} {nested_schema}"
3995
3996        this = self.sql(expression, "this")
3997        kind = self.sql(expression, "kind")
3998        kind = f" {kind}" if kind else ""
3999        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
4000
4001        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
4002        return f"{this}{kind}{format_json}{path}{ordinality}"
4003
4004    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
4005        return self.func("COLUMNS", *expression.expressions)
4006
4007    def jsontable_sql(self, expression: exp.JSONTable) -> str:
4008        this = self.sql(expression, "this")
4009        path = self.sql(expression, "path")
4010        path = f", {path}" if path else ""
4011        error_handling = expression.args.get("error_handling")
4012        error_handling = f" {error_handling}" if error_handling else ""
4013        empty_handling = expression.args.get("empty_handling")
4014        empty_handling = f" {empty_handling}" if empty_handling else ""
4015        schema = self.sql(expression, "schema")
4016        return self.func(
4017            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
4018        )
4019
4020    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
4021        this = self.sql(expression, "this")
4022        kind = self.sql(expression, "kind")
4023        path = self.sql(expression, "path")
4024        path = f" {path}" if path else ""
4025        as_json = " AS JSON" if expression.args.get("as_json") else ""
4026        return f"{this} {kind}{path}{as_json}"
4027
4028    def openjson_sql(self, expression: exp.OpenJSON) -> str:
4029        this = self.sql(expression, "this")
4030        path = self.sql(expression, "path")
4031        path = f", {path}" if path else ""
4032        expressions = self.expressions(expression)
4033        with_ = (
4034            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
4035            if expressions
4036            else ""
4037        )
4038        return f"OPENJSON({this}{path}){with_}"
4039
4040    def in_sql(self, expression: exp.In) -> str:
4041        query = expression.args.get("query")
4042        unnest = expression.args.get("unnest")
4043        field = expression.args.get("field")
4044        is_global = " GLOBAL" if expression.args.get("is_global") else ""
4045
4046        if query:
4047            in_sql = self.sql(query)
4048        elif unnest:
4049            in_sql = self.in_unnest_op(unnest)
4050        elif field:
4051            in_sql = self.sql(field)
4052        else:
4053            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
4054
4055        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
4056
4057    def in_unnest_op(self, unnest: exp.Unnest) -> str:
4058        return f"(SELECT {self.sql(unnest)})"
4059
4060    def interval_sql(self, expression: exp.Interval) -> str:
4061        include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance(
4062            expression.find_ancestor(exp.AutoRefreshProperty, exp.Select),
4063            exp.AutoRefreshProperty,
4064        )
4065        interval_keyword = "INTERVAL" if include_keyword else ""
4066        unit_expression = expression.args.get("unit")
4067        unit = self.sql(unit_expression) if unit_expression else ""
4068        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
4069            unit = self.TIME_PART_SINGULARS.get(unit, unit)
4070        unit = f" {unit}" if unit else ""
4071
4072        if self.SINGLE_STRING_INTERVAL:
4073            this = expression.this.name if expression.this else ""
4074            if this:
4075                interval_keyword = f"{interval_keyword} " if interval_keyword else ""
4076                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
4077                    return f"{interval_keyword}'{this}'{unit}"
4078                return f"{interval_keyword}'{this}{unit}'"
4079            return f"{interval_keyword}{unit}"
4080
4081        this = self.sql(expression, "this")
4082        if this:
4083            if not include_keyword and expression.this.is_string:
4084                this = expression.this.name
4085            if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES):
4086                this = f"({this})"
4087            if include_keyword:
4088                this = f" {this}"
4089
4090        return f"{interval_keyword}{this}{unit}"
4091
4092    def return_sql(self, expression: exp.Return) -> str:
4093        return f"RETURN {self.sql(expression, 'this')}"
4094
4095    def reference_sql(self, expression: exp.Reference) -> str:
4096        this = self.sql(expression, "this")
4097        expressions = self.expressions(expression, flat=True)
4098        expressions = f"({expressions})" if expressions else ""
4099        options = self.expressions(expression, key="options", flat=True, sep=" ")
4100        options = f" {options}" if options else ""
4101        return f"REFERENCES {this}{expressions}{options}"
4102
4103    def anonymous_sql(self, expression: exp.Anonymous) -> str:
4104        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
4105        parent = expression.parent
4106        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
4107
4108        return self.func(
4109            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
4110        )
4111
4112    def paren_sql(self, expression: exp.Paren) -> str:
4113        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
4114        return f"({sql}{self.seg(')', sep='')}"
4115
4116    def neg_sql(self, expression: exp.Neg) -> str:
4117        # This makes sure we don't convert "- - 5" to "--5", which is a comment
4118        this_sql = self.sql(expression, "this")
4119        sep = " " if this_sql[0] == "-" else ""
4120        return f"-{sep}{this_sql}"
4121
4122    def not_sql(self, expression: exp.Not) -> str:
4123        return f"NOT {self.sql(expression, 'this')}"
4124
4125    def alias_sql(self, expression: exp.Alias) -> str:
4126        alias = self.sql(expression, "alias")
4127        alias = f" AS {alias}" if alias else ""
4128        return f"{self.sql(expression, 'this')}{alias}"
4129
4130    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
4131        alias = expression.args["alias"]
4132
4133        parent = expression.parent
4134        pivot = parent and parent.parent
4135
4136        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
4137            identifier_alias = isinstance(alias, exp.Identifier)
4138            literal_alias = isinstance(alias, exp.Literal)
4139
4140            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4141                alias.replace(exp.Literal.string(alias.output_name))
4142            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4143                alias.replace(exp.to_identifier(alias.output_name))
4144
4145        return self.alias_sql(expression)
4146
4147    def aliases_sql(self, expression: exp.Aliases) -> str:
4148        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
4149
4150    def atindex_sql(self, expression: exp.AtIndex) -> str:
4151        this = self.sql(expression, "this")
4152        index = self.sql(expression, "expression")
4153        return f"{this} AT {index}"
4154
4155    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
4156        this = self.sql(expression, "this")
4157        zone = self.sql(expression, "zone")
4158        return f"{this} AT TIME ZONE {zone}"
4159
4160    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
4161        this = self.sql(expression, "this")
4162        zone = self.sql(expression, "zone")
4163        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
4164
4165    def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str:
4166        return self.sql(exp.cast(expression.this, exp.DType.DATE))
4167
4168    def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:
4169        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4170
4171    def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str:
4172        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4173
4174    def add_sql(self, expression: exp.Add) -> str:
4175        return self.binary(expression, "+")
4176
4177    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
4178        return self.connector_sql(expression, "AND", stack)
4179
4180    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
4181        return self.connector_sql(expression, "OR", stack)
4182
4183    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
4184        return self.connector_sql(expression, "XOR", stack)
4185
4186    def connector_sql(
4187        self,
4188        expression: exp.Connector,
4189        op: str,
4190        stack: list[str | exp.Expr] | None = None,
4191    ) -> str:
4192        if stack is not None:
4193            stack.append(expression.right)
4194            if expression.comments and self.comments:
4195                op = self.maybe_comment(op, comments=expression.comments)
4196
4197            stack.extend((op, expression.left))
4198            return op
4199
4200        stack = [expression]
4201        sqls: list[str] = []
4202        ops = set()
4203
4204        while stack:
4205            node = stack.pop()
4206            if isinstance(node, exp.Connector):
4207                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4208            else:
4209                sql = self.sql(node)
4210                if sqls and sqls[-1] in ops:
4211                    sqls[-1] += f" {sql}"
4212                else:
4213                    sqls.append(sql)
4214
4215        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4216        return sep.join(sqls)
4217
4218    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4219        return self.binary(expression, "&")
4220
4221    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4222        return self.binary(expression, "<<")
4223
4224    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4225        return f"~{self.sql(expression, 'this')}"
4226
4227    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4228        return self.binary(expression, "|")
4229
4230    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4231        return self.binary(expression, ">>")
4232
4233    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4234        return self.binary(expression, "^")
4235
4236    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4237        format_sql = self.sql(expression, "format")
4238        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4239        to_sql = self.sql(expression, "to")
4240        to_sql = f" {to_sql}" if to_sql else ""
4241        action = self.sql(expression, "action")
4242        action = f" {action}" if action else ""
4243        default = self.sql(expression, "default")
4244        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4245        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4246
4247    # Base implementation that excludes safe, zone, and target_type metadata args
4248    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4249        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
4250
4251    # Base implementation that excludes the safe and default_year metadata args
4252    def strtodate_sql(self, expression: exp.StrToDate) -> str:
4253        return self.func("STR_TO_DATE", expression.this, expression.args.get("format"))
4254
4255    def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str:
4256        return self.func(
4257            "PARSE_DATETIME",
4258            expression.this,
4259            expression.args.get("format"),
4260            expression.args.get("zone"),
4261        )
4262
4263    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4264        zone = self.sql(expression, "this")
4265        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
4266
4267    def collate_sql(self, expression: exp.Collate) -> str:
4268        if self.COLLATE_IS_FUNC:
4269            return self.function_fallback_sql(expression)
4270        return self.binary(expression, "COLLATE")
4271
4272    def command_sql(self, expression: exp.Command) -> str:
4273        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
4274
4275    def comment_sql(self, expression: exp.Comment) -> str:
4276        this = self.sql(expression, "this")
4277        kind = expression.args["kind"]
4278        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4279        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4280        expression_sql = self.sql(expression, "expression")
4281        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4282
4283    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4284        this = self.sql(expression, "this")
4285        delete = " DELETE" if expression.args.get("delete") else ""
4286        recompress = self.sql(expression, "recompress")
4287        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4288        to_disk = self.sql(expression, "to_disk")
4289        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4290        to_volume = self.sql(expression, "to_volume")
4291        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4292        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4293
4294    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4295        where = self.sql(expression, "where")
4296        group = self.sql(expression, "group")
4297        aggregates = self.expressions(expression, key="aggregates")
4298        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4299
4300        if not (where or group or aggregates) and len(expression.expressions) == 1:
4301            return f"TTL {self.expressions(expression, flat=True)}"
4302
4303        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4304
4305    def transaction_sql(self, expression: exp.Transaction) -> str:
4306        modes = self.expressions(expression, key="modes")
4307        modes = f" {modes}" if modes else ""
4308        return f"BEGIN{modes}"
4309
4310    def commit_sql(self, expression: exp.Commit) -> str:
4311        chain = expression.args.get("chain")
4312        if chain is not None:
4313            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4314
4315        return f"COMMIT{chain or ''}"
4316
4317    def rollback_sql(self, expression: exp.Rollback) -> str:
4318        savepoint = expression.args.get("savepoint")
4319        savepoint = f" TO {savepoint}" if savepoint else ""
4320        return f"ROLLBACK{savepoint}"
4321
4322    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4323        this = self.sql(expression, "this")
4324
4325        exists = ""
4326        if expression.args.get("exists"):
4327            if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS:
4328                exists = " IF EXISTS"
4329            else:
4330                self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect")
4331
4332        dtype = self.sql(expression, "dtype")
4333        if dtype:
4334            collate = self.sql(expression, "collate")
4335            collate = f" COLLATE {collate}" if collate else ""
4336            using = self.sql(expression, "using")
4337            using = f" USING {using}" if using else ""
4338            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4339            null_constraint = self._alter_column_null_constraint_sql(expression)
4340
4341            return (
4342                f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}"
4343                f"{collate}{using}{null_constraint}"
4344            )
4345
4346        default = self.sql(expression, "default")
4347        if default:
4348            return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}"
4349
4350        comment = self.sql(expression, "comment")
4351        if comment:
4352            return f"ALTER COLUMN{exists} {this} COMMENT {comment}"
4353
4354        visible = expression.args.get("visible")
4355        if visible:
4356            return f"ALTER COLUMN{exists} {this} SET {visible}"
4357
4358        allow_null = expression.args.get("allow_null")
4359        drop = expression.args.get("drop")
4360
4361        if not drop and not allow_null:
4362            self.unsupported("Unsupported ALTER COLUMN syntax")
4363
4364        if allow_null is not None:
4365            keyword = "DROP" if drop else "SET"
4366            return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL"
4367
4368        return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4369
4370    def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str:
4371        allow_null = expression.args.get("allow_null")
4372        if allow_null is None:
4373            return ""
4374
4375        if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY:
4376            self.unsupported("ALTER COLUMN cannot set nullability along with a type")
4377            return ""
4378
4379        return " NULL" if allow_null else " NOT NULL"
4380
4381    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4382        this = self.sql(expression, "this")
4383        rename_from = self.sql(expression, "rename_from")
4384        if rename_from:
4385            if not self.SUPPORTS_CHANGE_COLUMN:
4386                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4387            return f"CHANGE COLUMN {rename_from} {this}"
4388        if not self.SUPPORTS_MODIFY_COLUMN:
4389            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4390        return f"MODIFY COLUMN {this}"
4391
4392    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4393        this = self.sql(expression, "this")
4394
4395        visible = expression.args.get("visible")
4396        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4397
4398        return f"ALTER INDEX {this} {visible_sql}"
4399
4400    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4401        this = self.sql(expression, "this")
4402        if not isinstance(expression.this, exp.Var):
4403            this = f"KEY DISTKEY {this}"
4404        return f"ALTER DISTSTYLE {this}"
4405
4406    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4407        compound = " COMPOUND" if expression.args.get("compound") else ""
4408        this = self.sql(expression, "this")
4409        expressions = self.expressions(expression, flat=True)
4410        expressions = f"({expressions})" if expressions else ""
4411        return f"ALTER{compound} SORTKEY {this or expressions}"
4412
4413    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4414        if not self.RENAME_TABLE_WITH_DB:
4415            # Remove db from tables
4416            expression = expression.transform(
4417                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4418            ).assert_is(exp.AlterRename)
4419        this = self.sql(expression, "this")
4420        to_kw = " TO" if include_to else ""
4421        return f"RENAME{to_kw} {this}"
4422
4423    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4424        exists = " IF EXISTS" if expression.args.get("exists") else ""
4425        old_column = self.sql(expression, "this")
4426        new_column = self.sql(expression, "to")
4427        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
4428
4429    def alterset_sql(self, expression: exp.AlterSet) -> str:
4430        exprs = self.expressions(expression, flat=True)
4431        if self.ALTER_SET_WRAPPED:
4432            exprs = f"({exprs})"
4433
4434        return f"SET {exprs}"
4435
4436    def alter_sql(self, expression: exp.Alter) -> str:
4437        actions = expression.actions
4438
4439        if (
4440            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
4441            and actions
4442            and isinstance(actions[0], exp.ColumnDef)
4443        ):
4444            actions_sql = self.expressions(expression, key="actions", flat=True)
4445            actions_sql = f"ADD {actions_sql}"
4446        else:
4447            actions_list = []
4448            for action in actions:
4449                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4450                    action_sql = self.add_column_sql(action)
4451                else:
4452                    action_sql = self.sql(action)
4453                    if isinstance(action, exp.Query):
4454                        action_sql = f"AS {action_sql}"
4455
4456                actions_list.append(action_sql)
4457
4458            actions_sql = self.format_args(*actions_list).lstrip("\n")
4459
4460        iceberg = (
4461            "ICEBERG "
4462            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4463            else ""
4464        )
4465        exists = " IF EXISTS" if expression.args.get("exists") else ""
4466        on_cluster = self.sql(expression, "cluster")
4467        on_cluster = f" {on_cluster}" if on_cluster else ""
4468        only = " ONLY" if expression.args.get("only") else ""
4469        options = expression.args.get("options") or []
4470        options_sql = self.expressions(
4471            sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)]
4472        )
4473        options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql
4474        partitions_sql = self.expressions(
4475            sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)],
4476            sep=" ",
4477            flat=True,
4478        )
4479        if partitions_sql and (actions_sql or options_sql):
4480            partitions_sql = f" {partitions_sql}"
4481        kind = self.sql(expression, "kind")
4482        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4483        check = " WITH CHECK" if expression.args.get("check") else ""
4484        cascade = (
4485            " CASCADE"
4486            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4487            else ""
4488        )
4489        this = self.sql(expression, "this")
4490        this = f" {this}" if this else ""
4491
4492        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}"
4493
4494    def altersession_sql(self, expression: exp.AlterSession) -> str:
4495        items_sql = self.expressions(expression, flat=True)
4496        keyword = "UNSET" if expression.args.get("unset") else "SET"
4497        return f"{keyword} {items_sql}"
4498
4499    def add_column_sql(self, expression: exp.Expr) -> str:
4500        sql = self.sql(expression)
4501        if isinstance(expression, exp.Schema):
4502            column_text = " COLUMNS"
4503        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4504            column_text = " COLUMN"
4505        else:
4506            column_text = ""
4507
4508        return f"ADD{column_text} {sql}"
4509
4510    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4511        expressions = self.expressions(expression)
4512        exists = " IF EXISTS " if expression.args.get("exists") else " "
4513        return f"DROP{exists}{expressions}"
4514
4515    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4516        return "DROP PRIMARY KEY"
4517
4518    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4519        return f"ADD {self.expressions(expression, indent=False)}"
4520
4521    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4522        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4523        location = self.sql(expression, "location")
4524        location = f" {location}" if location else ""
4525        return f"ADD {exists}{self.sql(expression.this)}{location}"
4526
4527    def distinct_sql(self, expression: exp.Distinct) -> str:
4528        this = self.expressions(expression, flat=True)
4529
4530        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4531            case = exp.case()
4532            for arg in expression.expressions:
4533                case = case.when(arg.is_(exp.null()), exp.null())
4534            this = self.sql(case.else_(f"({this})"))
4535
4536        this = f" {this}" if this else ""
4537
4538        on = self.sql(expression, "on")
4539        on = f" ON {on}" if on else ""
4540        return f"DISTINCT{this}{on}"
4541
4542    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4543        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4544
4545    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4546        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4547
4548    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4549        this_sql = self.sql(expression, "this")
4550        expression_sql = self.sql(expression, "expression")
4551        kind = "MAX" if expression.args.get("max") else "MIN"
4552        return f"{this_sql} HAVING {kind} {expression_sql}"
4553
4554    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4555        return self._truncated_div_sql(expression.this, expression.expression)
4556
4557    def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str:
4558        div: exp.Expr = exp.Div(this=this, expression=divisor)
4559        if self.CAST_TO_INT_ROUNDS:
4560            div = exp.Trunc(this=div)
4561
4562        return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT)))
4563
4564    def dpipe_sql(self, expression: exp.DPipe) -> str:
4565        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4566            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4567        return self.binary(expression, "||")
4568
4569    def div_sql(self, expression: exp.Div) -> str:
4570        l, r = expression.left, expression.right
4571
4572        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4573            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4574
4575        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4576            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4577                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4578
4579        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4580            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4581                # Native integer division depends on the target's operand types, which may differ
4582                # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are
4583                # integer literals or casts
4584                if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and (
4585                    expression.args.get("safe") or not self.SAFE_INT_DIVISION
4586                ):
4587                    return self.sql(exp.IntDiv(this=l, expression=r))
4588                return self._truncated_div_sql(l, r)
4589
4590        return self.binary(expression, "/")
4591
4592    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4593        n = exp._wrap(expression.this, exp.Binary)
4594        d = exp._wrap(expression.expression, exp.Binary)
4595        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4596
4597    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4598        return self.binary(expression, "OVERLAPS")
4599
4600    def distance_sql(self, expression: exp.Distance) -> str:
4601        return self.binary(expression, "<->")
4602
4603    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4604        return self.binary(expression, "<<->>")
4605
4606    def dot_sql(self, expression: exp.Dot) -> str:
4607        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4608
4609    def eq_sql(self, expression: exp.EQ) -> str:
4610        return self.binary(expression, "=")
4611
4612    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4613        return self.binary(expression, ":=")
4614
4615    def escape_sql(self, expression: exp.Escape) -> str:
4616        this = expression.this
4617        if (
4618            isinstance(this, (exp.Like, exp.ILike))
4619            and isinstance(this.expression, (exp.All, exp.Any))
4620            and not self.SUPPORTS_LIKE_QUANTIFIERS
4621        ):
4622            return self._like_sql(this, escape=expression)
4623        return self.binary(expression, "ESCAPE")
4624
4625    def glob_sql(self, expression: exp.Glob) -> str:
4626        return self.binary(expression, "GLOB")
4627
4628    def gt_sql(self, expression: exp.GT) -> str:
4629        return self.binary(expression, ">")
4630
4631    def gte_sql(self, expression: exp.GTE) -> str:
4632        return self.binary(expression, ">=")
4633
4634    def is_sql(self, expression: exp.Is) -> str:
4635        negate = expression.args.get("negate")
4636        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4637            positive = bool(expression.expression.this) != bool(negate)
4638            return self.sql(expression.this if positive else exp.not_(expression.this))
4639        return self.binary(expression, "IS NOT" if negate else "IS")
4640
4641    def _like_sql(
4642        self,
4643        expression: exp.Like | exp.ILike,
4644        escape: exp.Escape | None = None,
4645    ) -> str:
4646        this = expression.this
4647        rhs = expression.expression
4648
4649        if isinstance(expression, exp.Like):
4650            exp_class: type[exp.Like | exp.ILike] = exp.Like
4651            op = "LIKE"
4652        else:
4653            exp_class = exp.ILike
4654            op = "ILIKE"
4655
4656        if expression.args.get("negate"):
4657            op = f"NOT {op}"
4658
4659        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4660            exprs = rhs.this.unnest()
4661
4662            if isinstance(exprs, exp.Tuple):
4663                exprs = exprs.expressions
4664            else:
4665                exprs = [exprs]
4666
4667            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4668
4669            def _make_like(expr: exp.Expression) -> exp.Expression:
4670                like: exp.Expression = exp_class(
4671                    this=this, expression=expr, negate=expression.args.get("negate")
4672                )
4673                if escape:
4674                    like = exp.Escape(this=like, expression=escape.expression.copy())
4675                return like
4676
4677            like_expr: exp.Expr = _make_like(exprs[0])
4678            for expr in exprs[1:]:
4679                like_expr = connective(like_expr, _make_like(expr), copy=False)
4680
4681            parent = escape.parent if escape else expression.parent
4682            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4683                parent, exp.Condition
4684            ):
4685                like_expr = exp.paren(like_expr, copy=False)
4686
4687            return self.sql(like_expr)
4688
4689        return self.binary(expression, op)
4690
4691    def like_sql(self, expression: exp.Like) -> str:
4692        return self._like_sql(expression)
4693
4694    def ilike_sql(self, expression: exp.ILike) -> str:
4695        return self._like_sql(expression)
4696
4697    def match_sql(self, expression: exp.Match) -> str:
4698        if expression.args.get("kind") or expression.args.get("analyzer"):
4699            self.unsupported("MATCH variants and USING ANALYZER are not supported in this dialect")
4700        return self.binary(expression, "MATCH")
4701
4702    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4703        return self.binary(expression, "SIMILAR TO")
4704
4705    def lt_sql(self, expression: exp.LT) -> str:
4706        return self.binary(expression, "<")
4707
4708    def lte_sql(self, expression: exp.LTE) -> str:
4709        return self.binary(expression, "<=")
4710
4711    def mod_sql(self, expression: exp.Mod) -> str:
4712        this = self.sql(expression, "this")
4713        expr = self.sql(expression, "expression")
4714        sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}"
4715
4716        parent = expression.parent
4717        if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression:
4718            return f"({sql})"
4719
4720        return sql
4721
4722    def mul_sql(self, expression: exp.Mul) -> str:
4723        return self.binary(expression, "*")
4724
4725    def neq_sql(self, expression: exp.NEQ) -> str:
4726        return self.binary(expression, "<>")
4727
4728    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4729        return self.binary(expression, "IS NOT DISTINCT FROM")
4730
4731    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4732        return self.binary(expression, "IS DISTINCT FROM")
4733
4734    def sub_sql(self, expression: exp.Sub) -> str:
4735        return self.binary(expression, "-")
4736
4737    def trycast_sql(self, expression: exp.TryCast) -> str:
4738        return self.cast_sql(expression, safe_prefix="TRY_")
4739
4740    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4741        return self.cast_sql(expression)
4742
4743    def try_sql(self, expression: exp.Try) -> str:
4744        if not self.TRY_SUPPORTED:
4745            self.unsupported("Unsupported TRY function")
4746            return self.sql(expression, "this")
4747
4748        return self.func("TRY", expression.this)
4749
4750    def log_sql(self, expression: exp.Log) -> str:
4751        this = expression.this
4752        expr = expression.expression
4753
4754        if self.dialect.LOG_BASE_FIRST is False:
4755            this, expr = expr, this
4756        elif self.dialect.LOG_BASE_FIRST is None and expr:
4757            if this.name in ("2", "10"):
4758                return self.func(f"LOG{this.name}", expr)
4759
4760            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4761
4762        return self.func("LOG", this, expr)
4763
4764    def use_sql(self, expression: exp.Use) -> str:
4765        kind = self.sql(expression, "kind")
4766        kind = f" {kind}" if kind else ""
4767        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4768        this = f" {this}" if this else ""
4769        return f"USE{kind}{this}"
4770
4771    def binary(self, expression: exp.Binary, op: str) -> str:
4772        sqls: list[str] = []
4773        stack: list[None | str | exp.Expr] = [expression]
4774        binary_type = type(expression)
4775
4776        while stack:
4777            node = stack.pop()
4778
4779            if type(node) is binary_type:
4780                op_func = node.args.get("operator")
4781                if op_func:
4782                    op = f"OPERATOR({self.sql(op_func)})"
4783
4784                stack.append(node.args.get("expression"))
4785                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4786                stack.append(node.args.get("this"))
4787            else:
4788                sqls.append(self.sql(node))
4789
4790        return "".join(sqls)
4791
4792    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4793        to_clause = self.sql(expression, "to")
4794        if to_clause:
4795            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4796
4797        return self.function_fallback_sql(expression)
4798
4799    def function_fallback_sql(self, expression: exp.Func) -> str:
4800        args = []
4801
4802        for key in expression.arg_types:
4803            arg_value = expression.args.get(key)
4804
4805            if isinstance(arg_value, list):
4806                for value in arg_value:
4807                    args.append(value)
4808            elif arg_value is not None:
4809                args.append(arg_value)
4810
4811        return self.func(self.function_name(expression), *args)
4812
4813    def function_name(self, expression: exp.Func) -> str:
4814        name_key = self.dialect.ORIGINAL_NAME_META_KEY
4815        return (expression.meta_get(name_key) if name_key else None) or expression.sql_name()
4816
4817    def func(
4818        self,
4819        name: str,
4820        *args: t.Any,
4821        prefix: str = "(",
4822        suffix: str = ")",
4823        normalize: bool = True,
4824    ) -> str:
4825        name = self.normalize_func(name) if normalize else name
4826        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4827
4828    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4829        arg_sqls = tuple(
4830            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4831        )
4832        if self.pretty and self.too_wide(arg_sqls):
4833            return self.indent(
4834                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4835            )
4836        return sep.join(arg_sqls)
4837
4838    def too_wide(self, args: t.Iterable) -> bool:
4839        return sum(len(arg) for arg in args) > self.max_text_width
4840
4841    def format_time(
4842        self,
4843        expression: exp.Expr,
4844        inverse_time_mapping: dict[str, str] | None = None,
4845        inverse_time_trie: dict | None = None,
4846    ) -> str | None:
4847        return format_time(
4848            self.sql(expression, "format"),
4849            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4850            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4851        )
4852
4853    def expressions(
4854        self,
4855        expression: exp.Expr | None = None,
4856        key: str | None = None,
4857        sqls: t.Collection[str | exp.Expr] | None = None,
4858        flat: bool = False,
4859        indent: bool = True,
4860        skip_first: bool = False,
4861        skip_last: bool = False,
4862        sep: str = ", ",
4863        prefix: str = "",
4864        dynamic: bool = False,
4865        new_line: bool = False,
4866    ) -> str:
4867        expressions = expression.args.get(key or "expressions") if expression else sqls
4868
4869        if not expressions:
4870            return ""
4871
4872        if flat:
4873            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4874
4875        num_sqls = len(expressions)
4876        result_sqls = []
4877
4878        for i, e in enumerate(expressions):
4879            sql = self.sql(e, comment=False)
4880            if not sql:
4881                continue
4882
4883            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4884
4885            if self.pretty:
4886                if self.leading_comma:
4887                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4888                else:
4889                    result_sqls.append(
4890                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4891                    )
4892            else:
4893                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4894
4895        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4896            if new_line:
4897                result_sqls.insert(0, "")
4898                result_sqls.append("")
4899            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4900        else:
4901            result_sql = "".join(result_sqls)
4902
4903        return (
4904            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4905            if indent
4906            else result_sql
4907        )
4908
4909    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4910        flat = flat or isinstance(expression.parent, exp.Properties)
4911        expressions_sql = self.expressions(expression, flat=flat)
4912        if flat:
4913            return f"{op} {expressions_sql}"
4914        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4915
4916    def naked_property(self, expression: exp.Property) -> str:
4917        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4918        if not property_name:
4919            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4920        return f"{property_name} {self.sql(expression, 'this')}"
4921
4922    def tag_sql(self, expression: exp.Tag) -> str:
4923        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4924
4925    def token_sql(self, token_type: TokenType) -> str:
4926        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4927
4928    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4929        this = self.sql(expression, "this")
4930        expressions = self.no_identify(self.expressions, expression)
4931        expressions = (
4932            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4933        )
4934        return f"{this}{expressions}" if expressions.strip() != "" else this
4935
4936    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
4937        return self.expressions(expression, flat=True)
4938
4939    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
4940        params = self.no_identify(self.expressions, expression, flat=True)
4941        body = self.sql(expression, "this")
4942        prefix = "TABLE " if expression.args.get("is_table") else ""
4943        return f"({params}) AS {prefix}{body}"
4944
4945    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4946        this = self.sql(expression, "this")
4947        expressions = self.expressions(expression, flat=True)
4948        return f"{this}({expressions})"
4949
4950    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4951        return self.binary(expression, "=>")
4952
4953    def when_sql(self, expression: exp.When) -> str:
4954        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4955        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4956        condition = self.sql(expression, "condition")
4957        condition = f" AND {condition}" if condition else ""
4958
4959        then_expression = expression.args.get("then")
4960        if isinstance(then_expression, exp.Insert):
4961            this = self.sql(then_expression, "this")
4962            this = f"INSERT {this}" if this else "INSERT"
4963            then = self.sql(then_expression, "expression")
4964            then = f"{this} VALUES {then}" if then else this
4965        elif isinstance(then_expression, exp.Update):
4966            if isinstance(then_expression.args.get("expressions"), exp.Star):
4967                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4968            else:
4969                expressions_sql = self.expressions(then_expression)
4970                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4971        else:
4972            then = self.sql(then_expression)
4973
4974        if isinstance(then_expression, (exp.Insert, exp.Update)):
4975            where = self.sql(then_expression, "where")
4976            if where and not self.SUPPORTS_MERGE_WHERE:
4977                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4978                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4979                where = ""
4980            then = f"{then}{where}"
4981        return f"WHEN {matched}{source}{condition} THEN {then}"
4982
4983    def whens_sql(self, expression: exp.Whens) -> str:
4984        return self.expressions(expression, sep=" ", indent=False)
4985
4986    def merge_sql(self, expression: exp.Merge) -> str:
4987        table = expression.this
4988        table_alias = ""
4989
4990        hints = table.args.get("hints")
4991        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4992            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4993            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4994
4995        this = self.sql(table)
4996        using = f"USING {self.sql(expression, 'using')}"
4997        whens = self.sql(expression, "whens")
4998
4999        on = self.sql(expression, "on")
5000        on = f"ON {on}" if on else ""
5001
5002        if not on:
5003            on = self.expressions(expression, key="using_cond")
5004            on = f"USING ({on})" if on else ""
5005
5006        returning = self.sql(expression, "returning")
5007        if returning:
5008            whens = f"{whens}{returning}"
5009
5010        sep = self.sep()
5011
5012        return self.prepend_ctes(
5013            expression,
5014            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
5015        )
5016
5017    @unsupported_args("format")
5018    def tochar_sql(self, expression: exp.ToChar) -> str:
5019        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
5020
5021    @unsupported_args("default")
5022    def tonumber_sql(self, expression: exp.ToNumber) -> str:
5023        if not self.SUPPORTS_TO_NUMBER:
5024            self.unsupported("Unsupported TO_NUMBER function")
5025            return self._tonumber_cast_sql(expression)
5026
5027        fmt = expression.args.get("format")
5028        if not fmt:
5029            self.unsupported("Conversion format is required for TO_NUMBER")
5030            return self._tonumber_cast_sql(expression)
5031
5032        return self.func("TO_NUMBER", expression.this, fmt)
5033
5034    def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str:
5035        if expression.args.get("safe"):
5036            return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE")))
5037        return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
5038
5039    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
5040        this = self.sql(expression, "this")
5041        kind = self.sql(expression, "kind")
5042        settings_sql = self.expressions(expression, key="settings", sep=" ")
5043        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
5044        return f"{this}({kind}{args})"
5045
5046    def dictrange_sql(self, expression: exp.DictRange) -> str:
5047        this = self.sql(expression, "this")
5048        max = self.sql(expression, "max")
5049        min = self.sql(expression, "min")
5050        return f"{this}(MIN {min} MAX {max})"
5051
5052    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
5053        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
5054
5055    def aggregatekeyproperty_sql(self, expression: exp.AggregateKeyProperty) -> str:
5056        return f"AGGREGATE KEY ({self.expressions(expression, flat=True)})"
5057
5058    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
5059        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
5060
5061    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
5062    def uniquekeyproperty_sql(
5063        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
5064    ) -> str:
5065        return f"{prefix} ({self.expressions(expression, flat=True)})"
5066
5067    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
5068    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
5069        expressions = self.expressions(expression, flat=True)
5070        expressions = f" {self.wrap(expressions)}" if expressions else ""
5071        buckets = self.sql(expression, "buckets")
5072        kind = self.sql(expression, "kind")
5073        buckets = f" BUCKETS {buckets}" if buckets else ""
5074        order = self.sql(expression, "order")
5075        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
5076
5077    def oncluster_sql(self, expression: exp.OnCluster) -> str:
5078        return ""
5079
5080    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
5081        expressions = self.expressions(expression, key="expressions", flat=True)
5082        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
5083        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
5084        buckets = self.sql(expression, "buckets")
5085        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
5086
5087    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
5088        this = self.sql(expression, "this")
5089        having = self.sql(expression, "having")
5090
5091        if having:
5092            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
5093
5094        return self.func("ANY_VALUE", this)
5095
5096    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
5097        transform = self.func("TRANSFORM", *expression.expressions)
5098        row_format_before = self.sql(expression, "row_format_before")
5099        row_format_before = f" {row_format_before}" if row_format_before else ""
5100        record_writer = self.sql(expression, "record_writer")
5101        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
5102        using = f" USING {self.sql(expression, 'command_script')}"
5103        schema = self.sql(expression, "schema")
5104        schema = f" AS {schema}" if schema else ""
5105        row_format_after = self.sql(expression, "row_format_after")
5106        row_format_after = f" {row_format_after}" if row_format_after else ""
5107        record_reader = self.sql(expression, "record_reader")
5108        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
5109        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
5110
5111    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
5112        key_block_size = self.sql(expression, "key_block_size")
5113        if key_block_size:
5114            return f"KEY_BLOCK_SIZE = {key_block_size}"
5115
5116        using = self.sql(expression, "using")
5117        if using:
5118            return f"USING {using}"
5119
5120        parser = self.sql(expression, "parser")
5121        if parser:
5122            return f"WITH PARSER {parser}"
5123
5124        comment = self.sql(expression, "comment")
5125        if comment:
5126            return f"COMMENT {comment}"
5127
5128        visible = expression.args.get("visible")
5129        if visible is not None:
5130            return "VISIBLE" if visible else "INVISIBLE"
5131
5132        engine_attr = self.sql(expression, "engine_attr")
5133        if engine_attr:
5134            return f"ENGINE_ATTRIBUTE = {engine_attr}"
5135
5136        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
5137        if secondary_engine_attr:
5138            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
5139
5140        self.unsupported("Unsupported index constraint option.")
5141        return ""
5142
5143    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
5144        enforced = " ENFORCED" if expression.args.get("enforced") else ""
5145        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
5146
5147    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
5148        kind = self.sql(expression, "kind")
5149        kind = f"{kind} INDEX" if kind else "INDEX"
5150        this = self.sql(expression, "this")
5151        this = f" {this}" if this else ""
5152        index_type = self.sql(expression, "index_type")
5153        index_type = f" USING {index_type}" if index_type else ""
5154        expressions = self.expressions(expression, flat=True)
5155        expressions = f" ({expressions})" if expressions else ""
5156        options = self.expressions(expression, key="options", sep=" ")
5157        options = f" {options}" if options else ""
5158        return f"{kind}{this}{index_type}{expressions}{options}"
5159
5160    def nvl2_sql(self, expression: exp.Nvl2) -> str:
5161        if self.NVL2_SUPPORTED:
5162            return self.function_fallback_sql(expression)
5163
5164        case = exp.Case().when(
5165            expression.this.is_(exp.null()).not_(copy=False),
5166            expression.args["true"],
5167            copy=False,
5168        )
5169        else_cond = expression.args.get("false")
5170        if else_cond:
5171            case.else_(else_cond, copy=False)
5172
5173        return self.sql(case)
5174
5175    def nthvalue_sql(self, expression: exp.NthValue) -> str:
5176        if expression.args.get("from_first") is False:
5177            self.unsupported("NTH_VALUE FROM LAST is not supported")
5178
5179        return self.function_fallback_sql(expression)
5180
5181    def comprehension_sql(self, expression: exp.Comprehension) -> str:
5182        this = self.sql(expression, "this")
5183        expr = self.sql(expression, "expression")
5184        position = self.sql(expression, "position")
5185        position = f", {position}" if position else ""
5186        iterator = self.sql(expression, "iterator")
5187        condition = self.sql(expression, "condition")
5188        condition = f" IF {condition}" if condition else ""
5189        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
5190
5191    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
5192        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
5193
5194    def opclass_sql(self, expression: exp.Opclass) -> str:
5195        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
5196
5197    def _ml_sql(self, expression: exp.Func, name: str) -> str:
5198        model = self.sql(expression, "this")
5199        model = f"MODEL {model}"
5200        expr = expression.expression
5201        if expr:
5202            expr_sql = self.sql(expression, "expression")
5203            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
5204        else:
5205            expr_sql = None
5206
5207        parameters = self.sql(expression, "params_struct") or None
5208
5209        return self.func(name, model, expr_sql, parameters)
5210
5211    def predict_sql(self, expression: exp.Predict) -> str:
5212        return self._ml_sql(expression, "PREDICT")
5213
5214    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
5215        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
5216        return self._ml_sql(expression, name)
5217
5218    def generatetext_sql(self, expression: exp.GenerateText) -> str:
5219        return self._ml_sql(expression, "GENERATE_TEXT")
5220
5221    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
5222        return self._ml_sql(expression, "GENERATE_TABLE")
5223
5224    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
5225        return self._ml_sql(expression, "GENERATE_BOOL")
5226
5227    def generateint_sql(self, expression: exp.GenerateInt) -> str:
5228        return self._ml_sql(expression, "GENERATE_INT")
5229
5230    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
5231        return self._ml_sql(expression, "GENERATE_DOUBLE")
5232
5233    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
5234        return self._ml_sql(expression, "TRANSLATE")
5235
5236    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
5237        return self._ml_sql(expression, "FORECAST")
5238
5239    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
5240        this_sql = self.sql(expression, "this")
5241        if isinstance(expression.this, exp.Table):
5242            this_sql = f"TABLE {this_sql}"
5243
5244        return self.func(
5245            "FORECAST",
5246            this_sql,
5247            expression.args.get("data_col"),
5248            expression.args.get("timestamp_col"),
5249            expression.args.get("model"),
5250            expression.args.get("id_cols"),
5251            expression.args.get("horizon"),
5252            expression.args.get("forecast_end_timestamp"),
5253            expression.args.get("confidence_level"),
5254            expression.args.get("output_historical_time_series"),
5255            expression.args.get("context_window"),
5256        )
5257
5258    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
5259        this_sql = self.sql(expression, "this")
5260        if isinstance(expression.this, exp.Table):
5261            this_sql = f"TABLE {this_sql}"
5262
5263        return self.func(
5264            "FEATURES_AT_TIME",
5265            this_sql,
5266            expression.args.get("time"),
5267            expression.args.get("num_rows"),
5268            expression.args.get("ignore_feature_nulls"),
5269        )
5270
5271    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5272        this_sql = self.sql(expression, "this")
5273        if isinstance(expression.this, exp.Table):
5274            this_sql = f"TABLE {this_sql}"
5275
5276        query_table = self.sql(expression, "query_table")
5277        if isinstance(expression.args["query_table"], exp.Table):
5278            query_table = f"TABLE {query_table}"
5279
5280        return self.func(
5281            "VECTOR_SEARCH",
5282            this_sql,
5283            expression.args.get("column_to_search"),
5284            query_table,
5285            expression.args.get("query_column_to_search"),
5286            expression.args.get("top_k"),
5287            expression.args.get("distance_type"),
5288            expression.args.get("options"),
5289        )
5290
5291    def forin_sql(self, expression: exp.ForIn) -> str:
5292        this = self.sql(expression, "this")
5293        expression_sql = self.sql(expression, "expression")
5294        return f"FOR {this} DO {expression_sql}"
5295
5296    @unsupported_args("partition_start", "partition_end", "force", "mode")
5297    def refresh_sql(self, expression: exp.Refresh) -> str:
5298        this = self.sql(expression, "this")
5299        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5300        return f"REFRESH {kind}{this}"
5301
5302    def toarray_sql(self, expression: exp.ToArray) -> str:
5303        arg = expression.this
5304        if not arg.type:
5305            import sqlglot.optimizer.annotate_types
5306
5307            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5308
5309        if arg.is_type(exp.DType.ARRAY):
5310            return self.sql(arg)
5311
5312        cond_for_null = arg.is_(exp.null())
5313        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5314
5315    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5316        this = expression.this
5317        time_format = self.format_time(expression)
5318
5319        if time_format:
5320            return self.sql(
5321                exp.cast(
5322                    exp.StrToTime(this=this, format=expression.args["format"]),
5323                    exp.DType.TIME,
5324                )
5325            )
5326
5327        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5328            return self.sql(this)
5329
5330        return self.sql(exp.cast(this, exp.DType.TIME))
5331
5332    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5333        this = expression.this
5334        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5335            return self.sql(this)
5336
5337        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5338
5339    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5340        this = expression.this
5341        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5342            return self.sql(this)
5343
5344        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5345
5346    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5347        this = expression.this
5348        time_format = self.format_time(expression)
5349        safe = expression.args.get("safe")
5350        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5351            return self.sql(
5352                exp.cast(
5353                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5354                    exp.DType.DATE,
5355                )
5356            )
5357
5358        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5359            return self.sql(this)
5360
5361        if safe:
5362            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5363
5364        return self.sql(exp.cast(this, exp.DType.DATE))
5365
5366    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5367        return self.sql(
5368            exp.func(
5369                "DATEDIFF",
5370                expression.this,
5371                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5372                "day",
5373            )
5374        )
5375
5376    def lastday_sql(self, expression: exp.LastDay) -> str:
5377        if self.LAST_DAY_SUPPORTS_DATE_PART:
5378            return self.function_fallback_sql(expression)
5379
5380        unit = expression.args.get("unit")
5381        if unit and unit.name.upper() != "MONTH":
5382            self.unsupported("Date parts are not supported in LAST_DAY.")
5383
5384        return self.func("LAST_DAY", expression.this)
5385
5386    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5387        import sqlglot.dialects.dialect
5388
5389        return self.func(
5390            "DATE_ADD",
5391            expression.this,
5392            expression.expression,
5393            sqlglot.dialects.dialect.unit_to_str(expression),
5394        )
5395
5396    def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str:
5397        this = expression.this
5398        position = expression.args["position"]
5399        offset = index_offset - (expression.args.get("offset") or 0)
5400
5401        if offset:
5402            if position.is_int:
5403                value = position.to_py()
5404                if value >= 0:
5405                    position = exp.Literal.number(value + offset)
5406                elif offset < 0 and value == -1:
5407                    # 1-based -1 appends, which a 0-based position can only express as the size
5408                    position = exp.ArraySize(this=this.copy())
5409                else:
5410                    # Negative positions count from the end, so they shift in the opposite
5411                    # direction, e.g. 0-based -1 (before the last element) is 1-based -2
5412                    position = exp.Literal.number(value - offset)
5413            else:
5414                self.unsupported("ARRAY_INSERT position can only be converted if it's a literal")
5415
5416        return self.func("ARRAY_INSERT", this, position, expression.expression)
5417
5418    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5419        if self.CAN_IMPLEMENT_ARRAY_ANY:
5420            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5421            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5422            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5423            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5424
5425        import sqlglot.dialects.dialect
5426
5427        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5428        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5429            self.unsupported("ARRAY_ANY is unsupported")
5430
5431        return self.function_fallback_sql(expression)
5432
5433    def struct_sql(self, expression: exp.Struct) -> str:
5434        expression.set(
5435            "expressions",
5436            [
5437                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5438                if isinstance(e, exp.PropertyEQ)
5439                else e
5440                for e in expression.expressions
5441            ],
5442        )
5443
5444        return self.function_fallback_sql(expression)
5445
5446    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5447        low = self.sql(expression, "this")
5448        high = self.sql(expression, "expression")
5449
5450        return f"{low} TO {high}"
5451
5452    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5453        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5454        tables = f" {self.expressions(expression)}"
5455
5456        exists = " IF EXISTS" if expression.args.get("exists") else ""
5457
5458        on_cluster = self.sql(expression, "cluster")
5459        on_cluster = f" {on_cluster}" if on_cluster else ""
5460
5461        identity = self.sql(expression, "identity")
5462        identity = f" {identity} IDENTITY" if identity else ""
5463
5464        option = self.sql(expression, "option")
5465        option = f" {option}" if option else ""
5466
5467        partition = self.sql(expression, "partition")
5468        partition = f" {partition}" if partition else ""
5469
5470        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5471
5472    # This transpiles T-SQL's CONVERT function
5473    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
5474    def convert_sql(self, expression: exp.Convert) -> str:
5475        to = expression.this
5476        value = expression.expression
5477        style = expression.args.get("style")
5478        safe = expression.args.get("safe")
5479        strict = expression.args.get("strict")
5480
5481        if not to or not value:
5482            return ""
5483
5484        # Retrieve length of datatype and override to default if not specified
5485        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5486            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5487
5488        transformed: exp.Expr | None = None
5489        cast = exp.Cast if strict else exp.TryCast
5490
5491        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5492        if isinstance(style, exp.Literal) and style.is_int:
5493            import sqlglot.dialects.tsql
5494
5495            style_value = style.name
5496            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5497            if not converted_style:
5498                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5499
5500            fmt = exp.Literal.string(converted_style)
5501
5502            if to.this == exp.DType.DATE:
5503                transformed = exp.StrToDate(this=value, format=fmt)
5504            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5505                transformed = exp.StrToTime(this=value, format=fmt)
5506            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5507                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5508            elif to.this == exp.DType.TEXT:
5509                transformed = exp.TimeToStr(this=value, format=fmt)
5510
5511        if not transformed:
5512            transformed = cast(this=value, to=to, safe=safe)
5513
5514        return self.sql(transformed)
5515
5516    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
5517        this = expression.this
5518        if isinstance(this, exp.JSONPathWildcard):
5519            this = self.json_path_part(this)
5520            return f".{this}" if this else ""
5521
5522        quoted = expression.args.get("quoted")
5523        if not (
5524            quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
5525        ) and self.SAFE_JSON_PATH_KEY_RE.match(this):
5526            return f".{this}"
5527
5528        this = self.json_path_part(this)
5529
5530        return (
5531            f"[{this}]"
5532            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5533            else f".{this}"
5534        )
5535
5536    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5537        this = self.json_path_part(expression.this)
5538        return f"[{this}]" if this else ""
5539
5540    def _simplify_unless_literal(self, expression: E) -> E:
5541        if not isinstance(expression, exp.Literal):
5542            import sqlglot.optimizer.simplify
5543
5544            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5545
5546        return expression
5547
5548    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5549        this = expression.this
5550        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5551            self.unsupported(
5552                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5553            )
5554            return self.sql(this)
5555
5556        if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5557            if self.IGNORE_NULLS_BEFORE_ORDER:
5558                from sqlglot.optimizer.scope import find_all_in_scope
5559
5560                # The first modifier here will be the one closest to the AggFunc's arg
5561                mods = sorted(
5562                    find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit),
5563                    key=lambda x: (
5564                        0
5565                        if isinstance(x, exp.HavingMax)
5566                        else (1 if isinstance(x, exp.Order) else 2)
5567                    ),
5568                )
5569
5570                if mods:
5571                    mod = mods[0]
5572                    this = expression.__class__(this=mod.this.copy())
5573                    this.meta["inline"] = True
5574                    mod.this.replace(this)
5575                    return self.sql(expression.this)
5576
5577            agg_func = expression.find(exp.AggFunc)
5578
5579            if agg_func:
5580                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5581                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5582
5583        return f"{self.sql(expression, 'this')} {text}"
5584
5585    def _replace_line_breaks(self, string: str) -> str:
5586        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5587        if self.pretty:
5588            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5589        return string
5590
5591    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5592        option = self.sql(expression, "this")
5593
5594        if expression.expressions:
5595            upper = option.upper()
5596
5597            # Snowflake FILE_FORMAT options are separated by whitespace
5598            sep = " " if upper == "FILE_FORMAT" else ", "
5599
5600            # Databricks copy/format options do not set their list of values with EQ
5601            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5602            values = self.expressions(expression, flat=True, sep=sep)
5603            return f"{option}{op}({values})"
5604
5605        value = self.sql(expression, "expression")
5606
5607        if not value:
5608            return option
5609
5610        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5611
5612        return f"{option}{op}{value}"
5613
5614    def credentials_sql(self, expression: exp.Credentials) -> str:
5615        cred_expr = expression.args.get("credentials")
5616        if isinstance(cred_expr, exp.Literal):
5617            # Redshift case: CREDENTIALS <string>
5618            credentials = self.sql(expression, "credentials")
5619            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5620        else:
5621            # Snowflake case: CREDENTIALS = (...)
5622            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5623            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5624
5625        storage = self.sql(expression, "storage")
5626        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5627
5628        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5629        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5630
5631        iam_role = self.sql(expression, "iam_role")
5632        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5633
5634        region = self.sql(expression, "region")
5635        region = f" REGION {region}" if region else ""
5636
5637        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5638
5639    def copy_sql(self, expression: exp.Copy) -> str:
5640        this = self.sql(expression, "this")
5641        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5642
5643        credentials = self.sql(expression, "credentials")
5644        credentials = self.seg(credentials) if credentials else ""
5645        files = self.expressions(expression, key="files", flat=True)
5646        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5647
5648        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5649        params = self.expressions(
5650            expression,
5651            key="params",
5652            sep=sep,
5653            new_line=True,
5654            skip_last=True,
5655            skip_first=True,
5656            indent=self.COPY_PARAMS_ARE_WRAPPED,
5657        )
5658
5659        if params:
5660            if self.COPY_PARAMS_ARE_WRAPPED:
5661                params = f" WITH ({params})"
5662            elif not self.pretty and (files or credentials):
5663                params = f" {params}"
5664
5665        return f"COPY{this}{kind} {files}{credentials}{params}"
5666
5667    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5668        return ""
5669
5670    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5671        on_sql = "ON" if expression.args.get("on") else "OFF"
5672        filter_col: str | None = self.sql(expression, "filter_column")
5673        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5674        retention_period: str | None = self.sql(expression, "retention_period")
5675        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5676
5677        if filter_col or retention_period:
5678            on_sql = self.func("ON", filter_col, retention_period)
5679
5680        return f"DATA_DELETION={on_sql}"
5681
5682    def maskingpolicycolumnconstraint_sql(
5683        self, expression: exp.MaskingPolicyColumnConstraint
5684    ) -> str:
5685        this = self.sql(expression, "this")
5686        expressions = self.expressions(expression, flat=True)
5687        expressions = f" USING ({expressions})" if expressions else ""
5688        return f"MASKING POLICY {this}{expressions}"
5689
5690    def gapfill_sql(self, expression: exp.GapFill) -> str:
5691        this = self.sql(expression, "this")
5692        this = f"TABLE {this}"
5693        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5694
5695    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5696        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5697
5698    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5699        this = self.sql(expression, "this")
5700        expr = expression.expression
5701
5702        if isinstance(expr, exp.Func):
5703            # T-SQL's CLR functions are case sensitive
5704            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5705        else:
5706            expr = self.sql(expression, "expression")
5707
5708        return self.scope_resolution(expr, this)
5709
5710    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5711        if self.PARSE_JSON_NAME is None:
5712            return self.sql(expression.this)
5713
5714        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5715
5716    def rand_sql(self, expression: exp.Rand) -> str:
5717        lower = self.sql(expression, "lower")
5718        upper = self.sql(expression, "upper")
5719
5720        if lower and upper:
5721            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5722        return self.func("RAND", expression.this)
5723
5724    def changes_sql(self, expression: exp.Changes) -> str:
5725        information = self.sql(expression, "information")
5726        information = f"INFORMATION => {information}"
5727        at_before = self.sql(expression, "at_before")
5728        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5729        end = self.sql(expression, "end")
5730        end = f"{self.seg('')}{end}" if end else ""
5731
5732        return f"CHANGES ({information}){at_before}{end}"
5733
5734    def pad_sql(self, expression: exp.Pad) -> str:
5735        prefix = "L" if expression.args.get("is_left") else "R"
5736
5737        fill_pattern = self.sql(expression, "fill_pattern") or None
5738        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5739            fill_pattern = "' '"
5740
5741        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5742
5743    def summarize_sql(self, expression: exp.Summarize) -> str:
5744        table = " TABLE" if expression.args.get("table") else ""
5745        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5746
5747    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5748        generate_series = exp.GenerateSeries(**expression.args)
5749
5750        parent = expression.parent
5751        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5752            parent = parent.parent
5753
5754        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5755            return self.sql(exp.Unnest(expressions=[generate_series]))
5756
5757        if isinstance(parent, exp.Select):
5758            self.unsupported("GenerateSeries projection unnesting is not supported.")
5759
5760        return self.sql(generate_series)
5761
5762    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5763        if self.SUPPORTS_CONVERT_TIMEZONE:
5764            return self.function_fallback_sql(expression)
5765
5766        source_tz = expression.args.get("source_tz")
5767        target_tz = expression.args.get("target_tz")
5768        timestamp = expression.args.get("timestamp")
5769
5770        if source_tz and timestamp:
5771            timestamp = exp.AtTimeZone(
5772                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5773            )
5774
5775        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5776
5777        return self.sql(expr)
5778
5779    def json_sql(self, expression: exp.JSON) -> str:
5780        this = self.sql(expression, "this")
5781        this = f" {this}" if this else ""
5782
5783        _with = expression.args.get("with_")
5784
5785        if _with is None:
5786            with_sql = ""
5787        elif not _with:
5788            with_sql = " WITHOUT"
5789        else:
5790            with_sql = " WITH"
5791
5792        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5793
5794        return f"JSON{this}{with_sql}{unique_sql}"
5795
5796    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5797        path = self.sql(expression, "path")
5798        returning = self.sql(expression, "returning")
5799        returning = f" RETURNING {returning}" if returning else ""
5800
5801        on_condition = self.sql(expression, "on_condition")
5802        on_condition = f" {on_condition}" if on_condition else ""
5803
5804        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5805
5806    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5807        regexp = " REGEXP" if expression.args.get("regexp") else ""
5808        return f"SKIP{regexp} {self.sql(expression.expression)}"
5809
5810    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5811        else_ = "ELSE " if expression.args.get("else_") else ""
5812        condition = self.sql(expression, "expression")
5813        condition = f"WHEN {condition} THEN " if condition else else_
5814        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5815        return f"{condition}{insert}"
5816
5817    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5818        kind = self.sql(expression, "kind")
5819        source = self.sql(expression, "source")
5820
5821        if kind:
5822            expressions = self.seg(self.expressions(expression, sep=" "))
5823            sql = f"INSERT {kind}{expressions}{self.seg(source)}"
5824        else:
5825            # If kind isn't present, we have Hive's FROM-first form:
5826            # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...]
5827            inserts = self.sep().join(self.sql(e) for e in expression.expressions)
5828            sql = f"FROM {source}{self.seg(inserts)}"
5829
5830        return self.prepend_ctes(expression, sql)
5831
5832    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5833        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5834        empty = expression.args.get("empty")
5835        empty = (
5836            f"DEFAULT {empty} ON EMPTY"
5837            if isinstance(empty, exp.Expr)
5838            else self.sql(expression, "empty")
5839        )
5840
5841        error = expression.args.get("error")
5842        error = (
5843            f"DEFAULT {error} ON ERROR"
5844            if isinstance(error, exp.Expr)
5845            else self.sql(expression, "error")
5846        )
5847
5848        if error and empty:
5849            error = (
5850                f"{empty} {error}"
5851                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5852                else f"{error} {empty}"
5853            )
5854            empty = ""
5855
5856        null = self.sql(expression, "null")
5857
5858        return f"{empty}{error}{null}"
5859
5860    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5861        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5862        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5863
5864    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5865        this = self.sql(expression, "this")
5866        path = self.sql(expression, "path")
5867
5868        passing = self.expressions(expression, "passing")
5869        passing = f" PASSING {passing}" if passing else ""
5870
5871        on_condition = self.sql(expression, "on_condition")
5872        on_condition = f" {on_condition}" if on_condition else ""
5873
5874        path = f"{path}{passing}{on_condition}"
5875
5876        return self.func("JSON_EXISTS", this, path)
5877
5878    def _add_arrayagg_null_filter(
5879        self,
5880        array_agg_sql: str,
5881        array_agg_expr: exp.ArrayAgg,
5882        column_expr: exp.Expr,
5883    ) -> str:
5884        """
5885        Add NULL filter to ARRAY_AGG if dialect requires it.
5886
5887        Args:
5888            array_agg_sql: The generated ARRAY_AGG SQL string
5889            array_agg_expr: The ArrayAgg expression node
5890            column_expr: The column/expression to filter (before ORDER BY wrapping)
5891
5892        Returns:
5893            SQL string with FILTER clause added if needed
5894        """
5895        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5896        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5897        if not (
5898            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5899        ):
5900            return array_agg_sql
5901
5902        parent = array_agg_expr.parent
5903        if isinstance(parent, exp.Filter):
5904            parent_cond = parent.expression.this
5905            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5906        elif column_expr.find(exp.Column):
5907            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5908            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5909            this_sql = (
5910                self.expressions(column_expr)
5911                if isinstance(column_expr, exp.Distinct)
5912                else self.sql(column_expr)
5913            )
5914            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5915
5916        return array_agg_sql
5917
5918    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5919        array_agg = self.function_fallback_sql(expression)
5920        column_expr = expression.this
5921        if isinstance(column_expr, exp.Order):
5922            column_expr = column_expr.this
5923
5924        return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
5925
5926    def slice_sql(self, expression: exp.Slice) -> str:
5927        step = self.sql(expression, "step")
5928        end = self.sql(expression.expression)
5929        begin = self.sql(expression.this)
5930
5931        sql = f"{end}:{step}" if step else end
5932        return f"{begin}:{sql}" if sql else f"{begin}:"
5933
5934    def apply_sql(self, expression: exp.Apply) -> str:
5935        this = self.sql(expression, "this")
5936        expr = self.sql(expression, "expression")
5937
5938        return f"{this} APPLY({expr})"
5939
5940    def _grant_or_revoke_sql(
5941        self,
5942        expression: exp.Grant | exp.Revoke,
5943        keyword: str,
5944        preposition: str,
5945        grant_option_prefix: str = "",
5946        grant_option_suffix: str = "",
5947    ) -> str:
5948        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5949
5950        kind = self.sql(expression, "kind")
5951        kind = f" {kind}" if kind else ""
5952
5953        securable = self.sql(expression, "securable")
5954        securable = f" {securable}" if securable else ""
5955
5956        principals = self.expressions(expression, key="principals", flat=True)
5957
5958        if not expression.args.get("grant_option"):
5959            grant_option_prefix = grant_option_suffix = ""
5960
5961        # cascade for revoke only
5962        cascade = self.sql(expression, "cascade")
5963        cascade = f" {cascade}" if cascade else ""
5964
5965        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5966
5967    def grant_sql(self, expression: exp.Grant) -> str:
5968        return self._grant_or_revoke_sql(
5969            expression,
5970            keyword="GRANT",
5971            preposition="TO",
5972            grant_option_suffix=" WITH GRANT OPTION",
5973        )
5974
5975    def revoke_sql(self, expression: exp.Revoke) -> str:
5976        return self._grant_or_revoke_sql(
5977            expression,
5978            keyword="REVOKE",
5979            preposition="FROM",
5980            grant_option_prefix="GRANT OPTION FOR ",
5981        )
5982
5983    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5984        this = self.sql(expression, "this")
5985        columns = self.expressions(expression, flat=True)
5986        columns = f"({columns})" if columns else ""
5987
5988        return f"{this}{columns}"
5989
5990    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5991        this = self.sql(expression, "this")
5992
5993        kind = self.sql(expression, "kind")
5994        kind = f"{kind} " if kind else ""
5995
5996        return f"{kind}{this}"
5997
5998    def columns_sql(self, expression: exp.Columns) -> str:
5999        func = self.function_fallback_sql(expression)
6000        if expression.args.get("unpack"):
6001            func = f"*{func}"
6002
6003        return func
6004
6005    def overlay_sql(self, expression: exp.Overlay) -> str:
6006        this = self.sql(expression, "this")
6007        expr = self.sql(expression, "expression")
6008        from_sql = self.sql(expression, "from_")
6009        for_sql = self.sql(expression, "for_")
6010        for_sql = f" FOR {for_sql}" if for_sql else ""
6011
6012        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
6013
6014    @unsupported_args("format")
6015    def todouble_sql(self, expression: exp.ToDouble) -> str:
6016        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
6017        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
6018
6019    def string_sql(self, expression: exp.String) -> str:
6020        this = expression.this
6021        zone = expression.args.get("zone")
6022
6023        if zone:
6024            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
6025            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
6026            # set for source_tz to transpile the time conversion before the STRING cast
6027            this = exp.ConvertTimezone(
6028                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
6029            )
6030
6031        return self.sql(exp.cast(this, exp.DType.VARCHAR))
6032
6033    def median_sql(self, expression: exp.Median) -> str:
6034        if not self.SUPPORTS_MEDIAN:
6035            return self.sql(
6036                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
6037            )
6038
6039        return self.function_fallback_sql(expression)
6040
6041    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
6042        filler = self.sql(expression, "this")
6043        filler = f" {filler}" if filler else ""
6044        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
6045        return f"TRUNCATE{filler} {with_count}"
6046
6047    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
6048        if self.SUPPORTS_UNIX_SECONDS:
6049            return self.function_fallback_sql(expression)
6050
6051        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
6052
6053        return self.sql(
6054            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
6055        )
6056
6057    def arraysize_sql(self, expression: exp.ArraySize) -> str:
6058        dim = expression.expression
6059
6060        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
6061        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
6062            if not (dim.is_int and dim.name == "1"):
6063                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
6064            dim = None
6065
6066        # If dimension is required but not specified, default initialize it
6067        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
6068            dim = exp.Literal.number(1)
6069
6070        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
6071
6072    def attach_sql(self, expression: exp.Attach) -> str:
6073        this = self.sql(expression, "this")
6074        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
6075        expressions = self.expressions(expression)
6076        expressions = f" ({expressions})" if expressions else ""
6077
6078        return f"ATTACH{exists_sql} {this}{expressions}"
6079
6080    def detach_sql(self, expression: exp.Detach) -> str:
6081        kind = self.sql(expression, "kind")
6082        kind = f" {kind}" if kind else ""
6083        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
6084        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
6085        exists = " IF EXISTS" if expression.args.get("exists") else ""
6086        if exists:
6087            kind = kind or " DATABASE"
6088
6089        this = self.sql(expression, "this")
6090        this = f" {this}" if this else ""
6091        cluster = self.sql(expression, "cluster")
6092        cluster = f" {cluster}" if cluster else ""
6093        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
6094        sync = " SYNC" if expression.args.get("sync") else ""
6095        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
6096
6097    def attachoption_sql(self, expression: exp.AttachOption) -> str:
6098        this = self.sql(expression, "this")
6099        value = self.sql(expression, "expression")
6100        value = f" {value}" if value else ""
6101        return f"{this}{value}"
6102
6103    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
6104        return (
6105            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
6106        )
6107
6108    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
6109        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
6110        encode = f"{encode} {self.sql(expression, 'this')}"
6111
6112        properties = expression.args.get("properties")
6113        if properties:
6114            encode = f"{encode} {self.properties(properties)}"
6115
6116        return encode
6117
6118    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
6119        this = self.sql(expression, "this")
6120        include = f"INCLUDE {this}"
6121
6122        column_def = self.sql(expression, "column_def")
6123        if column_def:
6124            include = f"{include} {column_def}"
6125
6126        alias = self.sql(expression, "alias")
6127        if alias:
6128            include = f"{include} AS {alias}"
6129
6130        return include
6131
6132    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
6133        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
6134        name = f"{prefix} {self.sql(expression, 'this')}"
6135        return self.func("XMLELEMENT", name, *expression.expressions)
6136
6137    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
6138        this = self.sql(expression, "this")
6139        expr = self.sql(expression, "expression")
6140        expr = f"({expr})" if expr else ""
6141        return f"{this}{expr}"
6142
6143    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
6144        partitions = self.expressions(expression, "partition_expressions")
6145        create = self.expressions(expression, "create_expressions")
6146        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
6147
6148    def partitionbyrangepropertydynamic_sql(
6149        self, expression: exp.PartitionByRangePropertyDynamic
6150    ) -> str:
6151        start = self.sql(expression, "start")
6152        end = self.sql(expression, "end")
6153
6154        every = expression.args["every"]
6155        if isinstance(every, exp.Interval) and every.this.is_string:
6156            every.this.replace(exp.Literal.number(every.name))
6157
6158        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6159
6160    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
6161        name = self.sql(expression, "this")
6162        values = self.expressions(expression, flat=True)
6163
6164        return f"NAME {name} VALUE {values}"
6165
6166    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
6167        kind = self.sql(expression, "kind")
6168        sample = self.sql(expression, "sample")
6169        return f"SAMPLE {sample} {kind}"
6170
6171    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
6172        kind = self.sql(expression, "kind")
6173        option = self.sql(expression, "option")
6174        option = f" {option}" if option else ""
6175        this = self.sql(expression, "this")
6176        this = f" {this}" if this else ""
6177        columns = self.expressions(expression)
6178        columns = f" {columns}" if columns else ""
6179        return f"{kind}{option} STATISTICS{this}{columns}"
6180
6181    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
6182        this = self.sql(expression, "this")
6183        columns = self.expressions(expression)
6184        inner_expression = self.sql(expression, "expression")
6185        inner_expression = f" {inner_expression}" if inner_expression else ""
6186        update_options = self.sql(expression, "update_options")
6187        update_options = f" {update_options} UPDATE" if update_options else ""
6188        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
6189
6190    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
6191        kind = self.sql(expression, "kind")
6192        kind = f" {kind}" if kind else ""
6193        return f"DELETE{kind} STATISTICS"
6194
6195    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
6196        inner_expression = self.sql(expression, "expression")
6197        return f"LIST CHAINED ROWS{inner_expression}"
6198
6199    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
6200        kind = self.sql(expression, "kind")
6201        this = self.sql(expression, "this")
6202        this = f" {this}" if this else ""
6203        inner_expression = self.sql(expression, "expression")
6204        return f"VALIDATE {kind}{this}{inner_expression}"
6205
6206    def analyze_sql(self, expression: exp.Analyze) -> str:
6207        options = self.expressions(expression, key="options", sep=" ")
6208        options = f" {options}" if options else ""
6209        kind = self.sql(expression, "kind")
6210        kind = f" {kind}" if kind else ""
6211        tables = self.expressions(expression, key="tables", flat=True)
6212        tables = f" {tables}" if tables else ""
6213        mode = self.sql(expression, "mode")
6214        mode = f" {mode}" if mode else ""
6215        properties = self.sql(expression, "properties")
6216        properties = f" {properties}" if properties else ""
6217        partition = self.sql(expression, "partition")
6218        partition = f" {partition}" if partition else ""
6219        inner_expression = self.sql(expression, "expression")
6220        inner_expression = f" {inner_expression}" if inner_expression else ""
6221        return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6222
6223    def xmltable_sql(self, expression: exp.XMLTable) -> str:
6224        this = self.sql(expression, "this")
6225        namespaces = self.expressions(expression, key="namespaces")
6226        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
6227        passing = self.expressions(expression, key="passing")
6228        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
6229        columns = self.expressions(expression, key="columns")
6230        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
6231        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
6232        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6233
6234    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
6235        this = self.sql(expression, "this")
6236        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
6237
6238    def export_sql(self, expression: exp.Export) -> str:
6239        this = self.sql(expression, "this")
6240        connection = self.sql(expression, "connection")
6241        connection = f"WITH CONNECTION {connection} " if connection else ""
6242        options = self.sql(expression, "options")
6243        return f"EXPORT DATA {connection}{options} AS {this}"
6244
6245    def declare_sql(self, expression: exp.Declare) -> str:
6246        replace = "OR REPLACE " if expression.args.get("replace") else ""
6247        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
6248
6249    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
6250        variables = self.expressions(expression, "this")
6251        default = self.sql(expression, "default")
6252        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
6253
6254        kind = self.sql(expression, "kind")
6255        if isinstance(expression.args.get("kind"), exp.Schema):
6256            kind = f"TABLE {kind}"
6257
6258        kind = f" {kind}" if kind else ""
6259
6260        return f"{variables}{kind}{default}"
6261
6262    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
6263        kind = self.sql(expression, "kind")
6264        this = self.sql(expression, "this")
6265        set = self.sql(expression, "expression")
6266        to = self.sql(expression, "to")
6267        to = f" TO {to}" if to else ""
6268        default = self.sql(expression, "default")
6269        default = f" DEFAULT {default}" if default else ""
6270        using = self.sql(expression, "using")
6271        using = f" USING {using}" if using else ""
6272
6273        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
6274
6275        return f"{kind_sql} {this} SET {set}{to}{default}{using}"
6276
6277    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
6278        params = self.expressions(expression, key="params", flat=True)
6279        return self.func(expression.name, *expression.expressions) + f"({params})"
6280
6281    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
6282        return self.func(expression.name, *expression.expressions)
6283
6284    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
6285        return self.anonymousaggfunc_sql(expression)
6286
6287    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
6288        return self.parameterizedagg_sql(expression)
6289
6290    def show_sql(self, expression: exp.Show) -> str:
6291        self.unsupported("Unsupported SHOW statement")
6292        return ""
6293
6294    def install_sql(self, expression: exp.Install) -> str:
6295        self.unsupported("Unsupported INSTALL statement")
6296        return ""
6297
6298    def liststage_sql(self, expression: exp.ListStage) -> str:
6299        pattern = self.sql(expression, "pattern")
6300        pattern = f" PATTERN = {pattern}" if pattern else ""
6301        return f"LIST {self.sql(expression, 'this')}{pattern}"
6302
6303    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
6304        # Snowflake GET/PUT statements:
6305        #   PUT <file> <internalStage> <properties>
6306        #   GET <internalStage> <file> <properties>
6307        props = expression.args.get("properties")
6308        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
6309        this = self.sql(expression, "this")
6310        target = self.sql(expression, "target")
6311
6312        if isinstance(expression, exp.Put):
6313            return f"PUT {this} {target}{props_sql}"
6314        else:
6315            return f"GET {target} {this}{props_sql}"
6316
6317    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
6318        this = self.sql(expression, "this")
6319        expr = self.sql(expression, "expression")
6320        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6321        return f"TRANSLATE({this} USING {expr}{with_error})"
6322
6323    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6324        if self.SUPPORTS_DECODE_CASE:
6325            return self.func("DECODE", *expression.expressions)
6326
6327        decode_expr, *expressions = expression.expressions
6328
6329        ifs = []
6330        for search, result in zip(expressions[::2], expressions[1::2]):
6331            if isinstance(search, exp.Literal):
6332                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6333            elif isinstance(search, exp.Null):
6334                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6335            else:
6336                if isinstance(search, exp.Binary):
6337                    search = exp.paren(search)
6338
6339                cond = exp.or_(
6340                    decode_expr.eq(search),
6341                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6342                    copy=False,
6343                )
6344                ifs.append(exp.If(this=cond, true=result))
6345
6346        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6347        return self.sql(case)
6348
6349    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6350        this = self.sql(expression, "this")
6351        this = self.seg(this, sep="")
6352        dimensions = self.expressions(
6353            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6354        )
6355        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6356        metrics = self.expressions(
6357            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6358        )
6359        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6360        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6361        facts = self.seg(f"FACTS {facts}") if facts else ""
6362        where = self.sql(expression, "where")
6363        where = self.seg(f"WHERE {where}") if where else ""
6364        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6365        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6366
6367    def getextract_sql(self, expression: exp.GetExtract) -> str:
6368        this = expression.this
6369        expr = expression.expression
6370
6371        if not this.type or not expression.type:
6372            import sqlglot.optimizer.annotate_types
6373
6374            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6375
6376        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6377            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6378
6379        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
6380
6381    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6382        return self.sql(
6383            exp.DateAdd(
6384                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6385                expression=expression.this,
6386                unit=exp.var("DAY"),
6387            )
6388        )
6389
6390    def space_sql(self: Generator, expression: exp.Space) -> str:
6391        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
6392
6393    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6394        return f"BUILD {self.sql(expression, 'this')}"
6395
6396    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6397        method = self.sql(expression, "method")
6398        kind = expression.args.get("kind")
6399        if not kind:
6400            return f"REFRESH {method}"
6401
6402        every = self.sql(expression, "every")
6403        unit = self.sql(expression, "unit")
6404        every = f" EVERY {every} {unit}" if every else ""
6405        starts = self.sql(expression, "starts")
6406        starts = f" STARTS {starts}" if starts else ""
6407
6408        return f"REFRESH {method} ON {kind}{every}{starts}"
6409
6410    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6411        self.unsupported("The model!attribute syntax is not supported")
6412        return ""
6413
6414    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6415        return self.func("DIRECTORY", expression.this)
6416
6417    def uuid_sql(self, expression: exp.Uuid) -> str:
6418        is_string = expression.args.get("is_string", False)
6419        uuid_func_sql = self.func("UUID")
6420
6421        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6422            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6423
6424        return uuid_func_sql
6425
6426    def initcap_sql(self, expression: exp.Initcap) -> str:
6427        delimiters = expression.expression
6428
6429        if delimiters:
6430            # do not generate delimiters arg if we are round-tripping from default delimiters
6431            if (
6432                delimiters.is_string
6433                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6434            ):
6435                delimiters = None
6436            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6437                self.unsupported("INITCAP does not support custom delimiters")
6438                delimiters = None
6439
6440        return self.func("INITCAP", expression.this, delimiters)
6441
6442    def localtime_sql(self, expression: exp.Localtime) -> str:
6443        this = expression.this
6444        return self.func("LOCALTIME", this) if this else "LOCALTIME"
6445
6446    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6447        this = expression.this
6448        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
6449
6450    def weekstart_name(self, expression: exp.WeekStart) -> str:
6451        import sqlglot.dialects.dialect
6452
6453        # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit
6454        this = expression.this.name.upper()
6455
6456        dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this)
6457        dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET)
6458
6459        if dow_from_week_start_day != dow_from_week_offset:
6460            self.unsupported(
6461                f"WEEK({this}) is not supported; falling back to the default week start day"
6462            )
6463
6464        return "WEEK"
6465
6466    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6467        name = self.weekstart_name(expression)
6468
6469        # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords
6470        if isinstance(expression.parent, exp.DateTrunc):
6471            return self.sql(exp.Literal.string(name))
6472
6473        return name
6474
6475    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6476        this = self.expressions(expression)
6477        charset = self.sql(expression, "charset")
6478        using = f" USING {charset}" if charset else ""
6479        return self.func(name, this + using)
6480
6481    def weightstring_sql(self, expression: exp.WeightString) -> str:
6482        to = self.sql(expression, "to")
6483        to = f" AS {to}" if to else ""
6484        return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}")
6485
6486    def block_sql(self, expression: exp.Block) -> str:
6487        expressions = self.expressions(expression, sep="; ", flat=True)
6488        begin = "BEGIN " if expression.args.get("begin") else ""
6489        return f"{begin}{expressions}" if expressions else ""
6490
6491    def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str:
6492        self.unsupported("Unsupported Inline UDFs syntax")
6493        return ""
6494
6495    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6496        self.unsupported("Unsupported Stored Procedure syntax")
6497        return ""
6498
6499    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6500        self.unsupported("Unsupported If block syntax")
6501        return ""
6502
6503    def casestatement_sql(self, expression: exp.CaseStatement) -> str:
6504        self.unsupported("Unsupported Case statement syntax")
6505        return ""
6506
6507    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6508        self.unsupported("Unsupported While block syntax")
6509        return ""
6510
6511    def loopblock_sql(self, expression: exp.LoopBlock) -> str:
6512        self.unsupported("Unsupported Loop block syntax")
6513        return ""
6514
6515    def repeatblock_sql(self, expression: exp.RepeatBlock) -> str:
6516        self.unsupported("Unsupported Repeat block syntax")
6517        return ""
6518
6519    def leave_sql(self, expression: exp.Leave) -> str:
6520        self.unsupported("Unsupported Leave syntax")
6521        return ""
6522
6523    def iterate_sql(self, expression: exp.Iterate) -> str:
6524        self.unsupported("Unsupported Iterate syntax")
6525        return ""
6526
6527    def execute_sql(self, expression: exp.Execute) -> str:
6528        self.unsupported("Unsupported Execute syntax")
6529        return ""
6530
6531    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6532        self.unsupported("Unsupported Execute syntax")
6533        return ""
6534
6535    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6536        props = self.expressions(expression, sep=" ")
6537        return f"MODIFY {props}"
6538
6539    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6540        kind = expression.args.get("kind")
6541        return f"USING {kind} {self.sql(expression, 'this')}"
6542
6543    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6544        this = self.sql(expression, "this")
6545        to = self.sql(expression, "to")
6546        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)
923    def __init__(
924        self,
925        pretty: bool | int | None = None,
926        identify: str | bool = False,
927        normalize: bool = False,
928        pad: int = 2,
929        indent: int = 2,
930        normalize_functions: str | bool | None = None,
931        unsupported_level: ErrorLevel = ErrorLevel.WARN,
932        max_unsupported: int = 3,
933        leading_comma: bool = False,
934        max_text_width: int = 80,
935        comments: bool = True,
936        dialect: DialectType = None,
937    ):
938        import sqlglot
939        import sqlglot.dialects.dialect
940
941        self.pretty = pretty if pretty is not None else sqlglot.pretty
942        self.identify = identify
943        self.normalize = normalize
944        self.pad = pad
945        self._indent = indent
946        self.unsupported_level = unsupported_level
947        self.max_unsupported = max_unsupported
948        self.leading_comma = leading_comma
949        self.max_text_width = max_text_width
950        self.comments = comments
951        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
952
953        # This is both a Dialect property and a Generator argument, so we prioritize the latter
954        self.normalize_functions = (
955            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
956        )
957
958        self.unsupported_messages: list[str] = []
959        self._escaped_quote_end: str = (
960            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
961        )
962        self._escaped_byte_quote_end: str = (
963            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
964            if self.dialect.BYTE_END
965            else ""
966        )
967        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
968        self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES
969
970        self._next_name = name_sequence("_t")
971
972        self._identifier_start = self.dialect.IDENTIFIER_START
973        self._identifier_end = self.dialect.IDENTIFIER_END
974
975        self._quote_json_path_key_using_brackets = True
976
977        cls = type(self)
978        dispatch = _DISPATCH_CACHE.get(cls)
979        if dispatch is None:
980            dispatch = _build_dispatch(cls)
981            _DISPATCH_CACHE[cls] = dispatch
982        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
CAST_TO_INT_ROUNDS = False
SAFE_INT_DIVISION = False
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'
BYTE_STRING_FUNCTION = ''
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.AggregateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <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.properties.PhysicalProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <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.StorageProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <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.TableSpaceProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
RESERVED_KEYWORDS: ClassVar[set[str]] = set()
EXCLUDE_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = (<class 'sqlglot.expressions.core.Binary'>, <class 'sqlglot.expressions.query.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = (<class 'sqlglot.expressions.core.Column'>, <class 'sqlglot.expressions.core.Literal'>, <class 'sqlglot.expressions.core.Neg'>, <class 'sqlglot.expressions.core.Paren'>)
PARAMETERIZABLE_TEXT_TYPES: ClassVar = {<DType.VARCHAR: 'VARCHAR'>, <DType.NVARCHAR: 'NVARCHAR'>, <DType.NCHAR: 'NCHAR'>, <DType.CHAR: 'CHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: ClassVar[set[type[sqlglot.expressions.core.Expr]]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = ()
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'>)
ALTER_PARTITION_OPTIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = ()
SAFE_JSON_PATH_KEY_RE: ClassVar = re.compile('^[_a-zA-Z][\\w]*$')
SENTINEL_LINE_BREAK = '__SQLGLOT__LB__'
pretty
identify
normalize
pad
unsupported_level
max_unsupported
leading_comma
max_text_width
comments
dialect
normalize_functions
unsupported_messages: list[str]
def generate( self, expression: sqlglot.expressions.core.Expr, copy: bool = True) -> str:
 984    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 985        """
 986        Generates the SQL string corresponding to the given syntax tree.
 987
 988        Args:
 989            expression: The syntax tree.
 990            copy: Whether to copy the expression. The generator performs mutations so
 991                it is safer to copy.
 992
 993        Returns:
 994            The SQL string corresponding to `expression`.
 995        """
 996        if copy:
 997            expression = expression.copy()
 998
 999        expression = self.preprocess(expression)
1000
1001        self.unsupported_messages = []
1002        sql = self.sql(expression).strip()
1003
1004        if self.pretty:
1005            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
1006
1007        if self.unsupported_level == ErrorLevel.IGNORE:
1008            return sql
1009
1010        if self.unsupported_level == ErrorLevel.WARN:
1011            for msg in self.unsupported_messages:
1012                logger.warning(msg)
1013        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
1014            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
1015
1016        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:
1018    def preprocess(self, expression: exp.Expr) -> exp.Expr:
1019        """Apply generic preprocessing transformations to a given expression."""
1020        expression = self._move_ctes_to_top_level(expression)
1021
1022        if self.ENSURE_BOOLS:
1023            import sqlglot.transforms
1024
1025            expression = sqlglot.transforms.ensure_bools(expression)
1026
1027        return expression

Apply generic preprocessing transformations to a given expression.

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