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.CopyTagsProperty: lambda *_: "COPY TAGS",
 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 MERGE uses ALL BY NAME instead of a star to update or insert all columns
 338    MERGE_ALL_BY_NAME = False
 339
 340    # Whether the INTERVAL expression works only with values like '1 day'
 341    SINGLE_STRING_INTERVAL = False
 342
 343    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 344    INTERVAL_ALLOWS_PLURAL_FORM = True
 345
 346    # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the
 347    # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND
 348    AUTO_REFRESH_BARE_INTERVALS = False
 349
 350    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 351    LIMIT_FETCH = "ALL"
 352
 353    # Whether limit and fetch allows expresions or just limits
 354    LIMIT_ONLY_LITERALS = False
 355
 356    # Whether a table is allowed to be renamed with a db
 357    RENAME_TABLE_WITH_DB = True
 358
 359    # The separator for grouping sets and rollups
 360    GROUPINGS_SEP = ","
 361
 362    # Whether GROUPING SETS can follow GROUP BY expressions without a comma
 363    SUPPORTS_GROUPING_SETS_AS_SUFFIX = False
 364
 365    # The string used for creating an index on a table
 366    INDEX_ON = "ON"
 367
 368    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 369    INOUT_SEPARATOR = " "
 370
 371    # Whether join hints should be generated
 372    JOIN_HINTS = True
 373
 374    # Whether directed joins are supported
 375    DIRECTED_JOINS = False
 376
 377    # Whether top-k distance/similarity joins are supported
 378    SUPPORTS_NEAREST_JOIN = False
 379
 380    # Whether table hints should be generated
 381    TABLE_HINTS = True
 382
 383    # Whether query hints should be generated
 384    QUERY_HINTS = True
 385
 386    # What kind of separator to use for query hints
 387    QUERY_HINT_SEP = ", "
 388
 389    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 390    IS_BOOL_ALLOWED = True
 391
 392    # Whether casting a fractional number to an integer rounds it instead of truncating it
 393    CAST_TO_INT_ROUNDS = False
 394
 395    # Whether the native integer division operator returns NULL for a zero divisor instead of raising
 396    SAFE_INT_DIVISION = False
 397
 398    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 399    DUPLICATE_KEY_UPDATE_WITH_SET = True
 400
 401    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 402    LIMIT_IS_TOP = False
 403
 404    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 405    RETURNING_END = True
 406
 407    # Whether to generate an unquoted value for EXTRACT's date part argument
 408    EXTRACT_ALLOWS_QUOTES = True
 409
 410    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 411    TZ_TO_WITH_TIME_ZONE = False
 412
 413    # Whether the NVL2 function is supported
 414    NVL2_SUPPORTED = True
 415
 416    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 417    SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE")
 418
 419    # Whether VALUES statements can be used as derived tables.
 420    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 421    # SELECT * VALUES into SELECT UNION
 422    VALUES_AS_TABLE = True
 423
 424    # Whether COLUMN/COLUMNS keywords are included when adding columns with ALTER TABLE.
 425    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 426
 427    # Whether ALTER TABLE mutli-column additions are wrapped in parentheses.
 428    ALTER_TABLE_ADD_COLUMNS_WRAPPED = True
 429
 430    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 431    UNNEST_WITH_ORDINALITY = True
 432
 433    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 434    SEMI_ANTI_JOIN_WITH_SIDE = True
 435
 436    # Whether to include the type of a computed column in the CREATE DDL
 437    COMPUTED_COLUMN_WITH_TYPE = True
 438
 439    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 440    SUPPORTS_TABLE_COPY = True
 441
 442    # Whether parentheses are required around the table sample's expression
 443    TABLESAMPLE_REQUIRES_PARENS = True
 444
 445    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 446    TABLESAMPLE_SIZE_IS_ROWS = True
 447
 448    # The keyword(s) to use when generating a sample clause
 449    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 450
 451    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 452    TABLESAMPLE_WITH_METHOD = True
 453
 454    # The keyword to use when specifying the seed of a sample clause
 455    TABLESAMPLE_SEED_KEYWORD = "SEED"
 456
 457    # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias
 458    HISTORICAL_DATA_POST_ALIAS = False
 459
 460    # Whether COLLATE is a function instead of a binary operator
 461    COLLATE_IS_FUNC = False
 462
 463    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 464    DATA_TYPE_SPECIFIERS_ALLOWED = False
 465
 466    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 467    ENSURE_BOOLS = False
 468
 469    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 470    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 471
 472    # Whether CONCAT requires >1 arguments
 473    SUPPORTS_SINGLE_ARG_CONCAT = True
 474
 475    # Whether LAST_DAY function supports a date part argument
 476    LAST_DAY_SUPPORTS_DATE_PART = True
 477
 478    # Whether named columns are allowed in table aliases
 479    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 480
 481    # Whether named columns are allowed in CTE definitions
 482    SUPPORTS_NAMED_CTE_COLUMNS = True
 483
 484    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 485    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 486
 487    # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048)
 488    PIVOT_ALIAS_WITH_AS = True
 489
 490    # What delimiter to use for separating JSON key/value pairs
 491    JSON_KEY_VALUE_PAIR_SEP = ":"
 492
 493    # INSERT OVERWRITE TABLE x override
 494    INSERT_OVERWRITE = " OVERWRITE TABLE"
 495
 496    # Whether the SELECT .. INTO syntax is used instead of CTAS
 497    SUPPORTS_SELECT_INTO = False
 498
 499    # Whether UNLOGGED tables can be created
 500    SUPPORTS_UNLOGGED_TABLES = False
 501
 502    # Whether the CREATE TABLE LIKE statement is supported
 503    SUPPORTS_CREATE_TABLE_LIKE = True
 504
 505    # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 506    SUPPORTS_MODIFY_COLUMN = False
 507
 508    # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported
 509    SUPPORTS_CHANGE_COLUMN = False
 510
 511    # Whether ALTER COLUMN can set a column's nullability together with its type
 512    SUPPORTS_ALTER_COLUMN_NULLABILITY = False
 513
 514    # Whether ALTER COLUMN IF EXISTS is supported
 515    SUPPORTS_ALTER_COLUMN_IF_EXISTS = False
 516
 517    # Whether the LikeProperty needs to be specified inside of the schema clause
 518    LIKE_PROPERTY_INSIDE_SCHEMA = False
 519
 520    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 521    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 522    MULTI_ARG_DISTINCT = True
 523
 524    # Whether the JSON extraction operators expect a value of type JSON
 525    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 526
 527    # Whether bracketed keys like ["foo"] are supported in JSON paths
 528    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 529
 530    # Whether to escape keys using single quotes in JSON paths
 531    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 532
 533    # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be
 534    # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match
 535    # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks
 536    # where a bare colon key is case-insensitive but a bracketed key is case-sensitive.
 537    JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False
 538
 539    # The JSONPathPart expressions supported by this dialect
 540    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 541
 542    # Whether any(f(x) for x in array) can be implemented by this dialect
 543    CAN_IMPLEMENT_ARRAY_ANY = False
 544
 545    # Whether the function TO_NUMBER is supported
 546    SUPPORTS_TO_NUMBER = True
 547
 548    # Whether EXCLUDE in window specification is supported
 549    SUPPORTS_WINDOW_EXCLUDE = False
 550
 551    # Whether or not set op modifiers apply to the outer set op or select.
 552    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 553    # True means limit 1 happens after the set op, False means it it happens on y.
 554    SET_OP_MODIFIERS = True
 555
 556    # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses.
 557    SET_OP_LIMITS = False
 558
 559    # Whether set operation operands can be parenthesized without a SELECT wrapper.
 560    SET_OP_PARENTHESIZED_OPERANDS = True
 561
 562    # Whether parameters from COPY statement are wrapped in parentheses
 563    COPY_PARAMS_ARE_WRAPPED = True
 564
 565    # Whether values of params are set with "=" token or empty space
 566    COPY_PARAMS_EQ_REQUIRED = False
 567
 568    # Whether COPY statement has INTO keyword
 569    COPY_HAS_INTO_KEYWORD = True
 570
 571    # Whether the conditional TRY(expression) function is supported
 572    TRY_SUPPORTED = True
 573
 574    # Whether the UESCAPE syntax in unicode strings is supported
 575    SUPPORTS_UESCAPE = True
 576
 577    # Function used to replace escaped unicode codes in unicode strings
 578    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 579
 580    # The keyword to use when generating a star projection with excluded columns
 581    STAR_EXCEPT = "EXCEPT"
 582
 583    # The HEX function name
 584    HEX_FUNC = "HEX"
 585
 586    # Function that constructs a binary value from a hex-encoded byte string.
 587    BYTE_STRING_FUNCTION = ""
 588
 589    # The keywords to use when prefixing & separating WITH based properties
 590    WITH_PROPERTIES_PREFIX = "WITH"
 591
 592    # Whether to quote the generated expression of exp.JsonPath
 593    QUOTE_JSON_PATH = True
 594
 595    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 596    PAD_FILL_PATTERN_IS_REQUIRED = False
 597
 598    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 599    SUPPORTS_EXPLODING_PROJECTIONS = True
 600
 601    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 602    ARRAY_CONCAT_IS_VAR_LEN = True
 603
 604    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 605    SUPPORTS_CONVERT_TIMEZONE = False
 606
 607    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 608    SUPPORTS_MEDIAN = True
 609
 610    # Whether UNIX_SECONDS(timestamp) is supported
 611    SUPPORTS_UNIX_SECONDS = False
 612
 613    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 614    ALTER_SET_WRAPPED = False
 615
 616    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 617    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 618    # TODO: The normalization should be done by default once we've tested it across all dialects.
 619    NORMALIZE_EXTRACT_DATE_PARTS = False
 620
 621    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 622    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 623
 624    # The function name of the exp.ArraySize expression
 625    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 626
 627    # The syntax to use when altering the type of a column
 628    ALTER_SET_TYPE = "SET DATA TYPE"
 629
 630    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 631    # None -> Doesn't support it at all
 632    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 633    # True (Postgres) -> Explicitly requires it
 634    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 635
 636    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 637    SUPPORTS_DECODE_CASE = True
 638
 639    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 640    SUPPORTS_BETWEEN_FLAGS = False
 641
 642    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 643    SUPPORTS_LIKE_QUANTIFIERS = True
 644
 645    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 646    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 647
 648    # Whether to include the VARIABLE keyword for SET assignments
 649    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 650
 651    # The keyword to use for default value assignment in DECLARE statements
 652    DECLARE_DEFAULT_ASSIGNMENT = "="
 653
 654    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 655    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 656    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 657    UPDATE_STATEMENT_SUPPORTS_FROM = True
 658
 659    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 660    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 661
 662    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 663    # - Snowflake: DROP ICEBERG TABLE a.b;
 664    # - DuckDB:    DROP TABLE a.b;
 665    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 666
 667    TYPE_MAPPING: t.ClassVar = {
 668        exp.DType.DATETIME2: "TIMESTAMP",
 669        exp.DType.NCHAR: "CHAR",
 670        exp.DType.NVARCHAR: "VARCHAR",
 671        exp.DType.MEDIUMTEXT: "TEXT",
 672        exp.DType.LONGTEXT: "TEXT",
 673        exp.DType.TINYTEXT: "TEXT",
 674        exp.DType.BLOB: "VARBINARY",
 675        exp.DType.MEDIUMBLOB: "BLOB",
 676        exp.DType.LONGBLOB: "BLOB",
 677        exp.DType.TINYBLOB: "BLOB",
 678        exp.DType.INET: "INET",
 679        exp.DType.ROWVERSION: "VARBINARY",
 680        exp.DType.SMALLDATETIME: "TIMESTAMP",
 681    }
 682
 683    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 684
 685    # mapping of DType to its default parameters, bounds
 686    TYPE_PARAM_SETTINGS: t.ClassVar[
 687        dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]
 688    ] = {}
 689
 690    TIME_PART_SINGULARS: t.ClassVar = {
 691        "MICROSECONDS": "MICROSECOND",
 692        "SECONDS": "SECOND",
 693        "MINUTES": "MINUTE",
 694        "HOURS": "HOUR",
 695        "DAYS": "DAY",
 696        "WEEKS": "WEEK",
 697        "MONTHS": "MONTH",
 698        "QUARTERS": "QUARTER",
 699        "YEARS": "YEAR",
 700    }
 701
 702    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 703        "cluster": lambda self, e: self.sql(e, "cluster"),
 704        "distribute": lambda self, e: self.sql(e, "distribute"),
 705        "sort": lambda self, e: self.sql(e, "sort"),
 706        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 707    }
 708
 709    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 710
 711    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 712
 713    PARAMETER_TOKEN = "@"
 714    NAMED_PLACEHOLDER_TOKEN = ":"
 715
 716    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 717
 718    PROPERTIES_LOCATION: t.ClassVar = {
 719        exp.AggregateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 720        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 721        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 722        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 723        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 724        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 725        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 726        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 727        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 728        exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA,
 729        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 730        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 731        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 732        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 733        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 734        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 735        exp.CopyTagsProperty: exp.Properties.Location.POST_SCHEMA,
 736        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 737        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 738        exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA,
 739        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 740        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 741        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 742        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 743        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 744        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 745        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 746        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 747        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 748        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 749        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 750        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 751        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 752        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 753        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 754        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 755        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 756        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 757        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 758        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 759        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 760        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 761        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 762        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 763        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 764        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 765        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 766        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 767        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 768        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 769        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 770        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 771        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 772        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 773        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 774        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 775        exp.LogProperty: exp.Properties.Location.POST_NAME,
 776        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 777        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 778        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 779        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 780        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 781        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 782        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 783        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 784        exp.Order: exp.Properties.Location.POST_SCHEMA,
 785        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 786        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 787        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 788        exp.PhysicalProperty: exp.Properties.Location.UNSUPPORTED,
 789        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 790        exp.Property: exp.Properties.Location.POST_WITH,
 791        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 792        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 793        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 794        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 795        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 796        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 797        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 798        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 799        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 800        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 801        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 802        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 803        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 804        exp.Set: exp.Properties.Location.POST_SCHEMA,
 805        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 806        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 807        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 808        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 809        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 810        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 811        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 812        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 813        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 814        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 815        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 816        exp.StorageProperty: exp.Properties.Location.UNSUPPORTED,
 817        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 818        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 819        exp.Tags: exp.Properties.Location.POST_WITH,
 820        exp.TableSpaceProperty: exp.Properties.Location.UNSUPPORTED,
 821        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 822        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 823        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 824        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 825        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 826        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 827        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 828        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 829        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 830        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 831        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 832        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 833        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 834        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 835        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 836        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 837        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 838    }
 839
 840    # Keywords that can't be used as unquoted identifier names
 841    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 842
 843    # Exprs whose comments are separated from them for better formatting
 844    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 845        exp.Command,
 846        exp.Create,
 847        exp.Describe,
 848        exp.Delete,
 849        exp.Drop,
 850        exp.From,
 851        exp.Insert,
 852        exp.Join,
 853        exp.MultitableInserts,
 854        exp.Order,
 855        exp.Group,
 856        exp.Having,
 857        exp.Select,
 858        exp.SetOperation,
 859        exp.Update,
 860        exp.Where,
 861        exp.With,
 862    )
 863
 864    # Exprs that should not have their comments generated in maybe_comment
 865    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 866        exp.Binary,
 867        exp.SetOperation,
 868    )
 869
 870    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 871    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 872        exp.Column,
 873        exp.Literal,
 874        exp.Neg,
 875        exp.Paren,
 876    )
 877
 878    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 879        exp.DType.NVARCHAR,
 880        exp.DType.VARCHAR,
 881        exp.DType.CHAR,
 882        exp.DType.NCHAR,
 883    }
 884
 885    # Exprs that need to have all CTEs under them bubbled up to them
 886    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 887
 888    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 889
 890    MOD_OPERATOR = "%"
 891
 892    # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses
 893    MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 894        exp.Mul,
 895        exp.Div,
 896        exp.IntDiv,
 897        exp.Mod,
 898    )
 899
 900    # ALTER options that follow the others without a comma, e.g. MySQL's
 901    # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ...
 902    ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 903
 904    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 905
 906    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 907
 908    __slots__ = (
 909        "pretty",
 910        "identify",
 911        "normalize",
 912        "pad",
 913        "_indent",
 914        "normalize_functions",
 915        "unsupported_level",
 916        "max_unsupported",
 917        "leading_comma",
 918        "max_text_width",
 919        "comments",
 920        "dialect",
 921        "unsupported_messages",
 922        "_escaped_quote_end",
 923        "_escaped_byte_quote_end",
 924        "_escaped_identifier_end",
 925        "_identifier_escaped_sequences",
 926        "_next_name",
 927        "_identifier_start",
 928        "_identifier_end",
 929        "_quote_json_path_key_using_brackets",
 930        "_dispatch",
 931    )
 932
 933    def __init__(
 934        self,
 935        pretty: bool | int | None = None,
 936        identify: str | bool = False,
 937        normalize: bool = False,
 938        pad: int = 2,
 939        indent: int = 2,
 940        normalize_functions: str | bool | None = None,
 941        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 942        max_unsupported: int = 3,
 943        leading_comma: bool = False,
 944        max_text_width: int = 80,
 945        comments: bool = True,
 946        dialect: DialectType = None,
 947    ):
 948        import sqlglot
 949        import sqlglot.dialects.dialect
 950
 951        self.pretty = pretty if pretty is not None else sqlglot.pretty
 952        self.identify = identify
 953        self.normalize = normalize
 954        self.pad = pad
 955        self._indent = indent
 956        self.unsupported_level = unsupported_level
 957        self.max_unsupported = max_unsupported
 958        self.leading_comma = leading_comma
 959        self.max_text_width = max_text_width
 960        self.comments = comments
 961        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 962
 963        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 964        self.normalize_functions = (
 965            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 966        )
 967
 968        self.unsupported_messages: list[str] = []
 969        self._escaped_quote_end: str = (
 970            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 971        )
 972        self._escaped_byte_quote_end: str = (
 973            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 974            if self.dialect.BYTE_END
 975            else ""
 976        )
 977        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 978        self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES
 979
 980        self._next_name = name_sequence("_t")
 981
 982        self._identifier_start = self.dialect.IDENTIFIER_START
 983        self._identifier_end = self.dialect.IDENTIFIER_END
 984
 985        self._quote_json_path_key_using_brackets = True
 986
 987        cls = type(self)
 988        dispatch = _DISPATCH_CACHE.get(cls)
 989        if dispatch is None:
 990            dispatch = _build_dispatch(cls)
 991            _DISPATCH_CACHE[cls] = dispatch
 992        self._dispatch = dispatch
 993
 994    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 995        """
 996        Generates the SQL string corresponding to the given syntax tree.
 997
 998        Args:
 999            expression: The syntax tree.
1000            copy: Whether to copy the expression. The generator performs mutations so
1001                it is safer to copy.
1002
1003        Returns:
1004            The SQL string corresponding to `expression`.
1005        """
1006        if copy:
1007            expression = expression.copy()
1008
1009        expression = self.preprocess(expression)
1010
1011        self.unsupported_messages = []
1012        sql = self.sql(expression).strip()
1013
1014        if self.pretty:
1015            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
1016
1017        if self.unsupported_level == ErrorLevel.IGNORE:
1018            return sql
1019
1020        if self.unsupported_level == ErrorLevel.WARN:
1021            for msg in self.unsupported_messages:
1022                logger.warning(msg)
1023        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
1024            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
1025
1026        return sql
1027
1028    def preprocess(self, expression: exp.Expr) -> exp.Expr:
1029        """Apply generic preprocessing transformations to a given expression."""
1030        expression = self._move_ctes_to_top_level(expression)
1031
1032        if self.ENSURE_BOOLS:
1033            import sqlglot.transforms
1034
1035            expression = sqlglot.transforms.ensure_bools(expression)
1036
1037        return expression
1038
1039    def _move_ctes_to_top_level(self, expression: E) -> E:
1040        if (
1041            not expression.parent
1042            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
1043            and any(node.parent is not expression for node in expression.find_all(exp.With))
1044        ):
1045            import sqlglot.transforms
1046
1047            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
1048        return expression
1049
1050    def unsupported(self, message: str) -> None:
1051        if self.unsupported_level == ErrorLevel.IMMEDIATE:
1052            raise UnsupportedError(message)
1053        self.unsupported_messages.append(message)
1054
1055    def sep(self, sep: str = " ") -> str:
1056        return f"{sep.strip()}\n" if self.pretty else sep
1057
1058    def seg(self, sql: str, sep: str = " ") -> str:
1059        return f"{self.sep(sep)}{sql}"
1060
1061    def sanitize_comment(self, comment: str) -> str:
1062        comment = " " + comment if comment[0].strip() else comment
1063        comment = comment + " " if comment[-1].strip() else comment
1064
1065        # Escape block comment markers to prevent premature closure or unintended nesting.
1066        # This is necessary because single-line comments (--) are converted to block comments
1067        # (/* */) on output, and any */ in the original text would close the comment early.
1068        comment = comment.replace("*/", "* /").replace("/*", "/ *")
1069
1070        return comment
1071
1072    def maybe_comment(
1073        self,
1074        sql: str,
1075        expression: exp.Expr | None = None,
1076        comments: list[str] | None = None,
1077        separated: bool = False,
1078    ) -> str:
1079        comments = (
1080            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1081            if self.comments
1082            else None
1083        )
1084
1085        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1086            return sql
1087
1088        comments_list = [
1089            f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/"
1090            for comment in comments
1091            if comment
1092        ]
1093
1094        if not comments_list:
1095            return sql
1096
1097        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1098            comments_sql = self.sep().join(comments_list)
1099            return (
1100                f"{self.sep()}{comments_sql}{sql}"
1101                if not sql or sql[0].isspace()
1102                else f"{comments_sql}{self.sep()}{sql}"
1103            )
1104
1105        return f"{sql} {' '.join(comments_list)}"
1106
1107    def wrap(self, expression: exp.Expr | str) -> str:
1108        this_sql = (
1109            self.sql(expression)
1110            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1111            else self.sql(expression, "this")
1112        )
1113        if not this_sql:
1114            return "()"
1115
1116        this_sql = self.indent(this_sql, level=1, pad=0)
1117        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1118
1119    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1120        original = self.identify
1121        self.identify = False
1122        result = func(*args, **kwargs)
1123        self.identify = original
1124        return result
1125
1126    def normalize_func(self, name: str) -> str:
1127        if self.normalize_functions == "upper" or self.normalize_functions is True:
1128            return name.upper()
1129        if self.normalize_functions == "lower":
1130            return name.lower()
1131        return name
1132
1133    def indent(
1134        self,
1135        sql: str,
1136        level: int = 0,
1137        pad: int | None = None,
1138        skip_first: bool = False,
1139        skip_last: bool = False,
1140    ) -> str:
1141        if not self.pretty or not sql:
1142            return sql
1143
1144        pad = self.pad if pad is None else pad
1145        lines = sql.split("\n")
1146
1147        return "\n".join(
1148            (
1149                line
1150                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1151                else f"{' ' * (level * self._indent + pad)}{line}"
1152            )
1153            for i, line in enumerate(lines)
1154        )
1155
1156    def sql(
1157        self,
1158        expression: str | exp.Expr | None,
1159        key: str | None = None,
1160        comment: bool = True,
1161    ) -> str:
1162        if not expression:
1163            return ""
1164
1165        if isinstance(expression, str):
1166            return expression
1167
1168        if key:
1169            value = expression.args.get(key)
1170            if value:
1171                return self.sql(value)
1172            return ""
1173
1174        handler = self._dispatch.get(expression.__class__)
1175
1176        if handler:
1177            sql = handler(self, expression)
1178        elif isinstance(expression, exp.Func):
1179            sql = self.function_fallback_sql(expression)
1180        elif isinstance(expression, exp.Property):
1181            sql = self.property_sql(expression)
1182        else:
1183            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1184
1185        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1186
1187    def uncache_sql(self, expression: exp.Uncache) -> str:
1188        table = self.sql(expression, "this")
1189        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1190        return f"UNCACHE TABLE{exists_sql} {table}"
1191
1192    def cache_sql(self, expression: exp.Cache) -> str:
1193        lazy = " LAZY" if expression.args.get("lazy") else ""
1194        table = self.sql(expression, "this")
1195        options = expression.args.get("options")
1196        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1197        sql = self.sql(expression, "expression")
1198        sql = f" AS{self.sep()}{sql}" if sql else ""
1199        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1200        return self.prepend_ctes(expression, sql)
1201
1202    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1203        default = "DEFAULT " if expression.args.get("default") else ""
1204        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1205
1206    def column_parts(self, expression: exp.Column) -> str:
1207        if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES:
1208            # The qualifier would be captured by a colliding projection alias (see qualify_columns)
1209            return self.sql(expression, "this")
1210
1211        return ".".join(
1212            self.sql(part)
1213            for part in (
1214                expression.args.get("catalog"),
1215                expression.args.get("db"),
1216                expression.args.get("table"),
1217                expression.args.get("this"),
1218            )
1219            if part
1220        )
1221
1222    def column_sql(self, expression: exp.Column) -> str:
1223        join_mark = " (+)" if expression.args.get("join_mark") else ""
1224
1225        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1226            join_mark = ""
1227            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1228
1229        return f"{self.column_parts(expression)}{join_mark}"
1230
1231    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1232        return self.column_sql(expression)
1233
1234    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1235        this = self.sql(expression, "this")
1236        this = f" {this}" if this else ""
1237        position = self.sql(expression, "position")
1238        return f"{position}{this}"
1239
1240    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1241        column = self.sql(expression, "this")
1242        kind = self.sql(expression, "kind")
1243        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1244        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1245        kind = f"{sep}{kind}" if kind else ""
1246        constraints = f" {constraints}" if constraints else ""
1247        position = self.sql(expression, "position")
1248        position = f" {position}" if position else ""
1249
1250        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1251            kind = ""
1252
1253        return f"{exists}{column}{kind}{constraints}{position}"
1254
1255    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1256        this = self.sql(expression, "this")
1257        kind_sql = self.sql(expression, "kind").strip()
1258        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1259
1260    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1261        this = self.sql(expression, "this")
1262        if expression.args.get("not_null"):
1263            persisted = " PERSISTED NOT NULL"
1264        elif expression.args.get("persisted"):
1265            persisted = " PERSISTED"
1266        else:
1267            persisted = ""
1268
1269        return f"AS {this}{persisted}"
1270
1271    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1272        return self.token_sql(TokenType.AUTO_INCREMENT)
1273
1274    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1275        if isinstance(expression.this, list):
1276            this = self.wrap(self.expressions(expression, key="this", flat=True))
1277        else:
1278            this = self.sql(expression, "this")
1279
1280        return f"COMPRESS {this}"
1281
1282    def generatedasidentitycolumnconstraint_sql(
1283        self, expression: exp.GeneratedAsIdentityColumnConstraint
1284    ) -> str:
1285        this = ""
1286        if expression.this is not None:
1287            on_null = " ON NULL" if expression.args.get("on_null") else ""
1288            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1289
1290        start = expression.args.get("start")
1291        start = f"START WITH {start}" if start else ""
1292        increment = expression.args.get("increment")
1293        increment = f" INCREMENT BY {increment}" if increment else ""
1294        minvalue = expression.args.get("minvalue")
1295        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1296        maxvalue = expression.args.get("maxvalue")
1297        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1298        cycle = expression.args.get("cycle")
1299        cycle_sql = ""
1300
1301        if cycle is not None:
1302            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1303            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1304
1305        sequence_opts = ""
1306        if start or increment or cycle_sql:
1307            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1308            sequence_opts = f" ({sequence_opts.strip()})"
1309
1310        expr = self.sql(expression, "expression")
1311        expr = f"({expr})" if expr else "IDENTITY"
1312
1313        return f"GENERATED{this} AS {expr}{sequence_opts}"
1314
1315    def generatedasrowcolumnconstraint_sql(
1316        self, expression: exp.GeneratedAsRowColumnConstraint
1317    ) -> str:
1318        start = "START" if expression.args.get("start") else "END"
1319        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1320        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1321
1322    def periodforsystemtimeconstraint_sql(
1323        self, expression: exp.PeriodForSystemTimeConstraint
1324    ) -> str:
1325        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1326
1327    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1328        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1329
1330    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1331        desc = expression.args.get("desc")
1332        if desc is not None:
1333            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1334        options = self.expressions(expression, key="options", flat=True, sep=" ")
1335        options = f" {options}" if options else ""
1336        include = self.sql(expression, "include")
1337        return f"PRIMARY KEY{include}{options}"
1338
1339    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1340        this = self.sql(expression, "this")
1341        this = f" {this}" if this else ""
1342        index_type = expression.args.get("index_type")
1343        index_type = f" USING {index_type}" if index_type else ""
1344        on_conflict = self.sql(expression, "on_conflict")
1345        on_conflict = f" {on_conflict}" if on_conflict else ""
1346        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1347        options = self.expressions(expression, key="options", flat=True, sep=" ")
1348        options = f" {options}" if options else ""
1349        include = self.sql(expression, "include")
1350        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{include}{options}"
1351
1352    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1353        input_ = expression.args.get("input_")
1354        output = expression.args.get("output")
1355        variadic = expression.args.get("variadic")
1356
1357        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1358        if variadic:
1359            return "VARIADIC"
1360
1361        if input_ and output:
1362            return f"IN{self.INOUT_SEPARATOR}OUT"
1363        if input_:
1364            return "IN"
1365        if output:
1366            return "OUT"
1367
1368        return ""
1369
1370    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1371        return self.sql(expression, "this")
1372
1373    def create_sql(self, expression: exp.Create) -> str:
1374        kind = self.sql(expression, "kind")
1375        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1376
1377        properties = expression.args.get("properties")
1378
1379        if (
1380            kind == "TRIGGER"
1381            and properties
1382            and properties.expressions
1383            and isinstance(properties.expressions[0], exp.TriggerProperties)
1384            and properties.expressions[0].args.get("constraint")
1385        ):
1386            kind = f"CONSTRAINT {kind}"
1387
1388        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1389
1390        this = self.createable_sql(expression, properties_locs)
1391
1392        properties_sql = ""
1393        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1394            exp.Properties.Location.POST_WITH
1395        ):
1396            props_ast = exp.Properties(
1397                expressions=[
1398                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1399                    *properties_locs[exp.Properties.Location.POST_WITH],
1400                ]
1401            )
1402            props_ast.parent = expression
1403            properties_sql = self.sql(props_ast)
1404
1405            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1406                properties_sql = self.sep() + properties_sql
1407            elif not self.pretty:
1408                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1409                properties_sql = f" {properties_sql}"
1410
1411        begin = " BEGIN" if expression.args.get("begin") else ""
1412
1413        expression_sql = self.sql(expression, "expression")
1414        if expression_sql:
1415            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1416
1417            if not isinstance(expression.expression, exp.MacroOverloads) and (
1418                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1419            ):
1420                postalias_props_sql = ""
1421                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1422                    postalias_props_sql = self.properties(
1423                        exp.Properties(
1424                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1425                        ),
1426                        wrapped=False,
1427                    )
1428                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1429                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1430
1431        postindex_props_sql = ""
1432        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1433            postindex_props_sql = self.properties(
1434                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1435                wrapped=False,
1436                prefix=" ",
1437            )
1438
1439        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1440        indexes = f" {indexes}" if indexes else ""
1441        index_sql = indexes + postindex_props_sql
1442
1443        replace = " OR REPLACE" if expression.args.get("replace") else ""
1444        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1445        unique = " UNIQUE" if expression.args.get("unique") else ""
1446
1447        clustered = expression.args.get("clustered")
1448        if clustered is None:
1449            clustered_sql = ""
1450        elif clustered:
1451            clustered_sql = " CLUSTERED COLUMNSTORE"
1452        else:
1453            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1454
1455        postcreate_props_sql = ""
1456        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1457            postcreate_props_sql = self.properties(
1458                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1459                sep=" ",
1460                prefix=" ",
1461                wrapped=False,
1462            )
1463
1464        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1465
1466        postexpression_props_sql = ""
1467        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1468            postexpression_props_sql = self.properties(
1469                exp.Properties(
1470                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1471                ),
1472                sep=" ",
1473                prefix=" ",
1474                wrapped=False,
1475            )
1476
1477        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1478        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1479        no_schema_binding = (
1480            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1481        )
1482
1483        clone = self.sql(expression, "clone")
1484        clone = f" {clone}" if clone else ""
1485
1486        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1487            properties_expression = f"{expression_sql}{properties_sql}"
1488        else:
1489            properties_expression = f"{properties_sql}{expression_sql}"
1490
1491        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1492        return self.prepend_ctes(expression, expression_sql)
1493
1494    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1495        start = self.sql(expression, "start")
1496        start = f"START WITH {start}" if start else ""
1497        increment = self.sql(expression, "increment")
1498        increment = f" INCREMENT BY {increment}" if increment else ""
1499        minvalue = self.sql(expression, "minvalue")
1500        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1501        maxvalue = self.sql(expression, "maxvalue")
1502        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1503        owned = self.sql(expression, "owned")
1504        owned = f" OWNED BY {owned}" if owned else ""
1505
1506        cache = expression.args.get("cache")
1507        if cache is None:
1508            cache_str = ""
1509        elif cache is True:
1510            cache_str = " CACHE"
1511        else:
1512            cache_str = f" CACHE {cache}"
1513
1514        options = self.expressions(expression, key="options", flat=True, sep=" ")
1515        options = f" {options}" if options else ""
1516
1517        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1518
1519    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1520        timing = expression.args.get("timing", "")
1521        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1522        timing_events = f"{timing} {events}".strip() if timing or events else ""
1523
1524        parts = [timing_events, "ON", self.sql(expression, "table")]
1525
1526        if referenced_table := expression.args.get("referenced_table"):
1527            parts.extend(["FROM", self.sql(referenced_table)])
1528
1529        if deferrable := expression.args.get("deferrable"):
1530            parts.append(deferrable)
1531
1532        if initially := expression.args.get("initially"):
1533            parts.append(f"INITIALLY {initially}")
1534
1535        if referencing := expression.args.get("referencing"):
1536            parts.append(self.sql(referencing))
1537
1538        if for_each := expression.args.get("for_each"):
1539            parts.append(f"FOR EACH {for_each}")
1540
1541        if when := expression.args.get("when"):
1542            parts.append(f"WHEN ({self.sql(when)})")
1543
1544        parts.append(self.sql(expression, "execute"))
1545
1546        return self.sep().join(parts)
1547
1548    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1549        parts = []
1550
1551        if old_alias := expression.args.get("old"):
1552            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1553
1554        if new_alias := expression.args.get("new"):
1555            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1556
1557        return f"REFERENCING {' '.join(parts)}"
1558
1559    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1560        columns = expression.args.get("columns")
1561        if columns:
1562            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1563
1564        return self.sql(expression, "this")
1565
1566    def clone_sql(self, expression: exp.Clone) -> str:
1567        this = self.sql(expression, "this")
1568        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1569        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1570        return f"{shallow}{keyword} {this}"
1571
1572    def describe_sql(self, expression: exp.Describe) -> str:
1573        style = expression.args.get("style")
1574        style = f" {style}" if style else ""
1575        partition = self.sql(expression, "partition")
1576        partition = f" {partition}" if partition else ""
1577        format = self.sql(expression, "format")
1578        format = f" {format}" if format else ""
1579        as_json = " AS JSON" if expression.args.get("as_json") else ""
1580
1581        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1582
1583    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1584        tag = self.sql(expression, "tag")
1585        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1586
1587    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1588        with_ = self.sql(expression, "with_")
1589        if with_:
1590            sql = f"{with_}{self.sep()}{sql}"
1591        return sql
1592
1593    def with_sql(self, expression: exp.With) -> str:
1594        udfs = self.expressions(expression, key="udfs", flat=True)
1595        udfs = f"WITH {udfs}" if udfs else ""
1596
1597        sql = self.expressions(expression, flat=True)
1598
1599        recursive = (
1600            "RECURSIVE "
1601            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1602            else ""
1603        )
1604        search = self.sql(expression, "search")
1605        search = f" {search}" if search else ""
1606        cycle = self.sql(expression, "cycle")
1607        cycle = f" {cycle}" if cycle else ""
1608
1609        sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else ""
1610        return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1611
1612    def cte_sql(self, expression: exp.CTE) -> str:
1613        alias = expression.args.get("alias")
1614        if alias:
1615            alias.add_comments(expression.pop_comments())
1616
1617        alias_sql = self.sql(expression, "alias")
1618
1619        materialized = expression.args.get("materialized")
1620        if materialized is False:
1621            materialized = "NOT MATERIALIZED "
1622        elif materialized:
1623            materialized = "MATERIALIZED "
1624
1625        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1626        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1627
1628        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1629
1630    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1631        alias = self.sql(expression, "this")
1632        columns = self.expressions(expression, key="columns", flat=True)
1633        columns = f"({columns})" if columns else ""
1634
1635        if (
1636            columns
1637            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1638            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1639        ):
1640            columns = ""
1641            self.unsupported("Named columns are not supported in table alias.")
1642
1643        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1644            alias = self._next_name()
1645
1646        return f"{alias}{columns}"
1647
1648    def bitstring_sql(self, expression: exp.BitString) -> str:
1649        this = self.sql(expression, "this")
1650        if self.dialect.BIT_START:
1651            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1652        return f"{int(this, 2)}"
1653
1654    def hexstring_sql(
1655        self, expression: exp.HexString, binary_function_repr: str | None = None
1656    ) -> str:
1657        this = self.sql(expression, "this")
1658        is_integer_type = expression.args.get("is_integer")
1659
1660        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1661            not self.dialect.HEX_START and not binary_function_repr
1662        ):
1663            # Integer representation will be returned if:
1664            # - The read dialect treats the hex value as integer literal but not the write
1665            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1666            return f"{int(this, 16)}"
1667
1668        if not is_integer_type:
1669            # Read dialect treats the hex value as BINARY/BLOB
1670            if binary_function_repr:
1671                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1672                return self.func(binary_function_repr, exp.Literal.string(this))
1673            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1674                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1675                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1676
1677        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1678
1679    def bytestring_sql(self, expression: exp.ByteString) -> str:
1680        this = self.sql(expression, "this")
1681        is_bytes = bool(expression.args.get("is_bytes"))
1682        if is_bytes and self.BYTE_STRING_FUNCTION:
1683            # Hex digits can be quoted directly without escaping.
1684            return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'")
1685
1686        if self.dialect.BYTE_START:
1687            escaped_byte_string = self.escape_str(
1688                this,
1689                escape_backslash=bool(
1690                    self.dialect.tokenizer_class.NUMERIC_ESCAPES
1691                    or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES
1692                ),
1693                delimiter=self.dialect.BYTE_END,
1694                escaped_delimiter=self._escaped_byte_quote_end,
1695                is_byte_string=True,
1696                is_bytes=is_bytes,
1697            )
1698            delimited_byte_string = (
1699                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1700            )
1701            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1702                return self.sql(
1703                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1704                )
1705            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1706                return self.sql(
1707                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1708                )
1709
1710            return delimited_byte_string
1711
1712        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1713            return self.sql(exp.Literal.string(this))
1714
1715        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1716        return ""
1717
1718    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1719        this = self.sql(expression, "this")
1720        escape = expression.args.get("escape")
1721        unicode_start = self.dialect.UNICODE_START
1722
1723        if unicode_start:
1724            escape_substitute = r"\\\1"
1725            left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or ""
1726        else:
1727            escape_substitute = r"\\u\1"
1728            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1729
1730        if escape:
1731            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1732            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1733        else:
1734            escape_pattern = ESCAPED_UNICODE_RE
1735            escape_sql = ""
1736
1737        if not unicode_start or (escape and not self.SUPPORTS_UESCAPE):
1738            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1739
1740        if unicode_start:
1741            # A Unicode literal only escapes its delimiter by doubling it; the escape character
1742            # introduces a code point, so the dialect's ordinary string escapes don't apply here
1743            this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2)
1744        else:
1745            this = self.escape_str(this, escape_backslash=False)
1746
1747        return f"{left_quote}{this}{right_quote}{escape_sql}"
1748
1749    def rawstring_sql(self, expression: exp.RawString) -> str:
1750        string = expression.this
1751        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1752            string = string.replace("\\", "\\\\")
1753
1754        string = self.escape_str(string, escape_backslash=False)
1755        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1756
1757    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1758        this = self.sql(expression, "this")
1759        specifier = self.sql(expression, "expression")
1760        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1761        return f"{this}{specifier}"
1762
1763    def datatype_param_bound_limiter(
1764        self,
1765        expression: exp.DataType,
1766        type_value: exp.DType,
1767        defaults: tuple[int, ...],
1768        bounds: tuple[int | None, ...],
1769    ) -> exp.DataType:
1770        params = expression.expressions
1771
1772        if not params:
1773            if defaults:
1774                expression.set(
1775                    "expressions",
1776                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1777                )
1778            return expression
1779
1780        if not bounds:
1781            return expression
1782
1783        for i, param in enumerate(params):
1784            bound = bounds[i] if i < len(bounds) else None
1785            if bound is None:
1786                continue
1787
1788            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1789            value = (
1790                param_value.to_py()
1791                if isinstance(param_value, exp.Literal) and param_value.is_number
1792                else None
1793            )
1794            if isinstance(value, (int, Decimal)) and value > bound:
1795                self.unsupported(
1796                    f"{type_value.value} parameter {param_value.name} exceeds "
1797                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1798                )
1799                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1800
1801        return expression
1802
1803    def datatype_sql(self, expression: exp.DataType) -> str:
1804        nested = ""
1805        values = ""
1806
1807        expr_nested = expression.args.get("nested")
1808        type_value = expression.this
1809
1810        if (
1811            not expr_nested
1812            and isinstance(type_value, exp.DType)
1813            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1814        ):
1815            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1816
1817        interior = (
1818            self.expressions(
1819                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1820            )
1821            if expr_nested and self.pretty
1822            else self.expressions(expression, flat=True)
1823        )
1824
1825        if type_value in self.UNSUPPORTED_TYPES:
1826            self.unsupported(
1827                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1828            )
1829
1830        type_sql: t.Any = ""
1831        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1832            type_sql = self.sql(expression, "kind")
1833        elif type_value == exp.DType.CHARACTER_SET:
1834            char = f"CHAR({interior})" if interior else "CHAR"
1835            return f"{char} CHARACTER SET {self.sql(expression, 'kind')}"
1836        else:
1837            type_sql = (
1838                self.TYPE_MAPPING.get(type_value, type_value.value)
1839                if isinstance(type_value, exp.DType)
1840                else type_value
1841            )
1842
1843        if interior:
1844            if expr_nested:
1845                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1846                if expression.args.get("values") is not None:
1847                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1848                    values = self.expressions(expression, key="values", flat=True)
1849                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1850            elif type_value == exp.DType.INTERVAL:
1851                nested = f" {interior}"
1852            else:
1853                nested = f"({interior})"
1854
1855        type_sql = f"{type_sql}{nested}{values}"
1856        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1857            exp.DType.TIMETZ,
1858            exp.DType.TIMESTAMPTZ,
1859        ):
1860            type_sql = f"{type_sql} WITH TIME ZONE"
1861
1862        collate = self.sql(expression, "collate")
1863        if collate:
1864            type_sql = f"{type_sql} COLLATE {collate}"
1865
1866        return type_sql
1867
1868    def directory_sql(self, expression: exp.Directory) -> str:
1869        local = "LOCAL " if expression.args.get("local") else ""
1870        row_format = self.sql(expression, "row_format")
1871        row_format = f" {row_format}" if row_format else ""
1872        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1873
1874    def delete_sql(self, expression: exp.Delete) -> str:
1875        hint = self.sql(expression, "hint")
1876        this = self.sql(expression, "this")
1877        this = f" FROM {this}" if this else ""
1878        using = self.expressions(expression, key="using")
1879        using = f" USING {using}" if using else ""
1880        cluster = self.sql(expression, "cluster")
1881        cluster = f" {cluster}" if cluster else ""
1882        where = self.sql(expression, "where")
1883        returning = self.sql(expression, "returning")
1884        order = self.sql(expression, "order")
1885        limit = self.sql(expression, "limit")
1886        tables = self.expressions(expression, key="tables")
1887        tables = f" {tables}" if tables else ""
1888        if self.RETURNING_END:
1889            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1890        else:
1891            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1892        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1893
1894    def drop_sql(self, expression: exp.Drop) -> str:
1895        tables = self.expressions(expression, key="tables", flat=True)
1896        expressions = self.expressions(expression, flat=True)
1897        expressions = f" ({expressions})" if expressions else ""
1898        kind = expression.args["kind"]
1899        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1900        iceberg = (
1901            " ICEBERG"
1902            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1903            else ""
1904        )
1905        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1906        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1907        on_cluster = self.sql(expression, "cluster")
1908        on_cluster = f" {on_cluster}" if on_cluster else ""
1909        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1910        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1911        cascade = " CASCADE" if expression.args.get("cascade") else ""
1912        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1913        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1914        purge = " PURGE" if expression.args.get("purge") else ""
1915        sync = " SYNC" if expression.args.get("sync") else ""
1916        force = " FORCE" if expression.args.get("force") else ""
1917        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1918
1919    def set_operation(self, expression: exp.SetOperation) -> str:
1920        op_type = type(expression)
1921        op_name = op_type.key.upper()
1922
1923        distinct = expression.args.get("distinct")
1924        if (
1925            distinct is False
1926            and op_type in (exp.Except, exp.Intersect)
1927            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1928        ):
1929            self.unsupported(f"{op_name} ALL is not supported")
1930
1931        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1932
1933        if distinct is None:
1934            distinct = default_distinct
1935            if distinct is None:
1936                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1937
1938        if distinct is default_distinct:
1939            distinct_or_all = ""
1940        else:
1941            distinct_or_all = " DISTINCT" if distinct else " ALL"
1942
1943        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1944        side_kind = f"{side_kind} " if side_kind else ""
1945
1946        by_name = " BY NAME" if expression.args.get("by_name") else ""
1947        on = self.expressions(expression, key="on", flat=True)
1948        on = f" ON ({on})" if on else ""
1949
1950        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1951
1952    def set_operations(self, expression: exp.SetOperation) -> str:
1953        if not self.SET_OP_MODIFIERS:
1954            limit = expression.args.get("limit")
1955            order = expression.args.get("order")
1956            offset = expression.args.get("offset")
1957
1958            if limit or order or offset:
1959                select = self._move_ctes_to_top_level(
1960                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1961                )
1962
1963                for arg in ("limit", "order", "offset"):
1964                    if value := expression.args.get(arg):
1965                        select.set(arg, value.pop())
1966                return self.sql(select)
1967
1968        sqls: list[str] = []
1969        stack: list[str | exp.Expr | int | None] = [expression]
1970        depth = 0
1971        open_parens = ""
1972
1973        while stack:
1974            node = stack.pop()
1975
1976            if isinstance(node, int):
1977                # Closes a plain grouping opened below, restoring its depth
1978                depth = node
1979                sqls[-1] += self.sep("") + self.indent(")", level=depth, pad=0)
1980            elif isinstance(node, exp.SetOperation):
1981                stack.append(node.expression)
1982                stack.append(
1983                    self.maybe_comment(
1984                        self.set_operation(node), comments=node.comments, separated=True
1985                    )
1986                )
1987                left = node.this
1988                if (
1989                    not self.SET_OP_PARENTHESIZED_OPERANDS
1990                    and not self.dialect.INTERSECT_BINDS_TIGHTER_THAN_UNION_AND_EXCEPT
1991                ):
1992                    # Left grouping is implicit, so skip the `SELECT * FROM (...)` wrapper:
1993                    # it renames duplicate columns and deepens recursion. Inner CTEs and
1994                    # modifiers still need the wrapper.
1995                    wrappers: list[exp.Subquery] = []
1996                    inner = left
1997                    while isinstance(inner, exp.Subquery) and inner.is_wrapper:
1998                        wrappers.append(inner)
1999                        inner = inner.this
2000
2001                    if (
2002                        isinstance(inner, exp.SetOperation)
2003                        and not inner.args.get("with_")
2004                        and not any(inner.args.get(key) for key in exp.QUERY_MODIFIERS)
2005                    ):
2006                        for wrapper in wrappers:
2007                            inner.add_comments(wrapper.pop_comments())
2008                        left = inner
2009                elif (
2010                    self.SET_OP_PARENTHESIZED_OPERANDS
2011                    and isinstance(left, exp.Subquery)
2012                    and left.is_wrapper
2013                    and not left.comments
2014                    and isinstance(left.this, exp.SetOperation)
2015                    and not left.this.args.get("with_")
2016                    and not any(left.this.args.get(key) for key in exp.QUERY_MODIFIERS)
2017                ):
2018                    # Write a plain grouping's parentheses here rather than recursing through
2019                    # subquery_sql: left-to-right chains nest one grouping per INTERSECT, and
2020                    # long ones would exhaust the stack. The depth pushed here marks the close.
2021                    open_parens += self.indent("(", level=depth, pad=0) + self.sep("")
2022                    stack.append(depth)
2023                    depth += 1
2024                    left = left.this
2025                stack.append(left)
2026            else:
2027                if (
2028                    not self.SET_OP_LIMITS
2029                    and isinstance(node, exp.Select)
2030                    and node.args.get("limit")
2031                ):
2032                    node = node.subquery(copy=False)
2033                    if not self.SET_OP_PARENTHESIZED_OPERANDS:
2034                        node = exp.select("*").from_(node, copy=False)
2035                elif (
2036                    not self.SET_OP_PARENTHESIZED_OPERANDS
2037                    and isinstance(node, exp.Subquery)
2038                    and isinstance(node.unnest(), exp.SetOperation)
2039                ):
2040                    node = exp.select("*").from_(node, copy=False)
2041                sqls.append(open_parens + self.indent(self.sql(node), level=depth, pad=0))
2042                open_parens = ""
2043
2044        this = self.sep().join(sqls)
2045        this = self.query_modifiers(expression, this)
2046        return self.prepend_ctes(expression, this)
2047
2048    def fetch_sql(self, expression: exp.Fetch) -> str:
2049        direction = expression.args.get("direction")
2050        direction = f" {direction}" if direction else ""
2051        count = self.sql(expression, "count")
2052        count = f" {count}" if count else ""
2053        limit_options = self.sql(expression, "limit_options")
2054        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
2055        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
2056
2057    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
2058        percent = " PERCENT" if expression.args.get("percent") else ""
2059        rows = " ROWS" if expression.args.get("rows") else ""
2060        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
2061        if not with_ties and rows:
2062            with_ties = " ONLY"
2063        return f"{percent}{rows}{with_ties}"
2064
2065    def filter_sql(self, expression: exp.Filter) -> str:
2066        this = self.sql(expression, "this")
2067        where = self.sql(expression, "expression").strip()
2068        return f"{this} FILTER({where})"
2069
2070    def per_sql(self, expression: exp.Per) -> str:
2071        return f"{self.sql(expression, 'this')} PER ({self.expressions(expression, flat=True)})"
2072
2073    def hint_sql(self, expression: exp.Hint) -> str:
2074        if not self.QUERY_HINTS:
2075            self.unsupported("Hints are not supported")
2076            return ""
2077
2078        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
2079
2080    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
2081        using = self.sql(expression, "using")
2082        using = f" USING {using}" if using else ""
2083        columns = self.expressions(expression, key="columns", flat=True)
2084        columns = f"({columns})" if columns else ""
2085        partition_by = self.expressions(expression, key="partition_by", flat=True)
2086        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
2087        where = self.sql(expression, "where")
2088        include = self.expressions(expression, key="include", flat=True)
2089        if include:
2090            include = f" INCLUDE ({include})"
2091        with_storage = self.expressions(expression, key="with_storage", flat=True)
2092        with_storage = f" WITH ({with_storage})" if with_storage else ""
2093        tablespace = self.sql(expression, "tablespace")
2094        if tablespace:
2095            prefix = "" if isinstance(expression.parent, exp.Index) else " USING INDEX"
2096            tablespace = f"{prefix} TABLESPACE {tablespace}"
2097        on = self.sql(expression, "on")
2098        on = f" ON {on}" if on else ""
2099
2100        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
2101
2102    def index_sql(self, expression: exp.Index) -> str:
2103        unique = "UNIQUE " if expression.args.get("unique") else ""
2104        primary = "PRIMARY " if expression.args.get("primary") else ""
2105        amp = "AMP " if expression.args.get("amp") else ""
2106        name = self.sql(expression, "this")
2107        name = f"{name} " if name else ""
2108        table = self.sql(expression, "table")
2109        table = f"{self.INDEX_ON} {table}" if table else ""
2110
2111        index = "INDEX " if not table else ""
2112
2113        params = self.sql(expression, "params")
2114        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
2115
2116    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
2117        this = expression.this
2118        if this and this.is_string:
2119            resolved = maybe_parse(this.name).sql(self.dialect)
2120            if "expressions" in expression.args:
2121                # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
2122                # We can't safely emit the call to other dialects since name/arg semantics may differ
2123                self.unsupported(
2124                    "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported"
2125                )
2126            return resolved
2127        self.unsupported("IDENTIFIER() with non-literal arguments is not supported")
2128        return self.func("IDENTIFIER", this)
2129
2130    def identifier_sql(self, expression: exp.Identifier) -> str:
2131        text = expression.name
2132        lower = text.lower()
2133        quoted = expression.quoted
2134        text = lower if self.normalize and not quoted else text
2135        if self._identifier_escaped_sequences:
2136            for char, escaped in self._identifier_escaped_sequences.items():
2137                text = text.replace(char, escaped)
2138        text = text.replace(self._identifier_end, self._escaped_identifier_end)
2139        if (
2140            quoted
2141            or self.dialect.can_quote(expression, self.identify)
2142            or lower in self.RESERVED_KEYWORDS
2143            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
2144        ):
2145            text = (
2146                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
2147            )
2148        return text
2149
2150    def hex_sql(self, expression: exp.Hex) -> str:
2151        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2152        if self.dialect.HEX_LOWERCASE:
2153            text = self.func("LOWER", text)
2154
2155        return text
2156
2157    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
2158        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2159        if not self.dialect.HEX_LOWERCASE:
2160            text = self.func("LOWER", text)
2161        return text
2162
2163    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
2164        input_format = self.sql(expression, "input_format")
2165        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
2166        output_format = self.sql(expression, "output_format")
2167        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
2168        return self.sep().join((input_format, output_format))
2169
2170    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
2171        string = self.sql(exp.Literal.string(expression.name))
2172        return f"{prefix}{string}"
2173
2174    def partition_sql(self, expression: exp.Partition) -> str:
2175        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
2176        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
2177
2178    def properties_sql(self, expression: exp.Properties) -> str:
2179        root_properties = []
2180        with_properties = []
2181
2182        for p in expression.expressions:
2183            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2184            if p_loc == exp.Properties.Location.POST_WITH:
2185                with_properties.append(p)
2186            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2187                root_properties.append(p)
2188
2189        root_props_ast = exp.Properties(expressions=root_properties)
2190        root_props_ast.parent = expression.parent
2191
2192        with_props_ast = exp.Properties(expressions=with_properties)
2193        with_props_ast.parent = expression.parent
2194
2195        root_props = self.root_properties(root_props_ast)
2196        with_props = self.with_properties(with_props_ast)
2197
2198        if root_props and with_props and not self.pretty:
2199            with_props = " " + with_props
2200
2201        return root_props + with_props
2202
2203    def root_properties(self, properties: exp.Properties) -> str:
2204        if properties.expressions:
2205            return self.expressions(properties, indent=False, sep=" ")
2206        return ""
2207
2208    def properties(
2209        self,
2210        properties: exp.Properties,
2211        prefix: str = "",
2212        sep: str = ", ",
2213        suffix: str = "",
2214        wrapped: bool = True,
2215    ) -> str:
2216        if properties.expressions:
2217            expressions = self.expressions(properties, sep=sep, indent=False)
2218            if expressions:
2219                expressions = self.wrap(expressions) if wrapped else expressions
2220                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2221        return ""
2222
2223    def with_properties(self, properties: exp.Properties) -> str:
2224        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
2225
2226    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2227        properties_locs = defaultdict(list)
2228        for p in properties.expressions:
2229            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2230            if p_loc != exp.Properties.Location.UNSUPPORTED:
2231                properties_locs[p_loc].append(p)
2232            else:
2233                self.unsupported(f"Unsupported property {p.key}")
2234
2235        return properties_locs
2236
2237    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2238        if isinstance(expression.this, exp.Dot):
2239            return self.sql(expression, "this")
2240        return self.sql(exp.Literal.string(expression.name)) if string_key else expression.name
2241
2242    def property_sql(self, expression: exp.Property) -> str:
2243        property_cls = expression.__class__
2244        if property_cls == exp.Property:
2245            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2246
2247        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2248        if not property_name:
2249            self.unsupported(f"Unsupported property {expression.key}")
2250            return ""
2251
2252        return f"{property_name}={self.sql(expression, 'this')}"
2253
2254    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2255        return f"UUID {self.sql(expression, 'this')}"
2256
2257    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2258        if self.SUPPORTS_CREATE_TABLE_LIKE:
2259            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2260            options = f" {options}" if options else ""
2261
2262            like = f"LIKE {self.sql(expression, 'this')}{options}"
2263            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2264                like = f"({like})"
2265
2266            return like
2267
2268        if expression.expressions:
2269            self.unsupported("Transpilation of LIKE property options is unsupported")
2270
2271        select = exp.select("*").from_(expression.this).limit(0)
2272        return f"AS {self.sql(select)}"
2273
2274    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2275        no = "NO " if expression.args.get("no") else ""
2276        protection = " PROTECTION" if expression.args.get("protection") else ""
2277        return f"{no}FALLBACK{protection}"
2278
2279    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2280        no = "NO " if expression.args.get("no") else ""
2281        local = expression.args.get("local")
2282        local = f"{local} " if local else ""
2283        dual = "DUAL " if expression.args.get("dual") else ""
2284        before = "BEFORE " if expression.args.get("before") else ""
2285        after = "AFTER " if expression.args.get("after") else ""
2286        return f"{no}{local}{dual}{before}{after}JOURNAL"
2287
2288    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2289        freespace = self.sql(expression, "this")
2290        percent = " PERCENT" if expression.args.get("percent") else ""
2291        return f"FREESPACE={freespace}{percent}"
2292
2293    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2294        if expression.args.get("default"):
2295            property = "DEFAULT"
2296        elif expression.args.get("on"):
2297            property = "ON"
2298        else:
2299            property = "OFF"
2300        return f"CHECKSUM={property}"
2301
2302    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2303        if expression.args.get("no"):
2304            return "NO MERGEBLOCKRATIO"
2305        if expression.args.get("default"):
2306            return "DEFAULT MERGEBLOCKRATIO"
2307
2308        percent = " PERCENT" if expression.args.get("percent") else ""
2309        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2310
2311    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2312        expressions = self.expressions(expression, flat=True)
2313        expressions = f"({expressions})" if expressions else ""
2314        return f"USING {self.sql(expression, 'this')}{expressions}"
2315
2316    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2317        default = expression.args.get("default")
2318        minimum = expression.args.get("minimum")
2319        maximum = expression.args.get("maximum")
2320        if default or minimum or maximum:
2321            if default:
2322                prop = "DEFAULT"
2323            elif minimum:
2324                prop = "MINIMUM"
2325            else:
2326                prop = "MAXIMUM"
2327            return f"{prop} DATABLOCKSIZE"
2328        units = expression.args.get("units")
2329        units = f" {units}" if units else ""
2330        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2331
2332    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2333        autotemp = expression.args.get("autotemp")
2334        always = expression.args.get("always")
2335        default = expression.args.get("default")
2336        manual = expression.args.get("manual")
2337        never = expression.args.get("never")
2338
2339        if autotemp is not None:
2340            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2341        elif always:
2342            prop = "ALWAYS"
2343        elif default:
2344            prop = "DEFAULT"
2345        elif manual:
2346            prop = "MANUAL"
2347        elif never:
2348            prop = "NEVER"
2349        return f"BLOCKCOMPRESSION={prop}"
2350
2351    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2352        no = expression.args.get("no")
2353        no = " NO" if no else ""
2354        concurrent = expression.args.get("concurrent")
2355        concurrent = " CONCURRENT" if concurrent else ""
2356        target = self.sql(expression, "target")
2357        target = f" {target}" if target else ""
2358        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2359
2360    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2361        if isinstance(expression.this, list):
2362            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2363        if expression.this:
2364            modulus = self.sql(expression, "this")
2365            remainder = self.sql(expression, "expression")
2366            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2367
2368        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2369        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2370        return f"FROM ({from_expressions}) TO ({to_expressions})"
2371
2372    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2373        this = self.sql(expression, "this")
2374
2375        for_values_or_default = expression.expression
2376        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2377            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2378        else:
2379            for_values_or_default = " DEFAULT"
2380
2381        return f"PARTITION OF {this}{for_values_or_default}"
2382
2383    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2384        kind = expression.args.get("kind")
2385        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2386        for_or_in = expression.args.get("for_or_in")
2387        for_or_in = f" {for_or_in}" if for_or_in else ""
2388        lock_type = expression.args.get("lock_type")
2389        override = " OVERRIDE" if expression.args.get("override") else ""
2390        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2391
2392    def lockingstatement_sql(self, expression: exp.LockingStatement) -> str:
2393        self.unsupported("LOCKING request modifier is not supported")
2394        return self.sql(expression, "expression")
2395
2396    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2397        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2398        statistics = expression.args.get("statistics")
2399        statistics_sql = ""
2400        if statistics is not None:
2401            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2402        return f"{data_sql}{statistics_sql}"
2403
2404    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2405        this = self.sql(expression, "this")
2406        this = f"HISTORY_TABLE={this}" if this else ""
2407        data_consistency: str | None = self.sql(expression, "data_consistency")
2408        data_consistency = (
2409            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2410        )
2411        retention_period: str | None = self.sql(expression, "retention_period")
2412        retention_period = (
2413            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2414        )
2415
2416        if this:
2417            on_sql = self.func("ON", this, data_consistency, retention_period)
2418        else:
2419            on_sql = "ON" if expression.args.get("on") else "OFF"
2420
2421        sql = f"SYSTEM_VERSIONING={on_sql}"
2422
2423        return f"WITH({sql})" if expression.args.get("with_") else sql
2424
2425    def insert_sql(self, expression: exp.Insert) -> str:
2426        hint = self.sql(expression, "hint")
2427        overwrite = expression.args.get("overwrite")
2428
2429        if isinstance(expression.this, exp.Directory):
2430            this = " OVERWRITE" if overwrite else " INTO"
2431        else:
2432            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2433
2434        stored = self.sql(expression, "stored")
2435        stored = f" {stored}" if stored else ""
2436        alternative = expression.args.get("alternative")
2437        alternative = f" OR {alternative}" if alternative else ""
2438        ignore = " IGNORE" if expression.args.get("ignore") else ""
2439        is_function = expression.args.get("is_function")
2440        if is_function:
2441            this = f"{this} FUNCTION"
2442        this = f"{this} {self.sql(expression, 'this')}"
2443
2444        exists = " IF EXISTS" if expression.args.get("exists") else ""
2445        where = self.sql(expression, "where")
2446        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2447        using = self.expressions(expression, key="using", flat=True)
2448        using = f"{self.sep()}REPLACE USING ({using})" if using else ""
2449        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2450        on_conflict = self.sql(expression, "conflict")
2451        on_conflict = f" {on_conflict}" if on_conflict else ""
2452        by_name = " BY NAME" if expression.args.get("by_name") else ""
2453        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2454        returning = self.sql(expression, "returning")
2455
2456        if self.RETURNING_END:
2457            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2458        else:
2459            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2460
2461        partition_by = self.sql(expression, "partition")
2462        partition_by = f" {partition_by}" if partition_by else ""
2463        settings = self.sql(expression, "settings")
2464        settings = f" {settings}" if settings else ""
2465
2466        source = self.sql(expression, "source")
2467        source = f"TABLE {source}" if source else ""
2468
2469        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}"
2470        return self.prepend_ctes(expression, sql)
2471
2472    def introducer_sql(self, expression: exp.Introducer) -> str:
2473        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2474
2475    def kill_sql(self, expression: exp.Kill) -> str:
2476        kind = self.sql(expression, "kind")
2477        kind = f" {kind}" if kind else ""
2478        this = self.sql(expression, "this")
2479        this = f" {this}" if this else ""
2480        return f"KILL{kind}{this}"
2481
2482    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2483        return expression.name
2484
2485    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2486        return expression.name
2487
2488    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2489        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2490
2491        constraint = self.sql(expression, "constraint")
2492        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2493
2494        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2495        if conflict_keys:
2496            conflict_keys = f"({conflict_keys})"
2497
2498        index_predicate = self.sql(expression, "index_predicate")
2499        conflict_keys = f"{conflict_keys}{index_predicate} "
2500
2501        action = self.sql(expression, "action")
2502
2503        expressions = self.expressions(expression, flat=True)
2504        if expressions:
2505            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2506            expressions = f" {set_keyword}{expressions}"
2507
2508        where = self.sql(expression, "where")
2509        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2510
2511    def returning_sql(self, expression: exp.Returning) -> str:
2512        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2513
2514    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2515        fields = self.sql(expression, "fields")
2516        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2517        escaped = self.sql(expression, "escaped")
2518        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2519        items = self.sql(expression, "collection_items")
2520        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2521        keys = self.sql(expression, "map_keys")
2522        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2523        lines = self.sql(expression, "lines")
2524        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2525        null = self.sql(expression, "null")
2526        null = f" NULL DEFINED AS {null}" if null else ""
2527        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2528
2529    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2530        return f"WITH ({self.expressions(expression, flat=True)})"
2531
2532    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2533        this = f"{self.sql(expression, 'this')} INDEX"
2534        target = self.sql(expression, "target")
2535        target = f" FOR {target}" if target else ""
2536        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2537
2538    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2539        this = self.sql(expression, "this")
2540        kind = self.sql(expression, "kind")
2541        expr = self.sql(expression, "expression")
2542        return f"{this} ({kind} => {expr})"
2543
2544    def table_parts(self, expression: exp.Table) -> str:
2545        return ".".join(
2546            self.sql(part)
2547            for part in (
2548                expression.args.get("catalog"),
2549                expression.args.get("db"),
2550                expression.args.get("this"),
2551            )
2552            if part is not None
2553        )
2554
2555    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2556        table = self.table_parts(expression)
2557        only = "ONLY " if expression.args.get("only") else ""
2558        partition = self.sql(expression, "partition")
2559        partition = f" {partition}" if partition else ""
2560        version = self.sql(expression, "version")
2561        version = f" {version}" if version else ""
2562        alias = self.sql(expression, "alias")
2563        alias = f"{sep}{alias}" if alias else ""
2564
2565        sample = self.sql(expression, "sample")
2566        post_alias = ""
2567        pre_alias = ""
2568
2569        if self.dialect.ALIAS_POST_TABLESAMPLE:
2570            pre_alias = sample
2571        else:
2572            post_alias = sample
2573
2574        if self.dialect.ALIAS_POST_VERSION:
2575            pre_alias = f"{pre_alias}{version}"
2576        else:
2577            post_alias = f"{post_alias}{version}"
2578
2579        hints = self.expressions(expression, key="hints", sep=" ")
2580        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2581        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2582        joins = self.indent(
2583            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2584        )
2585        laterals = self.expressions(expression, key="laterals", sep="")
2586
2587        file_format = self.sql(expression, "format")
2588        pattern = self.sql(expression, "pattern")
2589        if file_format:
2590            pattern = f", PATTERN => {pattern}" if pattern else ""
2591            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2592        elif pattern:
2593            file_format = f" (PATTERN => {pattern})"
2594
2595        ordinality = expression.args.get("ordinality") or ""
2596        if ordinality:
2597            ordinality = f" WITH ORDINALITY{alias}"
2598            alias = ""
2599
2600        when = self.sql(expression, "when")
2601        if when:
2602            if self.HISTORICAL_DATA_POST_ALIAS:
2603                alias = f"{alias} {when}"
2604            else:
2605                table = f"{table} {when}"
2606
2607        changes = self.sql(expression, "changes")
2608        changes = f" {changes}" if changes else ""
2609
2610        rows_from = self.expressions(expression, key="rows_from")
2611        if rows_from:
2612            table = f"ROWS FROM {self.wrap(rows_from)}"
2613
2614        indexed = expression.args.get("indexed")
2615        if indexed is not None:
2616            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2617        else:
2618            indexed = ""
2619
2620        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2621
2622    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2623        table = self.func("TABLE", expression.this)
2624        alias = self.sql(expression, "alias")
2625        alias = f" AS {alias}" if alias else ""
2626        sample = self.sql(expression, "sample")
2627        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2628        joins = self.indent(
2629            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2630        )
2631        return f"{table}{alias}{pivots}{sample}{joins}"
2632
2633    def tablesample_sql(
2634        self,
2635        expression: exp.TableSample,
2636        tablesample_keyword: str | None = None,
2637    ) -> str:
2638        if expression.expressions:
2639            self.unsupported("Multiple sample sets are not supported")
2640            return ""
2641
2642        method = self.sql(expression, "method")
2643        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2644        numerator = self.sql(expression, "bucket_numerator")
2645        denominator = self.sql(expression, "bucket_denominator")
2646        field = self.sql(expression, "bucket_field")
2647        field = f" ON {field}" if field else ""
2648        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2649        seed = self.sql(expression, "seed")
2650        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2651
2652        size = self.sql(expression, "size")
2653        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2654            size = f"{size} ROWS"
2655
2656        percent = self.sql(expression, "percent")
2657        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2658            percent = f"{percent} PERCENT"
2659
2660        expr = f"{bucket}{percent}{size}"
2661        if self.TABLESAMPLE_REQUIRES_PARENS:
2662            expr = f"({expr})"
2663
2664        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2665
2666    def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None:
2667        # Returns the rewritten field.expressions list with PivotAlias wrappers injected where
2668        # the stored column name differs from the target dialect's natural output.
2669        columns = expression.args.get("columns")
2670        if not columns or len(expression.fields) != 1:
2671            return None
2672
2673        args = expression.args
2674        parser_cls = self.dialect.parser_class
2675
2676        tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS
2677        tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS
2678        tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING
2679
2680        src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings)
2681        src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns)
2682        src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming)
2683
2684        if (
2685            src_identify_pivot_strings == tgt_identify_pivot_strings
2686            and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns
2687            and src_pivot_column_naming == tgt_pivot_column_naming
2688        ):
2689            return None
2690
2691        in_exprs = expression.fields[0].expressions
2692        step = len(columns) // len(in_exprs)
2693
2694        # Derive the per-value suffix from the first stored column vs the first IN-list value.
2695        # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases.
2696        first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name
2697        first_stored = columns[0].name
2698
2699        # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed)
2700        if not first_stored.startswith(first_base):
2701            return None
2702
2703        suffix = first_stored[len(first_base) :]
2704
2705        # Whether the target dialect would append an agg-name suffix for this pivot.
2706        # Spark single-agg uniquely drops the agg alias entirely.
2707        target_has_suffix = (
2708            len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple"
2709        ) and any(a.alias for a in expression.expressions)
2710        source_has_suffix = suffix != ""
2711
2712        new_exprs: list[exp.Expression] = []
2713        modified = False
2714        for val_idx, e in enumerate(in_exprs):
2715            if isinstance(e, exp.PivotAlias):
2716                new_exprs.append(e)
2717                continue
2718
2719            i = val_idx * step
2720            stored_full = columns[i].name
2721            stored_value = stored_full[: -len(suffix)] if suffix else stored_full
2722            target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name
2723
2724            # Source had a suffix, but target won't apply one
2725            if source_has_suffix and not target_has_suffix:
2726                new_exprs.append(
2727                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True))
2728                )
2729                modified = True
2730            # Value-part mismatch (e.g. Snowflake's literal-style values vs others).
2731            elif stored_value != target_value:
2732                new_exprs.append(
2733                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True))
2734                )
2735                modified = True
2736            else:
2737                new_exprs.append(e)
2738
2739        return new_exprs if modified else None
2740
2741    def pivot_sql(self, expression: exp.Pivot) -> str:
2742        expressions = self.expressions(expression, flat=True)
2743        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2744
2745        group = self.sql(expression, "group")
2746
2747        if expression.this:
2748            this = self.sql(expression, "this")
2749            if not expressions:
2750                sql = f"UNPIVOT {this}"
2751            else:
2752                on = f"{self.seg('ON')} {expressions}"
2753                into = self.sql(expression, "into")
2754                into = f"{self.seg('INTO')} {into}" if into else ""
2755                using = self.expressions(expression, key="using", flat=True)
2756                using = f"{self.seg('USING')} {using}" if using else ""
2757                sql = f"{direction} {this}{on}{into}{using}{group}"
2758            return self.prepend_ctes(expression, sql)
2759
2760        if not expression.unpivot:
2761            # Wrap IN-list values with explicit aliases where the target dialect would differ
2762            new_field_exprs = self._pivot_in_value_aliases(expression)
2763            if new_field_exprs is not None:
2764                expression.fields[0].set("expressions", new_field_exprs)
2765
2766        alias = self.sql(expression, "alias")
2767        if alias:
2768            alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}"
2769
2770        fields = self.expressions(
2771            expression,
2772            "fields",
2773            sep=" ",
2774            dynamic=True,
2775            new_line=True,
2776            skip_first=True,
2777            skip_last=True,
2778        )
2779
2780        include_nulls = expression.args.get("include_nulls")
2781        if include_nulls is not None:
2782            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2783        else:
2784            nulls = ""
2785
2786        default_on_null = self.sql(expression, "default_on_null")
2787        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2788        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2789        return self.prepend_ctes(expression, sql)
2790
2791    def version_sql(self, expression: exp.Version) -> str:
2792        this = f"FOR {expression.name}"
2793        kind = expression.text("kind")
2794        expr = self.sql(expression, "expression")
2795        return f"{this} {kind} {expr}"
2796
2797    def tuple_sql(self, expression: exp.Tuple) -> str:
2798        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2799
2800    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2801        """
2802        Returns (join_sql, from_sql) for UPDATE statements.
2803        - join_sql: placed after UPDATE table, before SET
2804        - from_sql: placed after SET clause (standard position)
2805        Dialects like MySQL need to convert FROM to JOIN syntax.
2806        """
2807        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2808            return ("", self.sql(expression, "from_"))
2809
2810        # Qualify unqualified columns in SET clause with the target table
2811        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2812        target_table = expression.this
2813        if isinstance(target_table, exp.Table):
2814            target_name = exp.to_identifier(target_table.alias_or_name)
2815            for eq in expression.expressions:
2816                col = eq.this
2817                if isinstance(col, exp.Column) and not col.table:
2818                    col.set("table", target_name)
2819
2820        table = from_expr.this
2821        if nested_joins := table.args.get("joins", []):
2822            table.set("joins", None)
2823
2824        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2825        for nested in nested_joins:
2826            if not nested.args.get("on") and not nested.args.get("using"):
2827                nested.set("on", exp.true())
2828            join_sql += self.sql(nested)
2829
2830        return (join_sql, "")
2831
2832    def update_sql(self, expression: exp.Update) -> str:
2833        hint = self.sql(expression, "hint")
2834        this = self.sql(expression, "this")
2835        join_sql, from_sql = self._update_from_joins_sql(expression)
2836        set_sql = self.expressions(expression, flat=True)
2837        where_sql = self.sql(expression, "where")
2838        returning = self.sql(expression, "returning")
2839        order = self.sql(expression, "order")
2840        limit = self.sql(expression, "limit")
2841        if self.RETURNING_END:
2842            expression_sql = f"{from_sql}{where_sql}{returning}"
2843        else:
2844            expression_sql = f"{returning}{from_sql}{where_sql}"
2845        options = self.expressions(expression, key="options")
2846        options = f" OPTION({options})" if options else ""
2847        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2848        return self.prepend_ctes(expression, sql)
2849
2850    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2851        values_as_table = values_as_table and self.VALUES_AS_TABLE
2852
2853        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2854        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2855            args = self.expressions(expression)
2856            alias = self.sql(expression, "alias")
2857            values = f"VALUES{self.seg('')}{args}"
2858            values = (
2859                f"({values})"
2860                if self.WRAP_DERIVED_VALUES
2861                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2862                else values
2863            )
2864            values = self.query_modifiers(expression, values)
2865            return f"{values} AS {alias}" if alias else values
2866
2867        # Converts `VALUES...` expression into a series of select unions.
2868        alias_node = expression.args.get("alias")
2869        column_names = alias_node and alias_node.columns
2870
2871        selects: list[exp.Query] = []
2872
2873        for i, tup in enumerate(expression.expressions):
2874            row = tup.expressions
2875
2876            if i == 0 and column_names:
2877                row = [
2878                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2879                ]
2880
2881            selects.append(exp.Select(expressions=row))
2882
2883        if self.pretty:
2884            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2885            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2886            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2887            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2888            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2889
2890        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2891        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2892        return f"({unions}){alias}"
2893
2894    def var_sql(self, expression: exp.Var) -> str:
2895        return self.sql(expression, "this")
2896
2897    @unsupported_args("expressions")
2898    def into_sql(self, expression: exp.Into) -> str:
2899        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2900        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2901        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2902
2903    def from_sql(self, expression: exp.From) -> str:
2904        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2905
2906    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2907        grouping_sets = self.expressions(expression, indent=False)
2908        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2909
2910    def rollup_sql(self, expression: exp.Rollup) -> str:
2911        expressions = self.expressions(expression, indent=False)
2912        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2913
2914    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2915        this = self.sql(expression, "this")
2916
2917        columns = self.expressions(expression, flat=True)
2918
2919        from_sql = self.sql(expression, "from_index")
2920        from_sql = f" FROM {from_sql}" if from_sql else ""
2921
2922        properties = expression.args.get("properties")
2923        properties_sql = (
2924            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2925        )
2926
2927        return f"{this}({columns}){from_sql}{properties_sql}"
2928
2929    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2930        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2931
2932    def cube_sql(self, expression: exp.Cube) -> str:
2933        expressions = self.expressions(expression, indent=False)
2934        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2935
2936    def group_sql(self, expression: exp.Group) -> str:
2937        group_by_all = expression.args.get("all")
2938        if group_by_all is True:
2939            modifier = " ALL"
2940        elif group_by_all is False:
2941            modifier = " DISTINCT"
2942        else:
2943            modifier = ""
2944
2945        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2946
2947        grouping_sets = self.expressions(expression, key="grouping_sets")
2948        cube = self.expressions(expression, key="cube")
2949        rollup = self.expressions(expression, key="rollup")
2950
2951        groupings = csv(
2952            self.seg(grouping_sets) if grouping_sets else "",
2953            self.seg(cube) if cube else "",
2954            self.seg(rollup) if rollup else "",
2955            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2956            sep=self.GROUPINGS_SEP,
2957        )
2958
2959        if (
2960            expression.expressions
2961            and groupings
2962            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2963        ):
2964            add_separator = True
2965
2966            if grouping_sets:
2967                if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX:
2968                    add_separator = False
2969                else:
2970                    self.unsupported(
2971                        "GROUPING SETS without a comma after GROUP BY expressions is not supported"
2972                    )
2973
2974            if add_separator:
2975                group_by = f"{group_by}{self.GROUPINGS_SEP}"
2976
2977        return f"{group_by}{groupings}"
2978
2979    def having_sql(self, expression: exp.Having) -> str:
2980        this = self.indent(self.sql(expression, "this"))
2981        return f"{self.seg('HAVING')}{self.sep()}{this}"
2982
2983    def connectto_sql(self, expression: exp.ConnectTo) -> str:
2984        return f"CONNECT {self.sql(expression, 'this')}"
2985
2986    def disconnect_sql(self, expression: exp.Disconnect) -> str:
2987        return "DISCONNECT"
2988
2989    def connect_sql(self, expression: exp.Connect) -> str:
2990        start = self.sql(expression, "start")
2991        start = self.seg(f"START WITH {start}") if start else ""
2992        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2993        connect = self.sql(expression, "connect")
2994        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2995        return start + connect
2996
2997    def prior_sql(self, expression: exp.Prior) -> str:
2998        return f"PRIOR {self.sql(expression, 'this')}"
2999
3000    def join_sql(self, expression: exp.Join, join_op: str | None = None) -> str:
3001        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
3002            side = None
3003        else:
3004            side = expression.side
3005
3006        op_sql = " ".join(
3007            op
3008            for op in (
3009                expression.method,
3010                "GLOBAL" if expression.args.get("global_") else None,
3011                side,
3012                expression.kind,
3013                expression.hint if self.JOIN_HINTS else None,
3014                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
3015            )
3016            if op
3017        )
3018        match_cond = self.sql(expression, "match_condition")
3019        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
3020        nearest = self.sql(expression, "nearest")
3021        nearest = f" {nearest}" if nearest else ""
3022        on_sql = self.sql(expression, "on")
3023        using = expression.args.get("using")
3024
3025        if not on_sql and using:
3026            on_sql = csv(*(self.sql(column) for column in using))
3027
3028        this = expression.this
3029        this_sql = self.sql(this)
3030
3031        exprs = self.expressions(expression)
3032        if exprs:
3033            this_sql = f"{this_sql},{self.seg(exprs)}"
3034
3035        if on_sql:
3036            on_sql = self.indent(on_sql, skip_first=True)
3037            space = self.seg(" " * self.pad) if self.pretty else " "
3038            if using:
3039                on_sql = f"{space}USING ({on_sql})"
3040            else:
3041                on_sql = f"{space}ON {on_sql}"
3042        elif not op_sql and not nearest:
3043            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
3044                return f" {this_sql}"
3045
3046            return f", {this_sql}"
3047
3048        if join_op:
3049            op_sql = join_op
3050        elif op_sql != "STRAIGHT_JOIN":
3051            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
3052
3053        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3054        return f"{self.seg(op_sql)} {this_sql}{match_cond}{nearest}{on_sql}{pivots}"
3055
3056    def nearest_sql(self, expression: exp.Nearest) -> str:
3057        if not self.SUPPORTS_NEAREST_JOIN:
3058            self.unsupported("NEAREST joins are not supported")
3059
3060        approximate = "APPROX " if expression.args.get("approximate") else ""
3061        metric = "SIMILARITY" if expression.args.get("similarity") else "DISTANCE"
3062        return f"{approximate}NEAREST {self.sql(expression, 'count')} BY {metric} {self.sql(expression, 'this')}"
3063
3064    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
3065        args = self.expressions(expression, flat=True)
3066        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
3067        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
3068
3069    def lateral_op(self, expression: exp.Lateral) -> str:
3070        cross_apply = expression.args.get("cross_apply")
3071
3072        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
3073        if cross_apply is True:
3074            op = "INNER JOIN "
3075        elif cross_apply is False:
3076            op = "LEFT JOIN "
3077        else:
3078            op = ""
3079
3080        return f"{op}LATERAL"
3081
3082    def lateral_sql(self, expression: exp.Lateral) -> str:
3083        this = self.sql(expression, "this")
3084
3085        if expression.args.get("view"):
3086            alias = expression.args["alias"]
3087            columns = self.expressions(alias, key="columns", flat=True)
3088            table = f" {alias.name}" if alias.name else ""
3089            columns = f" AS {columns}" if columns else ""
3090            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
3091            return f"{op_sql}{self.sep()}{this}{table}{columns}"
3092
3093        table_alias = expression.args.get("alias")
3094        offset = expression.this.args.get("offset")
3095
3096        if (
3097            self.UNNEST_WITH_ORDINALITY
3098            and table_alias
3099            and isinstance(expression.this, exp.Unnest)
3100            and isinstance(offset, exp.Identifier)
3101        ):
3102            # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset
3103            table_alias = table_alias.copy()
3104            table_alias.append("columns", offset.copy())
3105
3106        alias = self.sql(table_alias)
3107        alias = f" AS {alias}" if alias else ""
3108
3109        ordinality = expression.args.get("ordinality") or ""
3110        if ordinality:
3111            ordinality = f" WITH ORDINALITY{alias}"
3112            alias = ""
3113
3114        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
3115
3116    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
3117        this = self.sql(expression, "this")
3118
3119        if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL:
3120            return this
3121
3122        args = [
3123            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
3124            for e in (expression.args.get(k) for k in ("offset", "expression"))
3125            if e
3126        ]
3127
3128        args_sql = ", ".join(self.sql(e) for e in args)
3129        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
3130        expressions = self.expressions(expression, flat=True)
3131        limit_options = self.sql(expression, "limit_options")
3132        expressions = f" BY {expressions}" if expressions else ""
3133
3134        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
3135
3136    def offset_sql(self, expression: exp.Offset) -> str:
3137        this = self.sql(expression, "this")
3138        value = expression.expression
3139        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
3140        expressions = self.expressions(expression, flat=True)
3141        expressions = f" BY {expressions}" if expressions else ""
3142        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
3143
3144    def setitem_sql(self, expression: exp.SetItem) -> str:
3145        kind = self.sql(expression, "kind")
3146        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
3147            kind = ""
3148        else:
3149            kind = f"{kind} " if kind else ""
3150        this = self.sql(expression, "this")
3151        expressions = self.expressions(expression)
3152        collate = self.sql(expression, "collate")
3153        collate = f" COLLATE {collate}" if collate else ""
3154        global_ = "GLOBAL " if expression.args.get("global_") else ""
3155        return f"{global_}{kind}{this}{expressions}{collate}"
3156
3157    def set_sql(self, expression: exp.Set) -> str:
3158        expressions = f" {self.expressions(expression, flat=True)}"
3159        tag = " TAG" if expression.args.get("tag") else ""
3160        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
3161
3162    def queryband_sql(self, expression: exp.QueryBand) -> str:
3163        this = self.sql(expression, "this")
3164        update = " UPDATE" if expression.args.get("update") else ""
3165        scope = self.sql(expression, "scope")
3166        scope = f" FOR {scope}" if scope else ""
3167
3168        return f"QUERY_BAND = {this}{update}{scope}"
3169
3170    def pragma_sql(self, expression: exp.Pragma) -> str:
3171        return f"PRAGMA {self.sql(expression, 'this')}"
3172
3173    def lock_sql(self, expression: exp.Lock) -> str:
3174        if not self.LOCKING_READS_SUPPORTED:
3175            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
3176            return ""
3177
3178        update = expression.args["update"]
3179        key = expression.args.get("key")
3180        if update:
3181            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
3182        else:
3183            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
3184        expressions = self.expressions(expression, flat=True)
3185        expressions = f" OF {expressions}" if expressions else ""
3186        wait = expression.args.get("wait")
3187
3188        if wait is not None:
3189            if isinstance(wait, exp.Literal):
3190                wait = f" WAIT {self.sql(wait)}"
3191            else:
3192                wait = " NOWAIT" if wait else " SKIP LOCKED"
3193
3194        return f"{lock_type}{expressions}{wait or ''}"
3195
3196    def literal_sql(self, expression: exp.Literal) -> str:
3197        text = expression.this or ""
3198        if expression.is_string:
3199            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
3200        return text
3201
3202    def escape_str(
3203        self,
3204        text: str,
3205        escape_backslash: bool = True,
3206        delimiter: str | None = None,
3207        escaped_delimiter: str | None = None,
3208        is_byte_string: bool = False,
3209        is_bytes: bool = False,
3210    ) -> str:
3211        if is_byte_string:
3212            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
3213        else:
3214            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
3215
3216        if supports_escape_sequences:
3217            text = "".join(
3218                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
3219                for ch in text
3220            )
3221
3222            if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES:
3223                # Escape high bytes so they aren't re-encoded as UTF-8 characters.
3224                text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text)
3225
3226        delimiter = delimiter or self.dialect.QUOTE_END
3227        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
3228
3229        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3230
3231    def loaddata_sql(self, expression: exp.LoadData) -> str:
3232        is_overwrite = expression.args.get("overwrite")
3233        overwrite = " OVERWRITE" if is_overwrite else ""
3234        this = self.sql(expression, "this")
3235
3236        files = expression.args.get("files")
3237        if files:
3238            files_sql = self.expressions(files, flat=True)
3239            files_sql = f"FILES{self.wrap(files_sql)}"
3240            if is_overwrite:
3241                this = f" {this}"
3242            elif expression.args.get("temp"):
3243                this = f" INTO TEMP TABLE {this}"
3244            else:
3245                this = f" INTO TABLE {this}"
3246            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
3247
3248        local = " LOCAL" if expression.args.get("local") else ""
3249        inpath = f" INPATH {self.sql(expression, 'inpath')}"
3250        this = f" INTO TABLE {this}"
3251        partition = self.sql(expression, "partition")
3252        partition = f" {partition}" if partition else ""
3253        input_format = self.sql(expression, "input_format")
3254        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3255        serde = self.sql(expression, "serde")
3256        serde = f" SERDE {serde}" if serde else ""
3257        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3258
3259    def null_sql(self, *_) -> str:
3260        return "NULL"
3261
3262    def boolean_sql(self, expression: exp.Boolean) -> str:
3263        return "TRUE" if expression.this else "FALSE"
3264
3265    def booland_sql(self, expression: exp.Booland) -> str:
3266        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
3267
3268    def boolor_sql(self, expression: exp.Boolor) -> str:
3269        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
3270
3271    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3272        this = self.sql(expression, "this")
3273        this = f"{this} " if this else this
3274        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3275        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3276
3277    def withfill_sql(self, expression: exp.WithFill) -> str:
3278        from_sql = self.sql(expression, "from_")
3279        from_sql = f" FROM {from_sql}" if from_sql else ""
3280        to_sql = self.sql(expression, "to")
3281        to_sql = f" TO {to_sql}" if to_sql else ""
3282        step_sql = self.sql(expression, "step")
3283        step_sql = f" STEP {step_sql}" if step_sql else ""
3284        interpolated_values = [
3285            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3286            if isinstance(e, exp.Alias)
3287            else self.sql(e, "this")
3288            for e in expression.args.get("interpolate") or []
3289        ]
3290        interpolate = (
3291            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3292        )
3293        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3294
3295    def cluster_sql(self, expression: exp.Cluster) -> str:
3296        return self.op_expressions("CLUSTER BY", expression)
3297
3298    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3299        if expression.this:
3300            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3301            return ""
3302        expressions = self.expressions(expression, flat=True)
3303        return f"CLUSTER BY ({expressions})"
3304
3305    def distribute_sql(self, expression: exp.Distribute) -> str:
3306        return self.op_expressions("DISTRIBUTE BY", expression)
3307
3308    def sort_sql(self, expression: exp.Sort) -> str:
3309        return self.op_expressions("SORT BY", expression)
3310
3311    def _resolve_ordered_for_null_ordering_simulation(
3312        self, expression: exp.Ordered
3313    ) -> exp.Expr | None:
3314        """Resolve a bare ORDER BY name against the enclosing SELECT projection.
3315
3316        Returns the underlying expression of the uniquely-matching projection
3317        (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE
3318        simulation, since the CASE is evaluated in FROM-clause scope rather
3319        than alias scope (MySQL error 1052). Returns None if no safe
3320        substitution applies, leaving the original behaviour unchanged.
3321        """
3322        this = expression.this
3323        if not (isinstance(this, exp.Column) and not this.table):
3324            return None
3325
3326        # Only a query's own ORDER BY can reference its projection aliases
3327        order = expression.parent
3328        ancestor = order.parent if isinstance(order, exp.Order) else None
3329        if not isinstance(ancestor, exp.Select):
3330            return None
3331
3332        column_name = this.name
3333        matched: list[exp.Expr] = [
3334            p.this if isinstance(p, exp.Alias) else p
3335            for p in ancestor.selects
3336            if p.output_name == column_name
3337        ]
3338        match = matched[0] if len(matched) == 1 else None
3339
3340        # Skip the substitution when it would be identical to the existing
3341        # reference (e.g. ``SELECT col FROM t ORDER BY col``).
3342        if isinstance(match, exp.Column) and not match.table and match.name == column_name:
3343            return None
3344
3345        return match
3346
3347    def ordered_sql(self, expression: exp.Ordered) -> str:
3348        desc = expression.args.get("desc")
3349        asc = not desc
3350
3351        nulls_first = expression.args.get("nulls_first")
3352        nulls_last = not nulls_first
3353        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3354        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3355        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3356
3357        this = self.sql(expression, "this")
3358
3359        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3360        nulls_sort_change = ""
3361        if nulls_first and (
3362            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3363        ):
3364            nulls_sort_change = " NULLS FIRST"
3365        elif (
3366            nulls_last
3367            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3368            and not nulls_are_last
3369        ):
3370            nulls_sort_change = " NULLS LAST"
3371
3372        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3373        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3374            window = expression.find_ancestor(exp.Window, exp.Select)
3375
3376            if isinstance(window, exp.Window):
3377                window_this = window.this
3378                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3379                    window_this = window_this.this
3380                spec = window.args.get("spec")
3381            else:
3382                window_this = None
3383                spec = None
3384
3385            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3386            # without a spec or with a ROWS spec, but not with RANGE
3387            if not (
3388                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3389                and (not spec or spec.text("kind").upper() == "ROWS")
3390            ):
3391                if window_this and spec:
3392                    self.unsupported(
3393                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3394                    )
3395                    nulls_sort_change = ""
3396                elif self.NULL_ORDERING_SUPPORTED is False and (
3397                    (asc and nulls_sort_change == " NULLS LAST")
3398                    or (desc and nulls_sort_change == " NULLS FIRST")
3399                ):
3400                    # BigQuery does not allow these ordering/nulls combinations when used under
3401                    # an aggregation func or under a window containing one
3402                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3403
3404                    if isinstance(ancestor, exp.Window):
3405                        ancestor = ancestor.this
3406                    if isinstance(ancestor, exp.AggFunc):
3407                        self.unsupported(
3408                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3409                        )
3410                        nulls_sort_change = ""
3411                elif self.NULL_ORDERING_SUPPORTED is None:
3412                    if expression.this.is_int:
3413                        self.unsupported(
3414                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3415                        )
3416                    elif not isinstance(expression.this, exp.Rand):
3417                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3418                        target = self.sql(resolved) if resolved is not None else this
3419                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3420                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3421                    nulls_sort_change = ""
3422
3423        with_fill = self.sql(expression, "with_fill")
3424        with_fill = f" {with_fill}" if with_fill else ""
3425
3426        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
3427
3428    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3429        window_frame = self.sql(expression, "window_frame")
3430        window_frame = f"{window_frame} " if window_frame else ""
3431
3432        this = self.sql(expression, "this")
3433
3434        return f"{window_frame}{this}"
3435
3436    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3437        partition = self.partition_by_sql(expression)
3438        order = self.sql(expression, "order")
3439        measures = self.expressions(expression, key="measures")
3440        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3441        rows = self.sql(expression, "rows")
3442        rows = self.seg(rows) if rows else ""
3443        after = self.sql(expression, "after")
3444        after = self.seg(after) if after else ""
3445        pattern = self.sql(expression, "pattern")
3446        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3447        definition_sqls = [
3448            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3449            for definition in expression.args.get("define", [])
3450        ]
3451        definitions = self.expressions(sqls=definition_sqls)
3452        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3453        body = "".join(
3454            (
3455                partition,
3456                order,
3457                measures,
3458                rows,
3459                after,
3460                pattern,
3461                define,
3462            )
3463        )
3464        alias = self.sql(expression, "alias")
3465        alias = f" {alias}" if alias else ""
3466        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3467
3468    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3469        limit = expression.args.get("limit")
3470
3471        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3472            count = limit.args.get("count")
3473            # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL
3474            # standard; emitting a bare "LIMIT" here would produce invalid SQL.
3475            limit = exp.Limit(
3476                expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1)
3477            )
3478        elif (
3479            self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all
3480        ):
3481            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3482
3483        return csv(
3484            *sqls,
3485            *[self.sql(join) for join in expression.args.get("joins") or []],
3486            self.sql(expression, "match"),
3487            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3488            self.sql(expression, "prewhere"),
3489            self.sql(expression, "where"),
3490            self.sql(expression, "connect"),
3491            self.sql(expression, "group"),
3492            self.sql(expression, "having"),
3493            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3494            self.sql(expression, "order"),
3495            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3496            *self.after_limit_modifiers(expression),
3497            self.sql(expression, "for_"),
3498            self.options_modifier(expression),
3499            sep="",
3500        )
3501
3502    def options_modifier(self, expression: exp.Expr) -> str:
3503        options = self.expressions(expression, key="options")
3504        return f" {options}" if options else ""
3505
3506    def forclause_sql(self, expression: exp.ForClause) -> str:
3507        kind = expression.args["kind"]
3508        if kind == "BROWSE":
3509            return f"{self.sep()}FOR BROWSE"
3510        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3511        # the target dialect doesn't support QueryOption, so we drop the clause.
3512        options = self.expressions(expression, key="expressions")
3513        if not options:
3514            return ""
3515        return f"{self.sep()}FOR {kind}{self.seg(options)}"
3516
3517    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3518        self.unsupported("Unsupported query option.")
3519        return ""
3520
3521    def offset_limit_modifiers(
3522        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3523    ) -> list[str]:
3524        return [
3525            self.sql(expression, "offset") if fetch else self.sql(limit),
3526            self.sql(limit) if fetch else self.sql(expression, "offset"),
3527        ]
3528
3529    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3530        locks = self.expressions(expression, key="locks", sep=" ")
3531        locks = f" {locks}" if locks else ""
3532        return [locks, self.sql(expression, "sample")]
3533
3534    def select_sql(self, expression: exp.Select) -> str:
3535        into = expression.args.get("into")
3536        if not self.SUPPORTS_SELECT_INTO and into:
3537            into.pop()
3538
3539        hint = self.sql(expression, "hint")
3540        distinct = self.sql(expression, "distinct")
3541        distinct = f" {distinct}" if distinct else ""
3542        kind = self.sql(expression, "kind")
3543
3544        limit = expression.args.get("limit")
3545        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3546            top = self.limit_sql(limit, top=True)
3547            limit.pop()
3548        else:
3549            top = ""
3550
3551        expressions = self.expressions(expression)
3552
3553        if kind:
3554            if kind in self.SELECT_KINDS:
3555                kind = f" AS {kind}"
3556            else:
3557                if kind == "STRUCT":
3558                    expressions = self.expressions(
3559                        sqls=[
3560                            self.sql(
3561                                exp.Struct(
3562                                    expressions=[
3563                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3564                                        if isinstance(e, exp.Alias)
3565                                        else e
3566                                        for e in expression.expressions
3567                                    ]
3568                                )
3569                            )
3570                        ]
3571                    )
3572                kind = ""
3573
3574        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3575        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3576
3577        exclude = expression.args.get("exclude")
3578
3579        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3580            exclude_sql = self.expressions(sqls=exclude, flat=True)
3581            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3582
3583        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3584        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3585        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3586        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3587        sql = self.query_modifiers(
3588            expression,
3589            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3590            self.sql(expression, "into", comment=False),
3591            self.sql(expression, "from_", comment=False),
3592        )
3593
3594        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3595        if expression.args.get("with_"):
3596            sql = self.maybe_comment(sql, expression)
3597            expression.pop_comments()
3598
3599        sql = self.prepend_ctes(expression, sql)
3600
3601        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3602            expression.set("exclude", None)
3603            subquery = expression.subquery(copy=False)
3604            star = exp.Star(except_=exclude)
3605            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3606
3607        if not self.SUPPORTS_SELECT_INTO and into:
3608            if into.args.get("temporary"):
3609                table_kind = " TEMPORARY"
3610            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3611                table_kind = " UNLOGGED"
3612            else:
3613                table_kind = ""
3614            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3615
3616        return sql
3617
3618    def schema_sql(self, expression: exp.Schema) -> str:
3619        this = self.sql(expression, "this")
3620        sql = self.schema_columns_sql(expression)
3621        return f"{this} {sql}" if this and sql else this or sql
3622
3623    def schema_columns_sql(self, expression: exp.Expr) -> str:
3624        if expression.expressions:
3625            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3626        return ""
3627
3628    def star_sql(self, expression: exp.Star) -> str:
3629        except_ = self.expressions(expression, key="except_", flat=True)
3630        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3631        replace = self.expressions(expression, key="replace", flat=True)
3632        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3633        rename = self.expressions(expression, key="rename", flat=True)
3634        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3635        ilike = self.sql(expression, "ilike")
3636        ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else ""
3637        return f"*{ilike}{except_}{replace}{rename}"
3638
3639    def parameter_sql(self, expression: exp.Parameter) -> str:
3640        this = self.sql(expression, "this")
3641        return f"{self.PARAMETER_TOKEN}{this}"
3642
3643    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3644        this = self.sql(expression, "this")
3645        kind = expression.text("kind")
3646        if kind:
3647            kind = f"{kind}."
3648        return f"@@{kind}{this}"
3649
3650    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3651        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3652
3653    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3654        alias = self.sql(expression, "alias")
3655        alias = f"{sep}{alias}" if alias else ""
3656        sample = self.sql(expression, "sample")
3657        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3658            alias = f"{sample}{alias}"
3659
3660            # Set to None so it's not generated again by self.query_modifiers()
3661            expression.set("sample", None)
3662
3663        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3664        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3665        return self.prepend_ctes(expression, sql)
3666
3667    def qualify_sql(self, expression: exp.Qualify) -> str:
3668        this = self.indent(self.sql(expression, "this"))
3669        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3670
3671    def unnest_sql(self, expression: exp.Unnest) -> str:
3672        args = self.expressions(expression, flat=True)
3673
3674        alias = expression.args.get("alias")
3675        offset = expression.args.get("offset")
3676
3677        if self.UNNEST_WITH_ORDINALITY:
3678            if alias and isinstance(offset, exp.Expr):
3679                alias.append("columns", offset)
3680                expression.set("offset", None)
3681
3682        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3683            columns = alias.columns
3684            alias = self.sql(columns[0]) if columns else ""
3685        else:
3686            alias = self.sql(alias)
3687
3688        alias = f" AS {alias}" if alias else alias
3689        if self.UNNEST_WITH_ORDINALITY:
3690            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3691        else:
3692            if isinstance(offset, exp.Expr):
3693                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3694            elif offset:
3695                suffix = f"{alias} WITH OFFSET"
3696            else:
3697                suffix = alias
3698
3699        return f"UNNEST({args}){suffix}"
3700
3701    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3702        return ""
3703
3704    def where_sql(self, expression: exp.Where) -> str:
3705        this = self.indent(self.sql(expression, "this"))
3706        return f"{self.seg('WHERE')}{self.sep()}{this}"
3707
3708    def window_sql(self, expression: exp.Window) -> str:
3709        this = self.sql(expression, "this")
3710        partition = self.partition_by_sql(expression)
3711        order = expression.args.get("order")
3712        order = self.order_sql(order, flat=True) if order else ""
3713        spec = self.sql(expression, "spec")
3714        alias = self.sql(expression, "alias")
3715        over = self.sql(expression, "over") or "OVER"
3716
3717        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3718
3719        first = expression.args.get("first")
3720        if first is None:
3721            first = ""
3722        else:
3723            first = "FIRST" if first else "LAST"
3724
3725        if not partition and not order and not spec and alias:
3726            # WINDOW w2 AS w1 is unparseable in DuckDB/Postgres; parens are required.
3727            if expression.arg_key == "windows":
3728                return f"{this} ({alias})"
3729            return f"{this} {alias}"
3730
3731        args = self.format_args(
3732            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3733        )
3734        return f"{this} ({args})"
3735
3736    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3737        partition = self.expressions(expression, key="partition_by", flat=True)
3738        return f"PARTITION BY {partition}" if partition else ""
3739
3740    def partitionbyexcluding_sql(self, expression: exp.PartitionByExcluding) -> str:
3741        return f"EXCLUDING {self.expressions(expression, flat=True)}"
3742
3743    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3744        kind = self.sql(expression, "kind")
3745        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3746        end = (
3747            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3748            or "CURRENT ROW"
3749        )
3750
3751        window_spec = f"{kind} BETWEEN {start} AND {end}"
3752
3753        exclude = self.sql(expression, "exclude")
3754        if exclude:
3755            if self.SUPPORTS_WINDOW_EXCLUDE:
3756                window_spec += f" EXCLUDE {exclude}"
3757            else:
3758                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3759
3760        return window_spec
3761
3762    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3763        this = self.sql(expression, "this")
3764        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3765        return f"{this} WITHIN GROUP ({expression_sql})"
3766
3767    def between_sql(self, expression: exp.Between) -> str:
3768        this = self.sql(expression, "this")
3769        low = self.sql(expression, "low")
3770        high = self.sql(expression, "high")
3771        symmetric = expression.args.get("symmetric")
3772
3773        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3774            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3775
3776        flag = (
3777            " SYMMETRIC"
3778            if symmetric
3779            else " ASYMMETRIC"
3780            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3781            else ""  # silently drop ASYMMETRIC – semantics identical
3782        )
3783        return f"{this} BETWEEN{flag} {low} AND {high}"
3784
3785    def bracket_offset_expressions(
3786        self, expression: exp.Bracket, index_offset: int | None = None
3787    ) -> list[exp.Expr]:
3788        if expression.args.get("json_access"):
3789            return expression.expressions
3790
3791        return apply_index_offset(
3792            expression.this,
3793            expression.expressions,
3794            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3795            dialect=self.dialect,
3796        )
3797
3798    def bracket_sql(self, expression: exp.Bracket) -> str:
3799        expressions = self.bracket_offset_expressions(expression)
3800        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3801        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3802
3803    def all_sql(self, expression: exp.All) -> str:
3804        this = self.sql(expression, "this")
3805        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3806            this = self.wrap(this)
3807        return f"ALL {this}"
3808
3809    def any_sql(self, expression: exp.Any) -> str:
3810        this = self.sql(expression, "this")
3811        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3812            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3813                this = self.wrap(this)
3814            return f"ANY{this}"
3815        return f"ANY {this}"
3816
3817    def exists_sql(self, expression: exp.Exists) -> str:
3818        return f"EXISTS{self.wrap(expression)}"
3819
3820    def case_sql(self, expression: exp.Case) -> str:
3821        this = self.sql(expression, "this")
3822        statements = [f"CASE {this}" if this else "CASE"]
3823
3824        for e in expression.args["ifs"]:
3825            statements.append(f"WHEN {self.sql(e, 'this')}")
3826            statements.append(f"THEN {self.sql(e, 'true')}")
3827
3828        default = self.sql(expression, "default")
3829
3830        if default:
3831            statements.append(f"ELSE {default}")
3832
3833        statements.append("END")
3834
3835        if self.pretty and self.too_wide(statements):
3836            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3837
3838        return " ".join(statements)
3839
3840    def constraint_sql(self, expression: exp.Constraint) -> str:
3841        this = self.sql(expression, "this")
3842        expressions = self.expressions(expression, flat=True)
3843        return f"CONSTRAINT {this} {expressions}"
3844
3845    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3846        order = expression.args.get("order")
3847        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3848        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3849
3850    def extract_sql(self, expression: exp.Extract) -> str:
3851        import sqlglot.dialects.dialect
3852
3853        this = (
3854            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3855            if self.NORMALIZE_EXTRACT_DATE_PARTS
3856            else expression.this
3857        )
3858        if self.EXTRACT_ALLOWS_QUOTES:
3859            this_sql = self.sql(this)
3860        elif isinstance(this, exp.WeekStart):
3861            this_sql = self.weekstart_name(this)
3862        else:
3863            this_sql = this.name
3864        expression_sql = self.sql(expression, "expression")
3865
3866        return f"EXTRACT({this_sql} FROM {expression_sql})"
3867
3868    def trim_sql(self, expression: exp.Trim) -> str:
3869        trim_type = self.sql(expression, "position")
3870
3871        if trim_type == "LEADING":
3872            func_name = "LTRIM"
3873        elif trim_type == "TRAILING":
3874            func_name = "RTRIM"
3875        else:
3876            func_name = "TRIM"
3877
3878        return self.func(func_name, expression.this, expression.expression)
3879
3880    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3881        args = expression.expressions
3882        if isinstance(expression, exp.ConcatWs):
3883            args = args[1:]  # Skip the delimiter
3884
3885        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3886            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3887
3888        concat_coalesce = (
3889            self.dialect.CONCAT_WS_COALESCE
3890            if isinstance(expression, exp.ConcatWs)
3891            else self.dialect.CONCAT_COALESCE
3892        )
3893
3894        if not concat_coalesce and expression.args.get("coalesce"):
3895
3896            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3897                if not e.type:
3898                    import sqlglot.optimizer.annotate_types
3899
3900                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3901
3902                if e.is_string or e.is_type(exp.DType.ARRAY):
3903                    return e
3904
3905                return exp.func("coalesce", e, exp.Literal.string(""))
3906
3907            args = [_wrap_with_coalesce(e) for e in args]
3908
3909        return args
3910
3911    def concat_sql(self, expression: exp.Concat) -> str:
3912        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3913            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3914            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3915            # instead of coalescing them to empty string.
3916            import sqlglot.dialects.dialect
3917
3918            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3919
3920        expressions = self.convert_concat_args(expression)
3921
3922        # Some dialects don't allow a single-argument CONCAT call
3923        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3924            return self.sql(expressions[0])
3925
3926        return self.func("CONCAT", *expressions)
3927
3928    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3929        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3930            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3931            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3932            all_args = expression.expressions
3933            expression.set("coalesce", True)
3934            return self.sql(
3935                exp.case()
3936                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3937                .else_(expression)
3938            )
3939
3940        return self.func(
3941            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3942        )
3943
3944    def check_sql(self, expression: exp.Check) -> str:
3945        this = self.sql(expression, key="this")
3946        return f"CHECK ({this})"
3947
3948    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3949        this = self.sql(expression, "this")
3950        this = f" {this}" if this else ""
3951        expressions = self.expressions(expression, flat=True)
3952        expressions = f" ({expressions})" if expressions else ""
3953        reference = self.sql(expression, "reference")
3954        reference = f" {reference}" if reference else ""
3955        delete = self.sql(expression, "delete")
3956        delete = f" ON DELETE {delete}" if delete else ""
3957        update = self.sql(expression, "update")
3958        update = f" ON UPDATE {update}" if update else ""
3959        options = self.expressions(expression, key="options", flat=True, sep=" ")
3960        options = f" {options}" if options else ""
3961        return f"FOREIGN KEY{this}{expressions}{reference}{delete}{update}{options}"
3962
3963    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3964        this = self.sql(expression, "this")
3965        this = f" {this}" if this else ""
3966        expressions = self.expressions(expression, flat=True)
3967        include = self.sql(expression, "include")
3968        options = self.expressions(expression, key="options", flat=True, sep=" ")
3969        options = f" {options}" if options else ""
3970        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3971
3972    def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str:
3973        self.unsupported("TIMESERIES primary key columns are not supported")
3974        return self.sql(expression, "this")
3975
3976    def if_sql(self, expression: exp.If) -> str:
3977        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3978
3979    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3980        if self.MATCH_AGAINST_TABLE_PREFIX:
3981            expressions = []
3982            for expr in expression.expressions:
3983                if isinstance(expr, exp.Table):
3984                    expressions.append(f"TABLE {self.sql(expr)}")
3985                else:
3986                    expressions.append(expr)
3987        else:
3988            expressions = expression.expressions
3989
3990        modifier = expression.args.get("modifier")
3991        modifier = f" {modifier}" if modifier else ""
3992        return (
3993            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3994        )
3995
3996    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3997        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3998
3999    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
4000        path = self.expressions(expression, sep="", flat=True).lstrip(".")
4001
4002        if self.QUOTE_JSON_PATH:
4003            path = self.escape_str(path)
4004            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
4005
4006        return path
4007
4008    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
4009        if isinstance(expression, exp.JSONPathPart):
4010            transform = self.TRANSFORMS.get(expression.__class__)
4011            if not callable(transform):
4012                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
4013                return ""
4014
4015            return transform(self, expression)
4016
4017        if isinstance(expression, int):
4018            return str(expression)
4019
4020        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
4021            escaped = expression.replace("'", "\\'")
4022            escaped = f"'{escaped}'"
4023        else:
4024            escaped = expression.replace('"', '\\"')
4025            escaped = f'"{escaped}"'
4026
4027        return escaped
4028
4029    def formatjson_sql(self, expression: exp.FormatJson) -> str:
4030        return f"{self.sql(expression, 'this')} FORMAT JSON"
4031
4032    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
4033        # Output the Teradata column FORMAT override.
4034        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
4035        this = self.sql(expression, "this")
4036        fmt = self.sql(expression, "format")
4037        return f"{this} (FORMAT {fmt})"
4038
4039    def _jsonobject_sql(
4040        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
4041    ) -> str:
4042        null_handling = expression.args.get("null_handling")
4043        null_handling = f" {null_handling}" if null_handling else ""
4044
4045        unique_keys = expression.args.get("unique_keys")
4046        if unique_keys is not None:
4047            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
4048        else:
4049            unique_keys = ""
4050
4051        return_type = self.sql(expression, "return_type")
4052        return_type = f" RETURNING {return_type}" if return_type else ""
4053        encoding = self.sql(expression, "encoding")
4054        encoding = f" ENCODING {encoding}" if encoding else ""
4055
4056        if not name:
4057            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
4058
4059        return self.func(
4060            name,
4061            *expression.expressions,
4062            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
4063        )
4064
4065    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
4066        null_handling = expression.args.get("null_handling")
4067        null_handling = f" {null_handling}" if null_handling else ""
4068        return_type = self.sql(expression, "return_type")
4069        return_type = f" RETURNING {return_type}" if return_type else ""
4070        strict = " STRICT" if expression.args.get("strict") else ""
4071        return self.func(
4072            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
4073        )
4074
4075    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
4076        this = self.sql(expression, "this")
4077        order = self.sql(expression, "order")
4078        null_handling = expression.args.get("null_handling")
4079        null_handling = f" {null_handling}" if null_handling else ""
4080        return_type = self.sql(expression, "return_type")
4081        return_type = f" RETURNING {return_type}" if return_type else ""
4082        strict = " STRICT" if expression.args.get("strict") else ""
4083        return self.func(
4084            "JSON_ARRAYAGG",
4085            this,
4086            suffix=f"{order}{null_handling}{return_type}{strict})",
4087        )
4088
4089    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
4090        path = self.sql(expression, "path")
4091        path = f" PATH {path}" if path else ""
4092        nested_schema = self.sql(expression, "nested_schema")
4093
4094        if nested_schema:
4095            return f"NESTED{path} {nested_schema}"
4096
4097        this = self.sql(expression, "this")
4098        kind = self.sql(expression, "kind")
4099        kind = f" {kind}" if kind else ""
4100        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
4101
4102        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
4103        return f"{this}{kind}{format_json}{path}{ordinality}"
4104
4105    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
4106        return self.func("COLUMNS", *expression.expressions)
4107
4108    def jsontable_sql(self, expression: exp.JSONTable) -> str:
4109        this = self.sql(expression, "this")
4110        path = self.sql(expression, "path")
4111        path = f", {path}" if path else ""
4112        error_handling = expression.args.get("error_handling")
4113        error_handling = f" {error_handling}" if error_handling else ""
4114        empty_handling = expression.args.get("empty_handling")
4115        empty_handling = f" {empty_handling}" if empty_handling else ""
4116        schema = self.sql(expression, "schema")
4117        return self.func(
4118            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
4119        )
4120
4121    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
4122        this = self.sql(expression, "this")
4123        kind = self.sql(expression, "kind")
4124        path = self.sql(expression, "path")
4125        path = f" {path}" if path else ""
4126        as_json = " AS JSON" if expression.args.get("as_json") else ""
4127        return f"{this} {kind}{path}{as_json}"
4128
4129    def openjson_sql(self, expression: exp.OpenJSON) -> str:
4130        this = self.sql(expression, "this")
4131        path = self.sql(expression, "path")
4132        path = f", {path}" if path else ""
4133        expressions = self.expressions(expression)
4134        with_ = (
4135            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
4136            if expressions
4137            else ""
4138        )
4139        return f"OPENJSON({this}{path}){with_}"
4140
4141    def in_sql(self, expression: exp.In) -> str:
4142        query = expression.args.get("query")
4143        unnest = expression.args.get("unnest")
4144        field = expression.args.get("field")
4145        is_global = " GLOBAL" if expression.args.get("is_global") else ""
4146
4147        if query:
4148            in_sql = self.sql(query)
4149        elif unnest:
4150            in_sql = self.in_unnest_op(unnest)
4151        elif field:
4152            in_sql = self.sql(field)
4153        else:
4154            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
4155
4156        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
4157
4158    def in_unnest_op(self, unnest: exp.Unnest) -> str:
4159        return f"(SELECT {self.sql(unnest)})"
4160
4161    def interval_sql(self, expression: exp.Interval) -> str:
4162        include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance(
4163            expression.find_ancestor(exp.AutoRefreshProperty, exp.Select),
4164            exp.AutoRefreshProperty,
4165        )
4166        interval_keyword = "INTERVAL" if include_keyword else ""
4167        unit_expression = expression.args.get("unit")
4168        unit = self.sql(unit_expression) if unit_expression else ""
4169        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
4170            unit = self.TIME_PART_SINGULARS.get(unit, unit)
4171        unit = f" {unit}" if unit else ""
4172
4173        if self.SINGLE_STRING_INTERVAL:
4174            this = expression.this.name if expression.this else ""
4175            if this:
4176                interval_keyword = f"{interval_keyword} " if interval_keyword else ""
4177                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
4178                    return f"{interval_keyword}{self.sql(exp.Literal.string(this))}{unit}"
4179                return f"{interval_keyword}{self.sql(exp.Literal.string(f'{this}{unit}'))}"
4180            return f"{interval_keyword}{unit}"
4181
4182        this = self.sql(expression, "this")
4183        if this:
4184            if not include_keyword and expression.this.is_string:
4185                this = expression.this.name
4186            if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES):
4187                this = f"({this})"
4188            if include_keyword:
4189                this = f" {this}"
4190
4191        return f"{interval_keyword}{this}{unit}"
4192
4193    def return_sql(self, expression: exp.Return) -> str:
4194        return f"RETURN {self.sql(expression, 'this')}"
4195
4196    def reference_sql(self, expression: exp.Reference) -> str:
4197        this = self.sql(expression, "this")
4198        expressions = self.expressions(expression, flat=True)
4199        expressions = f"({expressions})" if expressions else ""
4200        options = self.expressions(expression, key="options", flat=True, sep=" ")
4201        options = f" {options}" if options else ""
4202        return f"REFERENCES {this}{expressions}{options}"
4203
4204    def anonymous_sql(self, expression: exp.Anonymous) -> str:
4205        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
4206        parent = expression.parent
4207        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
4208
4209        return self.func(
4210            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
4211        )
4212
4213    def paren_sql(self, expression: exp.Paren) -> str:
4214        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
4215        return f"({sql}{self.seg(')', sep='')}"
4216
4217    def neg_sql(self, expression: exp.Neg) -> str:
4218        # This makes sure we don't convert "- - 5" to "--5", which is a comment
4219        this_sql = self.sql(expression, "this")
4220        sep = " " if this_sql[0] == "-" else ""
4221        return f"-{sep}{this_sql}"
4222
4223    def not_sql(self, expression: exp.Not) -> str:
4224        return f"NOT {self.sql(expression, 'this')}"
4225
4226    def alias_sql(self, expression: exp.Alias) -> str:
4227        alias = self.sql(expression, "alias")
4228        alias = f" AS {alias}" if alias else ""
4229        return f"{self.sql(expression, 'this')}{alias}"
4230
4231    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
4232        alias = expression.args["alias"]
4233
4234        parent = expression.parent
4235        pivot = parent and parent.parent
4236
4237        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
4238            identifier_alias = isinstance(alias, exp.Identifier)
4239            literal_alias = isinstance(alias, exp.Literal)
4240
4241            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4242                alias.replace(exp.Literal.string(alias.output_name))
4243            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4244                alias.replace(exp.to_identifier(alias.output_name))
4245
4246        return self.alias_sql(expression)
4247
4248    def aliases_sql(self, expression: exp.Aliases) -> str:
4249        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
4250
4251    def atindex_sql(self, expression: exp.AtIndex) -> str:
4252        this = self.sql(expression, "this")
4253        index = self.sql(expression, "expression")
4254        return f"{this} AT {index}"
4255
4256    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
4257        this = self.sql(expression, "this")
4258        zone = self.sql(expression, "zone")
4259        return f"{this} AT TIME ZONE {zone}"
4260
4261    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
4262        this = self.sql(expression, "this")
4263        zone = self.sql(expression, "zone")
4264        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
4265
4266    def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str:
4267        return self.sql(exp.cast(expression.this, exp.DType.DATE))
4268
4269    def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:
4270        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4271
4272    def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str:
4273        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4274
4275    def add_sql(self, expression: exp.Add) -> str:
4276        return self.binary(expression, "+")
4277
4278    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
4279        return self.connector_sql(expression, "AND", stack)
4280
4281    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
4282        return self.connector_sql(expression, "OR", stack)
4283
4284    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
4285        return self.connector_sql(expression, "XOR", stack)
4286
4287    def connector_sql(
4288        self,
4289        expression: exp.Connector,
4290        op: str,
4291        stack: list[str | exp.Expr] | None = None,
4292    ) -> str:
4293        if stack is not None:
4294            stack.append(expression.right)
4295            if expression.comments and self.comments:
4296                op = self.maybe_comment(op, comments=expression.comments)
4297
4298            stack.extend((op, expression.left))
4299            return op
4300
4301        stack = [expression]
4302        sqls: list[str] = []
4303        ops = set()
4304
4305        while stack:
4306            node = stack.pop()
4307            if isinstance(node, exp.Connector):
4308                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4309            else:
4310                sql = self.sql(node)
4311                if sqls and sqls[-1] in ops:
4312                    sqls[-1] += f" {sql}"
4313                else:
4314                    sqls.append(sql)
4315
4316        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4317        return sep.join(sqls)
4318
4319    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4320        return self.binary(expression, "&")
4321
4322    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4323        return self.binary(expression, "<<")
4324
4325    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4326        return f"~{self.sql(expression, 'this')}"
4327
4328    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4329        return self.binary(expression, "|")
4330
4331    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4332        return self.binary(expression, ">>")
4333
4334    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4335        return self.binary(expression, "^")
4336
4337    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4338        format_sql = self.sql(expression, "format")
4339        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4340        to_sql = self.sql(expression, "to")
4341        to_sql = f" {to_sql}" if to_sql else ""
4342        action = self.sql(expression, "action")
4343        action = f" {action}" if action else ""
4344        default = self.sql(expression, "default")
4345        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4346        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4347
4348    # Base implementation that excludes safe, zone, and target_type metadata args
4349    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4350        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
4351
4352    # Base implementation that excludes the safe and default_year metadata args
4353    def strtodate_sql(self, expression: exp.StrToDate) -> str:
4354        return self.func("STR_TO_DATE", expression.this, expression.args.get("format"))
4355
4356    def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str:
4357        return self.func(
4358            "PARSE_DATETIME",
4359            expression.this,
4360            expression.args.get("format"),
4361            expression.args.get("zone"),
4362        )
4363
4364    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4365        zone = self.sql(expression, "this")
4366        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
4367
4368    def collate_sql(self, expression: exp.Collate) -> str:
4369        if self.COLLATE_IS_FUNC:
4370            return self.function_fallback_sql(expression)
4371        return self.binary(expression, "COLLATE")
4372
4373    def command_sql(self, expression: exp.Command) -> str:
4374        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
4375
4376    def comment_sql(self, expression: exp.Comment) -> str:
4377        this = self.sql(expression, "this")
4378        kind = expression.args["kind"]
4379        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4380        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4381        expression_sql = self.sql(expression, "expression")
4382        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4383
4384    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4385        this = self.sql(expression, "this")
4386        delete = " DELETE" if expression.args.get("delete") else ""
4387        recompress = self.sql(expression, "recompress")
4388        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4389        to_disk = self.sql(expression, "to_disk")
4390        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4391        to_volume = self.sql(expression, "to_volume")
4392        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4393        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4394
4395    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4396        where = self.sql(expression, "where")
4397        group = self.sql(expression, "group")
4398        aggregates = self.expressions(expression, key="aggregates")
4399        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4400
4401        if not (where or group or aggregates) and len(expression.expressions) == 1:
4402            return f"TTL {self.expressions(expression, flat=True)}"
4403
4404        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4405
4406    def transaction_sql(self, expression: exp.Transaction) -> str:
4407        modes = self.expressions(expression, key="modes")
4408        modes = f" {modes}" if modes else ""
4409        return f"BEGIN{modes}"
4410
4411    def commit_sql(self, expression: exp.Commit) -> str:
4412        chain = expression.args.get("chain")
4413        if chain is not None:
4414            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4415
4416        return f"COMMIT{chain or ''}"
4417
4418    def rollback_sql(self, expression: exp.Rollback) -> str:
4419        savepoint = expression.args.get("savepoint")
4420        savepoint = f" TO {savepoint}" if savepoint else ""
4421        return f"ROLLBACK{savepoint}"
4422
4423    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4424        this = self.sql(expression, "this")
4425
4426        exists = ""
4427        if expression.args.get("exists"):
4428            if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS:
4429                exists = " IF EXISTS"
4430            else:
4431                self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect")
4432
4433        dtype = self.sql(expression, "dtype")
4434        if dtype:
4435            collate = self.sql(expression, "collate")
4436            collate = f" COLLATE {collate}" if collate else ""
4437            using = self.sql(expression, "using")
4438            using = f" USING {using}" if using else ""
4439            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4440            null_constraint = self._alter_column_null_constraint_sql(expression)
4441
4442            return (
4443                f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}"
4444                f"{collate}{using}{null_constraint}"
4445            )
4446
4447        default = self.sql(expression, "default")
4448        if default:
4449            return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}"
4450
4451        comment = self.sql(expression, "comment")
4452        if comment:
4453            return f"ALTER COLUMN{exists} {this} COMMENT {comment}"
4454
4455        visible = expression.args.get("visible")
4456        if visible:
4457            return f"ALTER COLUMN{exists} {this} SET {visible}"
4458
4459        allow_null = expression.args.get("allow_null")
4460        drop = expression.args.get("drop")
4461
4462        if not drop and not allow_null:
4463            self.unsupported("Unsupported ALTER COLUMN syntax")
4464
4465        if allow_null is not None:
4466            keyword = "DROP" if drop else "SET"
4467            return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL"
4468
4469        return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4470
4471    def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str:
4472        allow_null = expression.args.get("allow_null")
4473        if allow_null is None:
4474            return ""
4475
4476        if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY:
4477            self.unsupported("ALTER COLUMN cannot set nullability along with a type")
4478            return ""
4479
4480        return " NULL" if allow_null else " NOT NULL"
4481
4482    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4483        this = self.sql(expression, "this")
4484        rename_from = self.sql(expression, "rename_from")
4485        if rename_from:
4486            if not self.SUPPORTS_CHANGE_COLUMN:
4487                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4488            return f"CHANGE COLUMN {rename_from} {this}"
4489        if not self.SUPPORTS_MODIFY_COLUMN:
4490            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4491        return f"MODIFY COLUMN {this}"
4492
4493    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4494        this = self.sql(expression, "this")
4495
4496        visible = expression.args.get("visible")
4497        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4498
4499        return f"ALTER INDEX {this} {visible_sql}"
4500
4501    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4502        this = self.sql(expression, "this")
4503        if not isinstance(expression.this, exp.Var):
4504            this = f"KEY DISTKEY {this}"
4505        return f"ALTER DISTSTYLE {this}"
4506
4507    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4508        compound = " COMPOUND" if expression.args.get("compound") else ""
4509        this = self.sql(expression, "this")
4510        expressions = self.expressions(expression, flat=True)
4511        expressions = f"({expressions})" if expressions else ""
4512        return f"ALTER{compound} SORTKEY {this or expressions}"
4513
4514    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4515        if not self.RENAME_TABLE_WITH_DB:
4516            # Remove db from tables
4517            expression = expression.transform(
4518                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4519            ).assert_is(exp.AlterRename)
4520        this = self.sql(expression, "this")
4521        to_kw = " TO" if include_to else ""
4522        return f"RENAME{to_kw} {this}"
4523
4524    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4525        exists = " IF EXISTS" if expression.args.get("exists") else ""
4526        old_column = self.sql(expression, "this")
4527        new_column = self.sql(expression, "to")
4528        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
4529
4530    def alterset_sql(self, expression: exp.AlterSet) -> str:
4531        exprs = self.expressions(expression, flat=True)
4532        if self.ALTER_SET_WRAPPED:
4533            exprs = f"({exprs})"
4534
4535        return f"SET {exprs}"
4536
4537    def alter_sql(self, expression: exp.Alter) -> str:
4538        actions = expression.actions
4539
4540        if (
4541            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
4542            and actions
4543            and isinstance(actions[0], exp.ColumnDef)
4544        ):
4545            actions_sql = self.expressions(expression, key="actions", flat=True)
4546            actions_sql = f"ADD {actions_sql}"
4547        else:
4548            actions_list = []
4549            for action in actions:
4550                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4551                    action_sql = self.add_column_sql(action)
4552                else:
4553                    action_sql = self.sql(action)
4554                    if isinstance(action, exp.Query):
4555                        action_sql = f"AS {action_sql}"
4556
4557                actions_list.append(action_sql)
4558
4559            actions_sql = self.format_args(*actions_list).lstrip("\n")
4560
4561        iceberg = (
4562            "ICEBERG "
4563            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4564            else ""
4565        )
4566        exists = " IF EXISTS" if expression.args.get("exists") else ""
4567        on_cluster = self.sql(expression, "cluster")
4568        on_cluster = f" {on_cluster}" if on_cluster else ""
4569        only = " ONLY" if expression.args.get("only") else ""
4570        options = expression.args.get("options") or []
4571        options_sql = self.expressions(
4572            sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)]
4573        )
4574        options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql
4575        partitions_sql = self.expressions(
4576            sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)],
4577            sep=" ",
4578            flat=True,
4579        )
4580        if partitions_sql and (actions_sql or options_sql):
4581            partitions_sql = f" {partitions_sql}"
4582        kind = self.sql(expression, "kind")
4583        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4584        check = " WITH CHECK" if expression.args.get("check") else ""
4585        cascade = (
4586            " CASCADE"
4587            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4588            else ""
4589        )
4590        this = self.sql(expression, "this")
4591        this = f" {this}" if this else ""
4592
4593        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}"
4594
4595    def altersession_sql(self, expression: exp.AlterSession) -> str:
4596        items_sql = self.expressions(expression, flat=True)
4597        keyword = "UNSET" if expression.args.get("unset") else "SET"
4598        return f"{keyword} {items_sql}"
4599
4600    def add_column_sql(self, expression: exp.Expr) -> str:
4601        column_text = ""
4602        if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4603            if isinstance(expression, exp.ColumnDef):
4604                column_text = " COLUMN"
4605            elif isinstance(expression, exp.Schema):
4606                column_text = " COLUMNS"
4607
4608        if isinstance(expression, exp.Schema) and not self.ALTER_TABLE_ADD_COLUMNS_WRAPPED:
4609            sql = self.expressions(expression, flat=True)
4610        else:
4611            sql = self.sql(expression)
4612
4613        return f"ADD{column_text} {sql}"
4614
4615    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4616        expressions = self.expressions(expression)
4617        exists = " IF EXISTS " if expression.args.get("exists") else " "
4618        return f"DROP{exists}{expressions}"
4619
4620    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4621        return "DROP PRIMARY KEY"
4622
4623    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4624        return f"ADD {self.expressions(expression, indent=False)}"
4625
4626    def replacecolumns_sql(self, expression: exp.ReplaceColumns) -> str:
4627        return f"REPLACE COLUMNS ({self.expressions(expression, flat=True)})"
4628
4629    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4630        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4631        location = self.sql(expression, "location")
4632        location = f" {location}" if location else ""
4633        return f"ADD {exists}{self.sql(expression.this)}{location}"
4634
4635    def distinct_sql(self, expression: exp.Distinct) -> str:
4636        this = self.expressions(expression, flat=True)
4637
4638        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4639            case = exp.case()
4640            for arg in expression.expressions:
4641                case = case.when(arg.is_(exp.null()), exp.null())
4642            this = self.sql(case.else_(f"({this})"))
4643
4644        this = f" {this}" if this else ""
4645
4646        on = self.sql(expression, "on")
4647        on = f" ON {on}" if on else ""
4648        return f"DISTINCT{this}{on}"
4649
4650    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4651        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4652
4653    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4654        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4655
4656    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4657        this_sql = self.sql(expression, "this")
4658        expression_sql = self.sql(expression, "expression")
4659        kind = "MAX" if expression.args.get("max") else "MIN"
4660        return f"{this_sql} HAVING {kind} {expression_sql}"
4661
4662    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4663        return self._truncated_div_sql(expression.this, expression.expression)
4664
4665    def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str:
4666        div: exp.Expr = exp.Div(this=this, expression=divisor)
4667        if self.CAST_TO_INT_ROUNDS:
4668            div = exp.Trunc(this=div)
4669
4670        return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT)))
4671
4672    def dpipe_sql(self, expression: exp.DPipe) -> str:
4673        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4674            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4675        return self.binary(expression, "||")
4676
4677    def div_sql(self, expression: exp.Div) -> str:
4678        l, r = expression.left, expression.right
4679
4680        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4681            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4682
4683        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4684            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4685                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4686
4687        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4688            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4689                # Native integer division depends on the target's operand types, which may differ
4690                # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are
4691                # integer literals or casts
4692                if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and (
4693                    expression.args.get("safe") or not self.SAFE_INT_DIVISION
4694                ):
4695                    return self.sql(exp.IntDiv(this=l, expression=r))
4696                return self._truncated_div_sql(l, r)
4697
4698        return self.binary(expression, "/")
4699
4700    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4701        n = exp._wrap(expression.this, exp.Binary)
4702        d = exp._wrap(expression.expression, exp.Binary)
4703        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4704
4705    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4706        return self.binary(expression, "OVERLAPS")
4707
4708    def distance_sql(self, expression: exp.Distance) -> str:
4709        return self.binary(expression, "<->")
4710
4711    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4712        return self.binary(expression, "<<->>")
4713
4714    def dot_sql(self, expression: exp.Dot) -> str:
4715        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4716
4717    def eq_sql(self, expression: exp.EQ) -> str:
4718        return self.binary(expression, "=")
4719
4720    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4721        return self.binary(expression, ":=")
4722
4723    def escape_sql(self, expression: exp.Escape) -> str:
4724        this = expression.this
4725        if (
4726            isinstance(this, (exp.Like, exp.ILike))
4727            and isinstance(this.expression, (exp.All, exp.Any))
4728            and not self.SUPPORTS_LIKE_QUANTIFIERS
4729        ):
4730            return self._like_sql(this, escape=expression)
4731        return self.binary(expression, "ESCAPE")
4732
4733    def glob_sql(self, expression: exp.Glob) -> str:
4734        return self.binary(expression, "GLOB")
4735
4736    def gt_sql(self, expression: exp.GT) -> str:
4737        return self.binary(expression, ">")
4738
4739    def gte_sql(self, expression: exp.GTE) -> str:
4740        return self.binary(expression, ">=")
4741
4742    def is_sql(self, expression: exp.Is) -> str:
4743        negate = expression.args.get("negate")
4744        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4745            positive = bool(expression.expression.this) != bool(negate)
4746            return self.sql(expression.this if positive else exp.not_(expression.this))
4747        return self.binary(expression, "IS NOT" if negate else "IS")
4748
4749    def _like_sql(
4750        self,
4751        expression: exp.Like | exp.ILike,
4752        escape: exp.Escape | None = None,
4753    ) -> str:
4754        this = expression.this
4755        rhs = expression.expression
4756
4757        if isinstance(expression, exp.Like):
4758            exp_class: type[exp.Like | exp.ILike] = exp.Like
4759            op = "LIKE"
4760        else:
4761            exp_class = exp.ILike
4762            op = "ILIKE"
4763
4764        if expression.args.get("negate"):
4765            op = f"NOT {op}"
4766
4767        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4768            exprs = rhs.this.unnest()
4769
4770            if isinstance(exprs, exp.Tuple):
4771                exprs = exprs.expressions
4772            else:
4773                exprs = [exprs]
4774
4775            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4776
4777            def _make_like(expr: exp.Expression) -> exp.Expression:
4778                like: exp.Expression = exp_class(
4779                    this=this, expression=expr, negate=expression.args.get("negate")
4780                )
4781                if escape:
4782                    like = exp.Escape(this=like, expression=escape.expression.copy())
4783                return like
4784
4785            like_expr: exp.Expr = _make_like(exprs[0])
4786            for expr in exprs[1:]:
4787                like_expr = connective(like_expr, _make_like(expr), copy=False)
4788
4789            parent = escape.parent if escape else expression.parent
4790            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4791                parent, exp.Condition
4792            ):
4793                like_expr = exp.paren(like_expr, copy=False)
4794
4795            return self.sql(like_expr)
4796
4797        return self.binary(expression, op)
4798
4799    def like_sql(self, expression: exp.Like) -> str:
4800        return self._like_sql(expression)
4801
4802    def ilike_sql(self, expression: exp.ILike) -> str:
4803        return self._like_sql(expression)
4804
4805    def match_sql(self, expression: exp.Match) -> str:
4806        if expression.args.get("kind") or expression.args.get("analyzer"):
4807            self.unsupported("MATCH variants and USING ANALYZER are not supported in this dialect")
4808        return self.binary(expression, "MATCH")
4809
4810    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4811        return self.binary(expression, "SIMILAR TO")
4812
4813    def lt_sql(self, expression: exp.LT) -> str:
4814        return self.binary(expression, "<")
4815
4816    def lte_sql(self, expression: exp.LTE) -> str:
4817        return self.binary(expression, "<=")
4818
4819    def mod_sql(self, expression: exp.Mod) -> str:
4820        this = self.sql(expression, "this")
4821        expr = self.sql(expression, "expression")
4822        sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}"
4823
4824        parent = expression.parent
4825        if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression:
4826            return f"({sql})"
4827
4828        return sql
4829
4830    def mul_sql(self, expression: exp.Mul) -> str:
4831        return self.binary(expression, "*")
4832
4833    def neq_sql(self, expression: exp.NEQ) -> str:
4834        return self.binary(expression, "<>")
4835
4836    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4837        return self.binary(expression, "IS NOT DISTINCT FROM")
4838
4839    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4840        return self.binary(expression, "IS DISTINCT FROM")
4841
4842    def sub_sql(self, expression: exp.Sub) -> str:
4843        return self.binary(expression, "-")
4844
4845    def trycast_sql(self, expression: exp.TryCast) -> str:
4846        return self.cast_sql(expression, safe_prefix="TRY_")
4847
4848    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4849        return self.cast_sql(expression)
4850
4851    def try_sql(self, expression: exp.Try) -> str:
4852        if not self.TRY_SUPPORTED:
4853            self.unsupported("Unsupported TRY function")
4854            return self.sql(expression, "this")
4855
4856        return self.func("TRY", expression.this)
4857
4858    def log_sql(self, expression: exp.Log) -> str:
4859        this = expression.this
4860        expr = expression.expression
4861
4862        if self.dialect.LOG_BASE_FIRST is False:
4863            this, expr = expr, this
4864        elif self.dialect.LOG_BASE_FIRST is None and expr:
4865            if this.name in ("2", "10"):
4866                return self.func(f"LOG{this.name}", expr)
4867
4868            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4869
4870        return self.func("LOG", this, expr)
4871
4872    def use_sql(self, expression: exp.Use) -> str:
4873        kind = self.sql(expression, "kind")
4874        kind = f" {kind}" if kind else ""
4875        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4876        this = f" {this}" if this else ""
4877        return f"USE{kind}{this}"
4878
4879    def binary(self, expression: exp.Binary, op: str) -> str:
4880        sqls: list[str] = []
4881        stack: list[None | str | exp.Expr] = [expression]
4882        binary_type = type(expression)
4883        is_is = binary_type is exp.Is and op in ("IS", "IS NOT")
4884        negate = expression.args.get("negate")
4885
4886        while stack:
4887            node = stack.pop()
4888
4889            if type(node) is binary_type and (
4890                node is expression
4891                or (is_is and not isinstance(node.expression, exp.Boolean))
4892                or node.args.get("negate") == negate
4893            ):
4894                op_func = node.args.get("operator")
4895                if op_func:
4896                    op = f"OPERATOR({self.sql(op_func)})"
4897                elif is_is:
4898                    op = "IS NOT" if node.args.get("negate") else "IS"
4899
4900                stack.append(node.args.get("expression"))
4901                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4902                stack.append(node.args.get("this"))
4903            else:
4904                sqls.append(self.sql(node))
4905
4906        return "".join(sqls)
4907
4908    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4909        to_clause = self.sql(expression, "to")
4910        if to_clause:
4911            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4912
4913        return self.function_fallback_sql(expression)
4914
4915    def function_fallback_sql(self, expression: exp.Func) -> str:
4916        args = []
4917
4918        for key in expression.arg_types:
4919            arg_value = expression.args.get(key)
4920
4921            if isinstance(arg_value, list):
4922                for value in arg_value:
4923                    args.append(value)
4924            elif arg_value is not None:
4925                args.append(arg_value)
4926
4927        return self.func(self.function_name(expression), *args)
4928
4929    def function_name(self, expression: exp.Func) -> str:
4930        name_key = self.dialect.ORIGINAL_NAME_META_KEY
4931        return (expression.meta_get(name_key) if name_key else None) or expression.sql_name()
4932
4933    def func(
4934        self,
4935        name: str,
4936        *args: t.Any,
4937        prefix: str = "(",
4938        suffix: str = ")",
4939        normalize: bool = True,
4940    ) -> str:
4941        name = self.normalize_func(name) if normalize else name
4942        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4943
4944    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4945        arg_sqls = tuple(
4946            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4947        )
4948        if self.pretty and self.too_wide(arg_sqls):
4949            return self.indent(
4950                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4951            )
4952        return sep.join(arg_sqls)
4953
4954    def too_wide(self, args: t.Iterable) -> bool:
4955        return sum(len(arg) for arg in args) > self.max_text_width
4956
4957    def format_time(
4958        self,
4959        expression: exp.Expr,
4960        inverse_time_mapping: dict[str, str] | None = None,
4961        inverse_time_trie: dict | None = None,
4962    ) -> str | None:
4963        return format_time(
4964            self.sql(expression, "format"),
4965            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4966            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4967        )
4968
4969    def expressions(
4970        self,
4971        expression: exp.Expr | None = None,
4972        key: str | None = None,
4973        sqls: t.Collection[str | exp.Expr] | None = None,
4974        flat: bool = False,
4975        indent: bool = True,
4976        skip_first: bool = False,
4977        skip_last: bool = False,
4978        sep: str = ", ",
4979        prefix: str = "",
4980        dynamic: bool = False,
4981        new_line: bool = False,
4982    ) -> str:
4983        expressions = expression.args.get(key or "expressions") if expression else sqls
4984
4985        if not expressions:
4986            return ""
4987
4988        if flat:
4989            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4990
4991        num_sqls = len(expressions)
4992        result_sqls = []
4993
4994        for i, e in enumerate(expressions):
4995            sql = self.sql(e, comment=False)
4996            if not sql:
4997                continue
4998
4999            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
5000
5001            if self.pretty:
5002                if self.leading_comma:
5003                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
5004                else:
5005                    result_sqls.append(
5006                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
5007                    )
5008            else:
5009                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
5010
5011        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
5012            if new_line:
5013                result_sqls.insert(0, "")
5014                result_sqls.append("")
5015            result_sql = "\n".join(s.rstrip() for s in result_sqls)
5016        else:
5017            result_sql = "".join(result_sqls)
5018
5019        return (
5020            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
5021            if indent
5022            else result_sql
5023        )
5024
5025    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
5026        flat = flat or isinstance(expression.parent, exp.Properties)
5027        expressions_sql = self.expressions(expression, flat=flat)
5028        if flat:
5029            return f"{op} {expressions_sql}"
5030        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
5031
5032    def naked_property(self, expression: exp.Property) -> str:
5033        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
5034        if not property_name:
5035            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
5036        return f"{property_name} {self.sql(expression, 'this')}"
5037
5038    def tag_sql(self, expression: exp.Tag) -> str:
5039        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
5040
5041    def token_sql(self, token_type: TokenType) -> str:
5042        return self.TOKEN_MAPPING.get(token_type, token_type.name)
5043
5044    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
5045        this = self.sql(expression, "this")
5046        expressions = self.no_identify(self.expressions, expression)
5047        expressions = (
5048            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
5049        )
5050        return f"{this}{expressions}" if expressions.strip() != "" else this
5051
5052    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
5053        return self.expressions(expression, flat=True)
5054
5055    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
5056        params = self.no_identify(self.expressions, expression, flat=True)
5057        body = self.sql(expression, "this")
5058        prefix = "TABLE " if expression.args.get("is_table") else ""
5059        return f"({params}) AS {prefix}{body}"
5060
5061    def joinhint_sql(self, expression: exp.JoinHint) -> str:
5062        this = self.sql(expression, "this")
5063        expressions = self.expressions(expression, flat=True)
5064        return f"{this}({expressions})"
5065
5066    def kwarg_sql(self, expression: exp.Kwarg) -> str:
5067        return self.binary(expression, "=>")
5068
5069    def when_sql(self, expression: exp.When) -> str:
5070        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
5071        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
5072        condition = self.sql(expression, "condition")
5073        condition = f" AND {condition}" if condition else ""
5074
5075        then_expression = expression.args.get("then")
5076        if isinstance(then_expression, exp.Insert):
5077            this = (
5078                "ALL BY NAME"
5079                if self.MERGE_ALL_BY_NAME and isinstance(then_expression.this, exp.Star)
5080                else self.sql(then_expression, "this")
5081            )
5082            this = f"INSERT {this}" if this else "INSERT"
5083            then = self.sql(then_expression, "expression")
5084            then = f"{this} VALUES {then}" if then else this
5085        elif isinstance(then_expression, exp.Update):
5086            if isinstance(then_expression.args.get("expressions"), exp.Star):
5087                then = (
5088                    "UPDATE ALL BY NAME"
5089                    if self.MERGE_ALL_BY_NAME
5090                    else f"UPDATE {self.sql(then_expression, 'expressions')}"
5091                )
5092            elif (
5093                self.MERGE_ALL_BY_NAME
5094                and len(then_expression.expressions) == 1
5095                and isinstance(then_expression.expressions[0], exp.Star)
5096            ):
5097                then = "UPDATE ALL BY NAME"
5098            else:
5099                expressions_sql = self.expressions(then_expression)
5100                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
5101        else:
5102            then = self.sql(then_expression)
5103
5104        if isinstance(then_expression, (exp.Insert, exp.Update)):
5105            where = self.sql(then_expression, "where")
5106            if where and not self.SUPPORTS_MERGE_WHERE:
5107                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
5108                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
5109                where = ""
5110            then = f"{then}{where}"
5111        return f"WHEN {matched}{source}{condition} THEN {then}"
5112
5113    def whens_sql(self, expression: exp.Whens) -> str:
5114        return self.expressions(expression, sep=" ", indent=False)
5115
5116    def merge_sql(self, expression: exp.Merge) -> str:
5117        table = expression.this
5118        table_alias = ""
5119
5120        hints = table.args.get("hints")
5121        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
5122            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
5123            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
5124
5125        this = self.sql(table)
5126        using = f"USING {self.sql(expression, 'using')}"
5127        whens = self.sql(expression, "whens")
5128
5129        on = self.sql(expression, "on")
5130        on = f"ON {on}" if on else ""
5131
5132        if not on:
5133            on = self.expressions(expression, key="using_cond")
5134            on = f"USING ({on})" if on else ""
5135
5136        returning = self.sql(expression, "returning")
5137        if returning:
5138            whens = f"{whens}{returning}"
5139
5140        sep = self.sep()
5141
5142        return self.prepend_ctes(
5143            expression,
5144            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
5145        )
5146
5147    @unsupported_args("format")
5148    def tochar_sql(self, expression: exp.ToChar) -> str:
5149        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
5150
5151    @unsupported_args("default")
5152    def tonumber_sql(self, expression: exp.ToNumber) -> str:
5153        if not self.SUPPORTS_TO_NUMBER:
5154            self.unsupported("Unsupported TO_NUMBER function")
5155            return self._tonumber_cast_sql(expression)
5156
5157        fmt = expression.args.get("format")
5158        if not fmt:
5159            self.unsupported("Conversion format is required for TO_NUMBER")
5160            return self._tonumber_cast_sql(expression)
5161
5162        return self.func("TO_NUMBER", expression.this, fmt)
5163
5164    def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str:
5165        if expression.args.get("safe"):
5166            return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE")))
5167        return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
5168
5169    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
5170        this = self.sql(expression, "this")
5171        kind = self.sql(expression, "kind")
5172        settings_sql = self.expressions(expression, key="settings", sep=" ")
5173        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
5174        return f"{this}({kind}{args})"
5175
5176    def dictrange_sql(self, expression: exp.DictRange) -> str:
5177        this = self.sql(expression, "this")
5178        max = self.sql(expression, "max")
5179        min = self.sql(expression, "min")
5180        return f"{this}(MIN {min} MAX {max})"
5181
5182    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
5183        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
5184
5185    def aggregatekeyproperty_sql(self, expression: exp.AggregateKeyProperty) -> str:
5186        return f"AGGREGATE KEY ({self.expressions(expression, flat=True)})"
5187
5188    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
5189        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
5190
5191    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
5192    def uniquekeyproperty_sql(
5193        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
5194    ) -> str:
5195        return f"{prefix} ({self.expressions(expression, flat=True)})"
5196
5197    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
5198    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
5199        expressions = self.expressions(expression, flat=True)
5200        expressions = f" {self.wrap(expressions)}" if expressions else ""
5201        buckets = self.sql(expression, "buckets")
5202        kind = self.sql(expression, "kind")
5203        buckets = f" BUCKETS {buckets}" if buckets else ""
5204        order = self.sql(expression, "order")
5205        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
5206
5207    def oncluster_sql(self, expression: exp.OnCluster) -> str:
5208        return ""
5209
5210    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
5211        expressions = self.expressions(expression, key="expressions", flat=True)
5212        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
5213        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
5214        buckets = self.sql(expression, "buckets")
5215        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
5216
5217    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
5218        this = self.sql(expression, "this")
5219        having = self.sql(expression, "having")
5220
5221        if having:
5222            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
5223
5224        return self.func("ANY_VALUE", this)
5225
5226    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
5227        transform = self.func("TRANSFORM", *expression.expressions)
5228        row_format_before = self.sql(expression, "row_format_before")
5229        row_format_before = f" {row_format_before}" if row_format_before else ""
5230        record_writer = self.sql(expression, "record_writer")
5231        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
5232        using = f" USING {self.sql(expression, 'command_script')}"
5233        schema = self.sql(expression, "schema")
5234        schema = f" AS {schema}" if schema else ""
5235        row_format_after = self.sql(expression, "row_format_after")
5236        row_format_after = f" {row_format_after}" if row_format_after else ""
5237        record_reader = self.sql(expression, "record_reader")
5238        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
5239        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
5240
5241    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
5242        key_block_size = self.sql(expression, "key_block_size")
5243        if key_block_size:
5244            return f"KEY_BLOCK_SIZE = {key_block_size}"
5245
5246        using = self.sql(expression, "using")
5247        if using:
5248            return f"USING {using}"
5249
5250        parser = self.sql(expression, "parser")
5251        if parser:
5252            return f"WITH PARSER {parser}"
5253
5254        comment = self.sql(expression, "comment")
5255        if comment:
5256            return f"COMMENT {comment}"
5257
5258        visible = expression.args.get("visible")
5259        if visible is not None:
5260            return "VISIBLE" if visible else "INVISIBLE"
5261
5262        engine_attr = self.sql(expression, "engine_attr")
5263        if engine_attr:
5264            return f"ENGINE_ATTRIBUTE = {engine_attr}"
5265
5266        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
5267        if secondary_engine_attr:
5268            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
5269
5270        self.unsupported("Unsupported index constraint option.")
5271        return ""
5272
5273    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
5274        enforced = " ENFORCED" if expression.args.get("enforced") else ""
5275        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
5276
5277    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
5278        kind = self.sql(expression, "kind")
5279        kind = f"{kind} INDEX" if kind else "INDEX"
5280        this = self.sql(expression, "this")
5281        this = f" {this}" if this else ""
5282        index_type = self.sql(expression, "index_type")
5283        index_type = f" USING {index_type}" if index_type else ""
5284        expressions = self.expressions(expression, flat=True)
5285        expressions = f" ({expressions})" if expressions else ""
5286        options = self.expressions(expression, key="options", sep=" ")
5287        options = f" {options}" if options else ""
5288        return f"{kind}{this}{index_type}{expressions}{options}"
5289
5290    def nvl2_sql(self, expression: exp.Nvl2) -> str:
5291        if self.NVL2_SUPPORTED:
5292            return self.function_fallback_sql(expression)
5293
5294        case = exp.Case().when(
5295            expression.this.is_(exp.null()).not_(copy=False),
5296            expression.args["true"],
5297            copy=False,
5298        )
5299        else_cond = expression.args.get("false")
5300        if else_cond:
5301            case.else_(else_cond, copy=False)
5302
5303        return self.sql(case)
5304
5305    def nthvalue_sql(self, expression: exp.NthValue) -> str:
5306        if expression.args.get("from_first") is False:
5307            self.unsupported("NTH_VALUE FROM LAST is not supported")
5308
5309        return self.function_fallback_sql(expression)
5310
5311    def comprehension_sql(self, expression: exp.Comprehension) -> str:
5312        this = self.sql(expression, "this")
5313        expr = self.sql(expression, "expression")
5314        position = self.sql(expression, "position")
5315        position = f", {position}" if position else ""
5316        iterator = self.sql(expression, "iterator")
5317        condition = self.sql(expression, "condition")
5318        condition = f" IF {condition}" if condition else ""
5319        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
5320
5321    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
5322        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
5323
5324    def opclass_sql(self, expression: exp.Opclass) -> str:
5325        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
5326
5327    def _ml_sql(self, expression: exp.Func, name: str) -> str:
5328        model = self.sql(expression, "this")
5329        model = f"MODEL {model}"
5330        expr = expression.expression
5331        if expr:
5332            expr_sql = self.sql(expression, "expression")
5333            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
5334        else:
5335            expr_sql = None
5336
5337        parameters = self.sql(expression, "params_struct") or None
5338
5339        return self.func(name, model, expr_sql, parameters)
5340
5341    def predict_sql(self, expression: exp.Predict) -> str:
5342        return self._ml_sql(expression, "PREDICT")
5343
5344    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
5345        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
5346        return self._ml_sql(expression, name)
5347
5348    def generatetext_sql(self, expression: exp.GenerateText) -> str:
5349        return self._ml_sql(expression, "GENERATE_TEXT")
5350
5351    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
5352        return self._ml_sql(expression, "GENERATE_TABLE")
5353
5354    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
5355        return self._ml_sql(expression, "GENERATE_BOOL")
5356
5357    def generateint_sql(self, expression: exp.GenerateInt) -> str:
5358        return self._ml_sql(expression, "GENERATE_INT")
5359
5360    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
5361        return self._ml_sql(expression, "GENERATE_DOUBLE")
5362
5363    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
5364        return self._ml_sql(expression, "TRANSLATE")
5365
5366    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
5367        return self._ml_sql(expression, "FORECAST")
5368
5369    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
5370        this_sql = self.sql(expression, "this")
5371        if isinstance(expression.this, exp.Table):
5372            this_sql = f"TABLE {this_sql}"
5373
5374        return self.func(
5375            "FORECAST",
5376            this_sql,
5377            expression.args.get("data_col"),
5378            expression.args.get("timestamp_col"),
5379            expression.args.get("model"),
5380            expression.args.get("id_cols"),
5381            expression.args.get("horizon"),
5382            expression.args.get("forecast_end_timestamp"),
5383            expression.args.get("confidence_level"),
5384            expression.args.get("output_historical_time_series"),
5385            expression.args.get("context_window"),
5386        )
5387
5388    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
5389        this_sql = self.sql(expression, "this")
5390        if isinstance(expression.this, exp.Table):
5391            this_sql = f"TABLE {this_sql}"
5392
5393        return self.func(
5394            "FEATURES_AT_TIME",
5395            this_sql,
5396            expression.args.get("time"),
5397            expression.args.get("num_rows"),
5398            expression.args.get("ignore_feature_nulls"),
5399        )
5400
5401    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5402        this_sql = self.sql(expression, "this")
5403        if isinstance(expression.this, exp.Table):
5404            this_sql = f"TABLE {this_sql}"
5405
5406        query_table = self.sql(expression, "query_table")
5407        if isinstance(expression.args["query_table"], exp.Table):
5408            query_table = f"TABLE {query_table}"
5409
5410        return self.func(
5411            "VECTOR_SEARCH",
5412            this_sql,
5413            expression.args.get("column_to_search"),
5414            query_table,
5415            expression.args.get("query_column_to_search"),
5416            expression.args.get("top_k"),
5417            expression.args.get("distance_type"),
5418            expression.args.get("options"),
5419        )
5420
5421    def forin_sql(self, expression: exp.ForIn) -> str:
5422        this = self.sql(expression, "this")
5423        expression_sql = self.sql(expression, "expression")
5424        return f"FOR {this} DO {expression_sql}"
5425
5426    @unsupported_args("partition_start", "partition_end", "force", "mode")
5427    def refresh_sql(self, expression: exp.Refresh) -> str:
5428        this = self.sql(expression, "this")
5429        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5430        return f"REFRESH {kind}{this}"
5431
5432    def toarray_sql(self, expression: exp.ToArray) -> str:
5433        arg = expression.this
5434        if not arg.type:
5435            import sqlglot.optimizer.annotate_types
5436
5437            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5438
5439        if arg.is_type(exp.DType.ARRAY):
5440            return self.sql(arg)
5441
5442        cond_for_null = arg.is_(exp.null())
5443        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5444
5445    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5446        this = expression.this
5447        time_format = self.format_time(expression)
5448
5449        if time_format:
5450            return self.sql(
5451                exp.cast(
5452                    exp.StrToTime(this=this, format=expression.args["format"]),
5453                    exp.DType.TIME,
5454                )
5455            )
5456
5457        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5458            return self.sql(this)
5459
5460        return self.sql(exp.cast(this, exp.DType.TIME))
5461
5462    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5463        this = expression.this
5464        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5465            return self.sql(this)
5466
5467        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5468
5469    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5470        this = expression.this
5471        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5472            return self.sql(this)
5473
5474        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5475
5476    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5477        this = expression.this
5478        time_format = self.format_time(expression)
5479        safe = expression.args.get("safe")
5480        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5481            return self.sql(
5482                exp.cast(
5483                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5484                    exp.DType.DATE,
5485                )
5486            )
5487
5488        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5489            return self.sql(this)
5490
5491        if safe:
5492            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5493
5494        return self.sql(exp.cast(this, exp.DType.DATE))
5495
5496    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5497        return self.sql(
5498            exp.func(
5499                "DATEDIFF",
5500                expression.this,
5501                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5502                "day",
5503            )
5504        )
5505
5506    def lastday_sql(self, expression: exp.LastDay) -> str:
5507        if self.LAST_DAY_SUPPORTS_DATE_PART:
5508            return self.function_fallback_sql(expression)
5509
5510        unit = expression.args.get("unit")
5511        if unit and unit.name.upper() != "MONTH":
5512            self.unsupported("Date parts are not supported in LAST_DAY.")
5513
5514        return self.func("LAST_DAY", expression.this)
5515
5516    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5517        import sqlglot.dialects.dialect
5518
5519        return self.func(
5520            "DATE_ADD",
5521            expression.this,
5522            expression.expression,
5523            sqlglot.dialects.dialect.unit_to_str(expression),
5524        )
5525
5526    def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str:
5527        this = expression.this
5528        position = expression.args["position"]
5529        offset = index_offset - (expression.args.get("offset") or 0)
5530
5531        if offset:
5532            if position.is_int:
5533                value = position.to_py()
5534                if value >= 0:
5535                    position = exp.Literal.number(value + offset)
5536                elif offset < 0 and value == -1:
5537                    # 1-based -1 appends, which a 0-based position can only express as the size
5538                    position = exp.ArraySize(this=this.copy())
5539                else:
5540                    # Negative positions count from the end, so they shift in the opposite
5541                    # direction, e.g. 0-based -1 (before the last element) is 1-based -2
5542                    position = exp.Literal.number(value - offset)
5543            else:
5544                self.unsupported("ARRAY_INSERT position can only be converted if it's a literal")
5545
5546        return self.func("ARRAY_INSERT", this, position, expression.expression)
5547
5548    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5549        if self.CAN_IMPLEMENT_ARRAY_ANY:
5550            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5551            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5552            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5553            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5554
5555        import sqlglot.dialects.dialect
5556
5557        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5558        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5559            self.unsupported("ARRAY_ANY is unsupported")
5560
5561        return self.function_fallback_sql(expression)
5562
5563    def struct_sql(self, expression: exp.Struct) -> str:
5564        expression.set(
5565            "expressions",
5566            [
5567                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5568                if isinstance(e, exp.PropertyEQ)
5569                else e
5570                for e in expression.expressions
5571            ],
5572        )
5573
5574        return self.function_fallback_sql(expression)
5575
5576    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5577        low = self.sql(expression, "this")
5578        high = self.sql(expression, "expression")
5579
5580        return f"{low} TO {high}"
5581
5582    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5583        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5584        tables = f" {self.expressions(expression)}"
5585
5586        exists = " IF EXISTS" if expression.args.get("exists") else ""
5587
5588        on_cluster = self.sql(expression, "cluster")
5589        on_cluster = f" {on_cluster}" if on_cluster else ""
5590
5591        identity = self.sql(expression, "identity")
5592        identity = f" {identity} IDENTITY" if identity else ""
5593
5594        option = self.sql(expression, "option")
5595        option = f" {option}" if option else ""
5596
5597        partition = self.sql(expression, "partition")
5598        partition = f" {partition}" if partition else ""
5599
5600        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5601
5602    # This transpiles T-SQL's CONVERT function
5603    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
5604    def convert_sql(self, expression: exp.Convert) -> str:
5605        to = expression.this
5606        value = expression.expression
5607        style = expression.args.get("style")
5608        safe = expression.args.get("safe")
5609        strict = expression.args.get("strict")
5610
5611        if not to or not value:
5612            return ""
5613
5614        # Retrieve length of datatype and override to default if not specified
5615        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5616            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5617
5618        transformed: exp.Expr | None = None
5619        cast = exp.Cast if strict else exp.TryCast
5620
5621        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5622        if isinstance(style, exp.Literal) and style.is_int:
5623            import sqlglot.dialects.tsql
5624
5625            style_value = style.name
5626            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5627            if not converted_style:
5628                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5629
5630            fmt = exp.Literal.string(converted_style)
5631
5632            if to.this == exp.DType.DATE:
5633                transformed = exp.StrToDate(this=value, format=fmt)
5634            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5635                transformed = exp.StrToTime(this=value, format=fmt)
5636            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5637                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5638            elif to.this == exp.DType.TEXT:
5639                transformed = exp.TimeToStr(this=value, format=fmt)
5640
5641        if not transformed:
5642            transformed = cast(this=value, to=to, safe=safe)
5643
5644        return self.sql(transformed)
5645
5646    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
5647        this = expression.this
5648        if isinstance(this, exp.JSONPathWildcard):
5649            this = self.json_path_part(this)
5650            return f".{this}" if this else ""
5651
5652        quoted = expression.args.get("quoted")
5653        if not (
5654            quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
5655        ) and self.SAFE_JSON_PATH_KEY_RE.match(this):
5656            return f".{this}"
5657
5658        this = self.json_path_part(this)
5659
5660        return (
5661            f"[{this}]"
5662            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5663            else f".{this}"
5664        )
5665
5666    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5667        this = self.json_path_part(expression.this)
5668        return f"[{this}]" if this else ""
5669
5670    def _simplify_unless_literal(self, expression: E) -> E:
5671        if not isinstance(expression, exp.Literal):
5672            import sqlglot.optimizer.simplify
5673
5674            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5675
5676        return expression
5677
5678    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5679        this = expression.this
5680        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5681            self.unsupported(
5682                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5683            )
5684            return self.sql(this)
5685
5686        if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5687            if self.IGNORE_NULLS_BEFORE_ORDER:
5688                from sqlglot.optimizer.scope import find_all_in_scope
5689
5690                # The first modifier here will be the one closest to the AggFunc's arg
5691                mods = sorted(
5692                    find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit),
5693                    key=lambda x: (
5694                        0
5695                        if isinstance(x, exp.HavingMax)
5696                        else (1 if isinstance(x, exp.Order) else 2)
5697                    ),
5698                )
5699
5700                if mods:
5701                    mod = mods[0]
5702                    this = expression.__class__(this=mod.this.copy())
5703                    this.meta["inline"] = True
5704                    mod.this.replace(this)
5705                    return self.sql(expression.this)
5706
5707            agg_func = expression.find(exp.AggFunc)
5708
5709            if agg_func:
5710                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5711                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5712
5713        return f"{self.sql(expression, 'this')} {text}"
5714
5715    def _replace_line_breaks(self, string: str) -> str:
5716        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5717        if self.pretty:
5718            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5719        return string
5720
5721    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5722        option = self.sql(expression, "this")
5723
5724        if expression.expressions:
5725            upper = option.upper()
5726
5727            # Snowflake FILE_FORMAT options are separated by whitespace
5728            sep = " " if upper == "FILE_FORMAT" else ", "
5729
5730            # Databricks copy/format options do not set their list of values with EQ
5731            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5732            values = self.expressions(expression, flat=True, sep=sep)
5733            return f"{option}{op}({values})"
5734
5735        value = self.sql(expression, "expression")
5736
5737        if not value:
5738            return option
5739
5740        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5741
5742        return f"{option}{op}{value}"
5743
5744    def credentials_sql(self, expression: exp.Credentials) -> str:
5745        cred_expr = expression.args.get("credentials")
5746        if isinstance(cred_expr, exp.Literal):
5747            # Redshift case: CREDENTIALS <string>
5748            credentials = self.sql(expression, "credentials")
5749            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5750        else:
5751            # Snowflake case: CREDENTIALS = (...)
5752            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5753            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5754
5755        storage = self.sql(expression, "storage")
5756        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5757
5758        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5759        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5760
5761        iam_role = self.sql(expression, "iam_role")
5762        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5763
5764        region = self.sql(expression, "region")
5765        region = f" REGION {region}" if region else ""
5766
5767        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5768
5769    def copy_sql(self, expression: exp.Copy) -> str:
5770        this = self.sql(expression, "this")
5771        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5772
5773        credentials = self.sql(expression, "credentials")
5774        credentials = self.seg(credentials) if credentials else ""
5775        files = self.expressions(expression, key="files", flat=True)
5776        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5777
5778        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5779        params = self.expressions(
5780            expression,
5781            key="params",
5782            sep=sep,
5783            new_line=True,
5784            skip_last=True,
5785            skip_first=True,
5786            indent=self.COPY_PARAMS_ARE_WRAPPED,
5787        )
5788
5789        if params:
5790            if self.COPY_PARAMS_ARE_WRAPPED:
5791                params = f" WITH ({params})"
5792            elif not self.pretty and (files or credentials):
5793                params = f" {params}"
5794
5795        return f"COPY{this}{kind} {files}{credentials}{params}"
5796
5797    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5798        return ""
5799
5800    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5801        on_sql = "ON" if expression.args.get("on") else "OFF"
5802        filter_col: str | None = self.sql(expression, "filter_column")
5803        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5804        retention_period: str | None = self.sql(expression, "retention_period")
5805        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5806
5807        if filter_col or retention_period:
5808            on_sql = self.func("ON", filter_col, retention_period)
5809
5810        return f"DATA_DELETION={on_sql}"
5811
5812    def maskingpolicycolumnconstraint_sql(
5813        self, expression: exp.MaskingPolicyColumnConstraint
5814    ) -> str:
5815        this = self.sql(expression, "this")
5816        expressions = self.expressions(expression, flat=True)
5817        expressions = f" USING ({expressions})" if expressions else ""
5818        return f"MASKING POLICY {this}{expressions}"
5819
5820    def gapfill_sql(self, expression: exp.GapFill) -> str:
5821        this = self.sql(expression, "this")
5822        this = f"TABLE {this}"
5823        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5824
5825    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5826        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5827
5828    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5829        this = self.sql(expression, "this")
5830        expr = expression.expression
5831
5832        if isinstance(expr, exp.Func):
5833            # T-SQL's CLR functions are case sensitive
5834            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5835        else:
5836            expr = self.sql(expression, "expression")
5837
5838        return self.scope_resolution(expr, this)
5839
5840    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5841        if self.PARSE_JSON_NAME is None:
5842            return self.sql(expression.this)
5843
5844        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5845
5846    def rand_sql(self, expression: exp.Rand) -> str:
5847        lower = self.sql(expression, "lower")
5848        upper = self.sql(expression, "upper")
5849
5850        if lower and upper:
5851            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5852        return self.func("RAND", expression.this)
5853
5854    def changes_sql(self, expression: exp.Changes) -> str:
5855        information = self.sql(expression, "information")
5856        information = f"INFORMATION => {information}"
5857        at_before = self.sql(expression, "at_before")
5858        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5859        end = self.sql(expression, "end")
5860        end = f"{self.seg('')}{end}" if end else ""
5861
5862        return f"CHANGES ({information}){at_before}{end}"
5863
5864    def pad_sql(self, expression: exp.Pad) -> str:
5865        prefix = "L" if expression.args.get("is_left") else "R"
5866
5867        fill_pattern = self.sql(expression, "fill_pattern") or None
5868        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5869            fill_pattern = "' '"
5870
5871        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5872
5873    def summarize_sql(self, expression: exp.Summarize) -> str:
5874        table = " TABLE" if expression.args.get("table") else ""
5875        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5876
5877    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5878        generate_series = exp.GenerateSeries(**expression.args)
5879
5880        parent = expression.parent
5881        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5882            parent = parent.parent
5883
5884        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5885            return self.sql(exp.Unnest(expressions=[generate_series]))
5886
5887        if isinstance(parent, exp.Select):
5888            self.unsupported("GenerateSeries projection unnesting is not supported.")
5889
5890        return self.sql(generate_series)
5891
5892    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5893        if self.SUPPORTS_CONVERT_TIMEZONE:
5894            return self.function_fallback_sql(expression)
5895
5896        source_tz = expression.args.get("source_tz")
5897        target_tz = expression.args.get("target_tz")
5898        timestamp = expression.args.get("timestamp")
5899
5900        if source_tz and timestamp:
5901            timestamp = exp.AtTimeZone(
5902                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5903            )
5904
5905        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5906
5907        return self.sql(expr)
5908
5909    def json_sql(self, expression: exp.JSON) -> str:
5910        this = self.sql(expression, "this")
5911        this = f" {this}" if this else ""
5912
5913        _with = expression.args.get("with_")
5914
5915        if _with is None:
5916            with_sql = ""
5917        elif not _with:
5918            with_sql = " WITHOUT"
5919        else:
5920            with_sql = " WITH"
5921
5922        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5923
5924        return f"JSON{this}{with_sql}{unique_sql}"
5925
5926    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5927        path = self.sql(expression, "path")
5928        returning = self.sql(expression, "returning")
5929        returning = f" RETURNING {returning}" if returning else ""
5930
5931        on_condition = self.sql(expression, "on_condition")
5932        on_condition = f" {on_condition}" if on_condition else ""
5933
5934        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5935
5936    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5937        regexp = " REGEXP" if expression.args.get("regexp") else ""
5938        return f"SKIP{regexp} {self.sql(expression.expression)}"
5939
5940    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5941        else_ = "ELSE " if expression.args.get("else_") else ""
5942        condition = self.sql(expression, "expression")
5943        condition = f"WHEN {condition} THEN " if condition else else_
5944        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5945        return f"{condition}{insert}"
5946
5947    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5948        kind = self.sql(expression, "kind")
5949        source = self.sql(expression, "source")
5950
5951        if kind:
5952            expressions = self.seg(self.expressions(expression, sep=" "))
5953            sql = f"INSERT {kind}{expressions}{self.seg(source)}"
5954        else:
5955            # If kind isn't present, we have Hive's FROM-first form:
5956            # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...]
5957            inserts = self.sep().join(self.sql(e) for e in expression.expressions)
5958            sql = f"FROM {source}{self.seg(inserts)}"
5959
5960        return self.prepend_ctes(expression, sql)
5961
5962    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5963        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5964        empty = expression.args.get("empty")
5965        empty = (
5966            f"DEFAULT {empty} ON EMPTY"
5967            if isinstance(empty, exp.Expr)
5968            else self.sql(expression, "empty")
5969        )
5970
5971        error = expression.args.get("error")
5972        error = (
5973            f"DEFAULT {error} ON ERROR"
5974            if isinstance(error, exp.Expr)
5975            else self.sql(expression, "error")
5976        )
5977
5978        if error and empty:
5979            error = (
5980                f"{empty} {error}"
5981                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5982                else f"{error} {empty}"
5983            )
5984            empty = ""
5985
5986        null = self.sql(expression, "null")
5987
5988        return f"{empty}{error}{null}"
5989
5990    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5991        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5992        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5993
5994    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5995        this = self.sql(expression, "this")
5996        path = self.sql(expression, "path")
5997
5998        passing = self.expressions(expression, "passing")
5999        passing = f" PASSING {passing}" if passing else ""
6000
6001        on_condition = self.sql(expression, "on_condition")
6002        on_condition = f" {on_condition}" if on_condition else ""
6003
6004        path = f"{path}{passing}{on_condition}"
6005
6006        return self.func("JSON_EXISTS", this, path)
6007
6008    def _add_arrayagg_null_filter(
6009        self,
6010        array_agg_sql: str,
6011        array_agg_expr: exp.ArrayAgg,
6012        column_expr: exp.Expr,
6013    ) -> str:
6014        """
6015        Add NULL filter to ARRAY_AGG if dialect requires it.
6016
6017        Args:
6018            array_agg_sql: The generated ARRAY_AGG SQL string
6019            array_agg_expr: The ArrayAgg expression node
6020            column_expr: The column/expression to filter (before ORDER BY wrapping)
6021
6022        Returns:
6023            SQL string with FILTER clause added if needed
6024        """
6025        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
6026        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
6027        if not (
6028            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
6029        ):
6030            return array_agg_sql
6031
6032        parent = array_agg_expr.parent
6033        if isinstance(parent, exp.Filter):
6034            parent_cond = parent.expression.this
6035            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
6036        elif column_expr.find(exp.Column):
6037            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
6038            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
6039            this_sql = (
6040                self.expressions(column_expr)
6041                if isinstance(column_expr, exp.Distinct)
6042                else self.sql(column_expr)
6043            )
6044            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
6045
6046        return array_agg_sql
6047
6048    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
6049        array_agg = self.function_fallback_sql(expression)
6050        column_expr = expression.this
6051        if isinstance(column_expr, exp.Order):
6052            column_expr = column_expr.this
6053
6054        return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
6055
6056    def slice_sql(self, expression: exp.Slice) -> str:
6057        step = self.sql(expression, "step")
6058        # An omitted end is written as `-` before a step, since `::` is the cast operator
6059        end = self.sql(expression.expression) or ("-" if step else "")
6060        begin = self.sql(expression.this)
6061
6062        sql = f"{end}:{step}" if step else end
6063        return f"{begin}:{sql}" if sql else f"{begin}:"
6064
6065    def apply_sql(self, expression: exp.Apply) -> str:
6066        this = self.sql(expression, "this")
6067        expr = self.sql(expression, "expression")
6068
6069        return f"{this} APPLY({expr})"
6070
6071    def _grant_or_revoke_sql(
6072        self,
6073        expression: exp.Grant | exp.Revoke,
6074        keyword: str,
6075        preposition: str,
6076        grant_option_prefix: str = "",
6077        grant_option_suffix: str = "",
6078    ) -> str:
6079        privileges_sql = self.expressions(expression, key="privileges", flat=True)
6080
6081        kind = self.sql(expression, "kind")
6082        kind = f" {kind}" if kind else ""
6083
6084        securable = self.sql(expression, "securable")
6085        securable = f" {securable}" if securable else ""
6086
6087        principals = self.expressions(expression, key="principals", flat=True)
6088
6089        if not expression.args.get("grant_option"):
6090            grant_option_prefix = grant_option_suffix = ""
6091
6092        # cascade for revoke only
6093        cascade = self.sql(expression, "cascade")
6094        cascade = f" {cascade}" if cascade else ""
6095
6096        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
6097
6098    def grant_sql(self, expression: exp.Grant) -> str:
6099        return self._grant_or_revoke_sql(
6100            expression,
6101            keyword="GRANT",
6102            preposition="TO",
6103            grant_option_suffix=" WITH GRANT OPTION",
6104        )
6105
6106    def revoke_sql(self, expression: exp.Revoke) -> str:
6107        return self._grant_or_revoke_sql(
6108            expression,
6109            keyword="REVOKE",
6110            preposition="FROM",
6111            grant_option_prefix="GRANT OPTION FOR ",
6112        )
6113
6114    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
6115        this = self.sql(expression, "this")
6116        columns = self.expressions(expression, flat=True)
6117        columns = f"({columns})" if columns else ""
6118
6119        return f"{this}{columns}"
6120
6121    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
6122        this = self.sql(expression, "this")
6123
6124        kind = self.sql(expression, "kind")
6125        kind = f"{kind} " if kind else ""
6126
6127        return f"{kind}{this}"
6128
6129    def columns_sql(self, expression: exp.Columns) -> str:
6130        func = self.function_fallback_sql(expression)
6131        if expression.args.get("unpack"):
6132            func = f"*{func}"
6133
6134        return func
6135
6136    def overlay_sql(self, expression: exp.Overlay) -> str:
6137        this = self.sql(expression, "this")
6138        expr = self.sql(expression, "expression")
6139        from_sql = self.sql(expression, "from_")
6140        for_sql = self.sql(expression, "for_")
6141        for_sql = f" FOR {for_sql}" if for_sql else ""
6142
6143        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
6144
6145    @unsupported_args("format")
6146    def todouble_sql(self, expression: exp.ToDouble) -> str:
6147        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
6148        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
6149
6150    def string_sql(self, expression: exp.String) -> str:
6151        this = expression.this
6152        zone = expression.args.get("zone")
6153
6154        if zone:
6155            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
6156            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
6157            # set for source_tz to transpile the time conversion before the STRING cast
6158            this = exp.ConvertTimezone(
6159                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
6160            )
6161
6162        return self.sql(exp.cast(this, exp.DType.VARCHAR))
6163
6164    def median_sql(self, expression: exp.Median) -> str:
6165        if not self.SUPPORTS_MEDIAN:
6166            return self.sql(
6167                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
6168            )
6169
6170        return self.function_fallback_sql(expression)
6171
6172    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
6173        filler = self.sql(expression, "this")
6174        filler = f" {filler}" if filler else ""
6175        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
6176        return f"TRUNCATE{filler} {with_count}"
6177
6178    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
6179        if self.SUPPORTS_UNIX_SECONDS:
6180            return self.function_fallback_sql(expression)
6181
6182        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
6183
6184        return self.sql(
6185            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
6186        )
6187
6188    def arraysort_sql(self, expression: exp.ArraySort) -> str:
6189        asc = expression.args.get("asc")
6190        nulls_first = expression.args.get("nulls_first")
6191        if (asc and asc != exp.true()) or (nulls_first and nulls_first != exp.false()):
6192            self.unsupported("ARRAY_SORT only sorts ascending with NULLs last")
6193        return self.func("ARRAY_SORT", expression.this, expression.expression)
6194
6195    def arraysize_sql(self, expression: exp.ArraySize) -> str:
6196        dim = expression.expression
6197
6198        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
6199        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
6200            if not (dim.is_int and dim.name == "1"):
6201                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
6202            dim = None
6203
6204        # If dimension is required but not specified, default initialize it
6205        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
6206            dim = exp.Literal.number(1)
6207
6208        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
6209
6210    def attach_sql(self, expression: exp.Attach) -> str:
6211        this = self.sql(expression, "this")
6212        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
6213        expressions = self.expressions(expression)
6214        expressions = f" ({expressions})" if expressions else ""
6215
6216        return f"ATTACH{exists_sql} {this}{expressions}"
6217
6218    def detach_sql(self, expression: exp.Detach) -> str:
6219        kind = self.sql(expression, "kind")
6220        kind = f" {kind}" if kind else ""
6221        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
6222        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
6223        exists = " IF EXISTS" if expression.args.get("exists") else ""
6224        if exists:
6225            kind = kind or " DATABASE"
6226
6227        this = self.sql(expression, "this")
6228        this = f" {this}" if this else ""
6229        cluster = self.sql(expression, "cluster")
6230        cluster = f" {cluster}" if cluster else ""
6231        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
6232        sync = " SYNC" if expression.args.get("sync") else ""
6233        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
6234
6235    def attachoption_sql(self, expression: exp.AttachOption) -> str:
6236        this = self.sql(expression, "this")
6237        value = self.sql(expression, "expression")
6238        value = f" {value}" if value else ""
6239        return f"{this}{value}"
6240
6241    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
6242        return (
6243            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
6244        )
6245
6246    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
6247        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
6248        encode = f"{encode} {self.sql(expression, 'this')}"
6249
6250        properties = expression.args.get("properties")
6251        if properties:
6252            encode = f"{encode} {self.properties(properties)}"
6253
6254        return encode
6255
6256    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
6257        this = self.sql(expression, "this")
6258        include = f"INCLUDE {this}"
6259
6260        column_def = self.sql(expression, "column_def")
6261        if column_def:
6262            include = f"{include} {column_def}"
6263
6264        alias = self.sql(expression, "alias")
6265        if alias:
6266            include = f"{include} AS {alias}"
6267
6268        return include
6269
6270    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
6271        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
6272        name = f"{prefix} {self.sql(expression, 'this')}"
6273        return self.func("XMLELEMENT", name, *expression.expressions)
6274
6275    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
6276        this = self.sql(expression, "this")
6277        expr = self.sql(expression, "expression")
6278        expr = f"({expr})" if expr else ""
6279        return f"{this}{expr}"
6280
6281    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
6282        partitions = self.expressions(expression, "partition_expressions")
6283        create = self.expressions(expression, "create_expressions")
6284        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
6285
6286    def partitionbyrangepropertydynamic_sql(
6287        self, expression: exp.PartitionByRangePropertyDynamic
6288    ) -> str:
6289        start = self.sql(expression, "start")
6290        end = self.sql(expression, "end")
6291
6292        every = expression.args["every"]
6293        if isinstance(every, exp.Interval) and every.this.is_string:
6294            every.this.replace(exp.Literal.number(every.name))
6295
6296        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6297
6298    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
6299        name = self.sql(expression, "this")
6300        values = self.expressions(expression, flat=True)
6301
6302        return f"NAME {name} VALUE {values}"
6303
6304    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
6305        kind = self.sql(expression, "kind")
6306        sample = self.sql(expression, "sample")
6307        return f"SAMPLE {sample} {kind}"
6308
6309    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
6310        kind = self.sql(expression, "kind")
6311        option = self.sql(expression, "option")
6312        option = f" {option}" if option else ""
6313        this = self.sql(expression, "this")
6314        this = f" {this}" if this else ""
6315        columns = self.expressions(expression)
6316        columns = f" {columns}" if columns else ""
6317        return f"{kind}{option} STATISTICS{this}{columns}"
6318
6319    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
6320        this = self.sql(expression, "this")
6321        columns = self.expressions(expression)
6322        inner_expression = self.sql(expression, "expression")
6323        inner_expression = f" {inner_expression}" if inner_expression else ""
6324        update_options = self.sql(expression, "update_options")
6325        update_options = f" {update_options} UPDATE" if update_options else ""
6326        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
6327
6328    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
6329        kind = self.sql(expression, "kind")
6330        kind = f" {kind}" if kind else ""
6331        return f"DELETE{kind} STATISTICS"
6332
6333    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
6334        inner_expression = self.sql(expression, "expression")
6335        return f"LIST CHAINED ROWS{inner_expression}"
6336
6337    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
6338        kind = self.sql(expression, "kind")
6339        this = self.sql(expression, "this")
6340        this = f" {this}" if this else ""
6341        inner_expression = self.sql(expression, "expression")
6342        return f"VALIDATE {kind}{this}{inner_expression}"
6343
6344    def analyze_sql(self, expression: exp.Analyze) -> str:
6345        options = self.expressions(expression, key="options", sep=" ")
6346        options = f" {options}" if options else ""
6347        kind = self.sql(expression, "kind")
6348        kind = f" {kind}" if kind else ""
6349        tables = self.expressions(expression, key="tables", flat=True)
6350        tables = f" {tables}" if tables else ""
6351        mode = self.sql(expression, "mode")
6352        mode = f" {mode}" if mode else ""
6353        properties = self.sql(expression, "properties")
6354        properties = f" {properties}" if properties else ""
6355        partition = self.sql(expression, "partition")
6356        partition = f" {partition}" if partition else ""
6357        inner_expression = self.sql(expression, "expression")
6358        inner_expression = f" {inner_expression}" if inner_expression else ""
6359        return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6360
6361    def xmltable_sql(self, expression: exp.XMLTable) -> str:
6362        this = self.sql(expression, "this")
6363        namespaces = self.expressions(expression, key="namespaces")
6364        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
6365        passing = self.expressions(expression, key="passing")
6366        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
6367        columns = self.expressions(expression, key="columns")
6368        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
6369        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
6370        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6371
6372    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
6373        this = self.sql(expression, "this")
6374        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
6375
6376    def export_sql(self, expression: exp.Export) -> str:
6377        this = self.sql(expression, "this")
6378        connection = self.sql(expression, "connection")
6379        connection = f"WITH CONNECTION {connection} " if connection else ""
6380        options = self.sql(expression, "options")
6381        return f"EXPORT DATA {connection}{options} AS {this}"
6382
6383    def declare_sql(self, expression: exp.Declare) -> str:
6384        replace = "OR REPLACE " if expression.args.get("replace") else ""
6385        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
6386
6387    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
6388        variables = self.expressions(expression, "this")
6389        default = self.sql(expression, "default")
6390        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
6391
6392        kind = self.sql(expression, "kind")
6393        if isinstance(expression.args.get("kind"), exp.Schema):
6394            kind = f"TABLE {kind}"
6395
6396        kind = f" {kind}" if kind else ""
6397
6398        return f"{variables}{kind}{default}"
6399
6400    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
6401        kind = self.sql(expression, "kind")
6402        this = self.sql(expression, "this")
6403        set = self.sql(expression, "expression")
6404        to = self.sql(expression, "to")
6405        to = f" TO {to}" if to else ""
6406        default = self.sql(expression, "default")
6407        default = f" DEFAULT {default}" if default else ""
6408        using = self.sql(expression, "using")
6409        using = f" USING {using}" if using else ""
6410
6411        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
6412
6413        return f"{kind_sql} {this} SET {set}{to}{default}{using}"
6414
6415    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
6416        params = self.expressions(expression, key="params", flat=True)
6417        return self.func(expression.name, *expression.expressions) + f"({params})"
6418
6419    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
6420        return self.func(expression.name, *expression.expressions)
6421
6422    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
6423        return self.anonymousaggfunc_sql(expression)
6424
6425    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
6426        return self.parameterizedagg_sql(expression)
6427
6428    def show_sql(self, expression: exp.Show) -> str:
6429        self.unsupported("Unsupported SHOW statement")
6430        return ""
6431
6432    def install_sql(self, expression: exp.Install) -> str:
6433        self.unsupported("Unsupported INSTALL statement")
6434        return ""
6435
6436    def liststage_sql(self, expression: exp.ListStage) -> str:
6437        pattern = self.sql(expression, "pattern")
6438        pattern = f" PATTERN = {pattern}" if pattern else ""
6439        return f"LIST {self.sql(expression, 'this')}{pattern}"
6440
6441    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
6442        # Snowflake GET/PUT statements:
6443        #   PUT <file> <internalStage> <properties>
6444        #   GET <internalStage> <file> <properties>
6445        props = expression.args.get("properties")
6446        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
6447        this = self.sql(expression, "this")
6448        target = self.sql(expression, "target")
6449
6450        if isinstance(expression, exp.Put):
6451            return f"PUT {this} {target}{props_sql}"
6452        else:
6453            return f"GET {target} {this}{props_sql}"
6454
6455    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
6456        this = self.sql(expression, "this")
6457        expr = self.sql(expression, "expression")
6458        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6459        return f"TRANSLATE({this} USING {expr}{with_error})"
6460
6461    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6462        if self.SUPPORTS_DECODE_CASE:
6463            return self.func("DECODE", *expression.expressions)
6464
6465        decode_expr, *expressions = expression.expressions
6466
6467        ifs = []
6468        for search, result in zip(expressions[::2], expressions[1::2]):
6469            if isinstance(search, exp.Literal):
6470                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6471            elif isinstance(search, exp.Null):
6472                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6473            else:
6474                if isinstance(search, exp.Binary):
6475                    search = exp.paren(search)
6476
6477                cond = exp.or_(
6478                    decode_expr.eq(search),
6479                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6480                    copy=False,
6481                )
6482                ifs.append(exp.If(this=cond, true=result))
6483
6484        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6485        return self.sql(case)
6486
6487    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6488        this = self.sql(expression, "this")
6489        this = self.seg(this, sep="")
6490        dimensions = self.expressions(
6491            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6492        )
6493        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6494        metrics = self.expressions(
6495            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6496        )
6497        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6498        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6499        facts = self.seg(f"FACTS {facts}") if facts else ""
6500        where = self.sql(expression, "where")
6501        where = self.seg(f"WHERE {where}") if where else ""
6502        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6503        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6504
6505    def getextract_sql(self, expression: exp.GetExtract) -> str:
6506        this = expression.this
6507        expr = expression.expression
6508
6509        if not this.type or not expression.type:
6510            import sqlglot.optimizer.annotate_types
6511
6512            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6513
6514        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6515            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6516
6517        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
6518
6519    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6520        return self.sql(
6521            exp.DateAdd(
6522                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6523                expression=expression.this,
6524                unit=exp.var("DAY"),
6525            )
6526        )
6527
6528    def space_sql(self: Generator, expression: exp.Space) -> str:
6529        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
6530
6531    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6532        return f"BUILD {self.sql(expression, 'this')}"
6533
6534    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6535        method = self.sql(expression, "method")
6536        kind = expression.args.get("kind")
6537        if not kind:
6538            return f"REFRESH {method}"
6539
6540        every = self.sql(expression, "every")
6541        unit = self.sql(expression, "unit")
6542        every = f" EVERY {every} {unit}" if every else ""
6543        starts = self.sql(expression, "starts")
6544        starts = f" STARTS {starts}" if starts else ""
6545
6546        return f"REFRESH {method} ON {kind}{every}{starts}"
6547
6548    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6549        self.unsupported("The model!attribute syntax is not supported")
6550        return ""
6551
6552    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6553        return self.func("DIRECTORY", expression.this)
6554
6555    def uuid_sql(self, expression: exp.Uuid) -> str:
6556        is_string = expression.args.get("is_string", False)
6557        uuid_func_sql = self.func("UUID")
6558
6559        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6560            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6561
6562        return uuid_func_sql
6563
6564    def initcap_sql(self, expression: exp.Initcap) -> str:
6565        delimiters = expression.expression
6566
6567        if delimiters:
6568            # do not generate delimiters arg if we are round-tripping from default delimiters
6569            if (
6570                delimiters.is_string
6571                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6572            ):
6573                delimiters = None
6574            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6575                self.unsupported("INITCAP does not support custom delimiters")
6576                delimiters = None
6577
6578        return self.func("INITCAP", expression.this, delimiters)
6579
6580    def localtime_sql(self, expression: exp.Localtime) -> str:
6581        this = expression.this
6582        return self.func("LOCALTIME", this) if this else "LOCALTIME"
6583
6584    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6585        this = expression.this
6586        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
6587
6588    def weekstart_name(self, expression: exp.WeekStart) -> str:
6589        import sqlglot.dialects.dialect
6590
6591        # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit
6592        this = expression.this.name.upper()
6593
6594        dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this)
6595        dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET)
6596
6597        if dow_from_week_start_day != dow_from_week_offset:
6598            self.unsupported(
6599                f"WEEK({this}) is not supported; falling back to the default week start day"
6600            )
6601
6602        return "WEEK"
6603
6604    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6605        name = self.weekstart_name(expression)
6606
6607        # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords
6608        if isinstance(expression.parent, exp.DateTrunc):
6609            return self.sql(exp.Literal.string(name))
6610
6611        return name
6612
6613    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6614        this = self.expressions(expression)
6615        charset = self.sql(expression, "charset")
6616        using = f" USING {charset}" if charset else ""
6617        return self.func(name, this + using)
6618
6619    def weightstring_sql(self, expression: exp.WeightString) -> str:
6620        to = self.sql(expression, "to")
6621        to = f" AS {to}" if to else ""
6622        return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}")
6623
6624    def block_sql(self, expression: exp.Block) -> str:
6625        expressions = self.expressions(expression, sep="; ", flat=True)
6626        begin = "BEGIN " if expression.args.get("begin") else ""
6627        return f"{begin}{expressions}" if expressions else ""
6628
6629    def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str:
6630        self.unsupported("Unsupported Inline UDFs syntax")
6631        return ""
6632
6633    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6634        self.unsupported("Unsupported Stored Procedure syntax")
6635        return ""
6636
6637    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6638        self.unsupported("Unsupported If block syntax")
6639        return ""
6640
6641    def casestatement_sql(self, expression: exp.CaseStatement) -> str:
6642        self.unsupported("Unsupported Case statement syntax")
6643        return ""
6644
6645    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6646        self.unsupported("Unsupported While block syntax")
6647        return ""
6648
6649    def loopblock_sql(self, expression: exp.LoopBlock) -> str:
6650        self.unsupported("Unsupported Loop block syntax")
6651        return ""
6652
6653    def repeatblock_sql(self, expression: exp.RepeatBlock) -> str:
6654        self.unsupported("Unsupported Repeat block syntax")
6655        return ""
6656
6657    def leave_sql(self, expression: exp.Leave) -> str:
6658        self.unsupported("Unsupported Leave syntax")
6659        return ""
6660
6661    def iterate_sql(self, expression: exp.Iterate) -> str:
6662        self.unsupported("Unsupported Iterate syntax")
6663        return ""
6664
6665    def execute_sql(self, expression: exp.Execute) -> str:
6666        self.unsupported("Unsupported Execute syntax")
6667        return ""
6668
6669    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6670        self.unsupported("Unsupported Execute syntax")
6671        return ""
6672
6673    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6674        props = self.expressions(expression, sep=" ")
6675        return f"MODIFY {props}"
6676
6677    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6678        kind = expression.args.get("kind")
6679        return f"USING {kind} {self.sql(expression, 'this')}"
6680
6681    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6682        this = self.sql(expression, "this")
6683        to = self.sql(expression, "to")
6684        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.CopyTagsProperty: lambda *_: "COPY TAGS",
 174        exp.CredentialsProperty: lambda self, e: (
 175            f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})"
 176        ),
 177        exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG",
 178        exp.SessionUser: lambda *_: "SESSION_USER",
 179        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 180        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 181        exp.ApiProperty: lambda *_: "API",
 182        exp.ApplicationProperty: lambda *_: "APPLICATION",
 183        exp.CatalogProperty: lambda *_: "CATALOG",
 184        exp.ComputeProperty: lambda *_: "COMPUTE",
 185        exp.DatabaseProperty: lambda *_: "DATABASE",
 186        exp.DynamicProperty: lambda *_: "DYNAMIC",
 187        exp.EmptyProperty: lambda *_: "EMPTY",
 188        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 189        exp.EndStatement: lambda *_: "END",
 190        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 191        exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}",
 192        exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}",
 193        exp.EphemeralColumnConstraint: lambda self, e: (
 194            f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}"
 195        ),
 196        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 197        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 198        exp.Except: lambda self, e: self.set_operations(e),
 199        exp.ExternalProperty: lambda *_: "EXTERNAL",
 200        exp.Floor: lambda self, e: self.ceil_floor(e),
 201        exp.Get: lambda self, e: self.get_put_sql(e),
 202        exp.GlobalProperty: lambda *_: "GLOBAL",
 203        exp.HeapProperty: lambda *_: "HEAP",
 204        exp.HybridProperty: lambda *_: "HYBRID",
 205        exp.IcebergProperty: lambda *_: "ICEBERG",
 206        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 207        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 208        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 209        exp.Intersect: lambda self, e: self.set_operations(e),
 210        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 211        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)),
 212        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 213        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 214        exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"),
 215        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 216        exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"),
 217        exp.JSONObject: lambda self, e: self._jsonobject_sql(e),
 218        exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e),
 219        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 220        exp.LocationProperty: lambda self, e: self.naked_property(e),
 221        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 222        exp.MaskingProperty: lambda *_: "MASKING",
 223        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 224        exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
 225        exp.NetworkProperty: lambda *_: "NETWORK",
 226        exp.NonClusteredColumnConstraint: lambda self, e: (
 227            f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})"
 228        ),
 229        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 230        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 231        exp.OnCommitProperty: lambda _, e: (
 232            f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS"
 233        ),
 234        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 235        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 236        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 237        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 238        exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
 239        exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
 240        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 241        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 242        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 243        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 244        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 245        exp.ProjectionPolicyColumnConstraint: lambda self, e: (
 246            f"PROJECTION POLICY {self.sql(e, 'this')}"
 247        ),
 248        exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE",
 249        exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
 250        exp.Put: lambda self, e: self.get_put_sql(e),
 251        exp.RemoteWithConnectionModelProperty: lambda self, e: (
 252            f"REMOTE WITH CONNECTION {self.sql(e, 'this')}"
 253        ),
 254        exp.ReturnsProperty: lambda self, e: (
 255            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 256        ),
 257        exp.RowAccessProperty: lambda *_: "ROW ACCESS",
 258        exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
 259        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 260        exp.SecureProperty: lambda *_: "SECURE",
 261        exp.SecurityIntegrationProperty: lambda *_: "SECURITY",
 262        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 263        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 264        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 265        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 266        exp.SqlReadWriteProperty: lambda _, e: e.name,
 267        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
 268        exp.StabilityProperty: lambda _, e: e.name,
 269        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 270        exp.StreamingTableProperty: lambda *_: "STREAMING",
 271        exp.StrictProperty: lambda *_: "STRICT",
 272        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 273        exp.TableColumn: lambda self, e: self.sql(e.this),
 274        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 275        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 276        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 277        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 278        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 279        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 280        exp.TransientProperty: lambda *_: "TRANSIENT",
 281        exp.VirtualProperty: lambda *_: "VIRTUAL",
 282        exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}",
 283        exp.Union: lambda self, e: self.set_operations(e),
 284        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 285        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 286        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 287        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 288        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 289        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 290        exp.UtcTimestamp: lambda self, e: self.sql(
 291            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 292        ),
 293        exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
 294        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 295        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 296        exp.VolatileProperty: lambda *_: "VOLATILE",
 297        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 298        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 299        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 300        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 301        exp.ForceProperty: lambda *_: "FORCE",
 302    }
 303
 304    # Whether null ordering is supported in order by
 305    # True: Full Support, None: No support, False: No support for certain cases
 306    # such as window specifications, aggregate functions etc
 307    NULL_ORDERING_SUPPORTED: bool | None = True
 308
 309    # Window functions that support NULLS FIRST/LAST
 310    WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = ()
 311
 312    # Whether ignore nulls is inside the agg or outside.
 313    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 314    IGNORE_NULLS_IN_FUNC = False
 315
 316    # Whether IGNORE NULLS is placed before ORDER BY in the agg.
 317    # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS)
 318    IGNORE_NULLS_BEFORE_ORDER = True
 319
 320    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 321    LOCKING_READS_SUPPORTED = False
 322
 323    # Whether the EXCEPT and INTERSECT operations can return duplicates
 324    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 325
 326    # Wrap derived values in parens, usually standard but spark doesn't support it
 327    WRAP_DERIVED_VALUES = True
 328
 329    # Whether create function uses an AS before the RETURN
 330    CREATE_FUNCTION_RETURN_AS = True
 331
 332    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 333    MATCHED_BY_SOURCE = True
 334
 335    # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported
 336    SUPPORTS_MERGE_WHERE = False
 337
 338    # Whether MERGE uses ALL BY NAME instead of a star to update or insert all columns
 339    MERGE_ALL_BY_NAME = False
 340
 341    # Whether the INTERVAL expression works only with values like '1 day'
 342    SINGLE_STRING_INTERVAL = False
 343
 344    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 345    INTERVAL_ALLOWS_PLURAL_FORM = True
 346
 347    # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the
 348    # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND
 349    AUTO_REFRESH_BARE_INTERVALS = False
 350
 351    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 352    LIMIT_FETCH = "ALL"
 353
 354    # Whether limit and fetch allows expresions or just limits
 355    LIMIT_ONLY_LITERALS = False
 356
 357    # Whether a table is allowed to be renamed with a db
 358    RENAME_TABLE_WITH_DB = True
 359
 360    # The separator for grouping sets and rollups
 361    GROUPINGS_SEP = ","
 362
 363    # Whether GROUPING SETS can follow GROUP BY expressions without a comma
 364    SUPPORTS_GROUPING_SETS_AS_SUFFIX = False
 365
 366    # The string used for creating an index on a table
 367    INDEX_ON = "ON"
 368
 369    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 370    INOUT_SEPARATOR = " "
 371
 372    # Whether join hints should be generated
 373    JOIN_HINTS = True
 374
 375    # Whether directed joins are supported
 376    DIRECTED_JOINS = False
 377
 378    # Whether top-k distance/similarity joins are supported
 379    SUPPORTS_NEAREST_JOIN = False
 380
 381    # Whether table hints should be generated
 382    TABLE_HINTS = True
 383
 384    # Whether query hints should be generated
 385    QUERY_HINTS = True
 386
 387    # What kind of separator to use for query hints
 388    QUERY_HINT_SEP = ", "
 389
 390    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 391    IS_BOOL_ALLOWED = True
 392
 393    # Whether casting a fractional number to an integer rounds it instead of truncating it
 394    CAST_TO_INT_ROUNDS = False
 395
 396    # Whether the native integer division operator returns NULL for a zero divisor instead of raising
 397    SAFE_INT_DIVISION = False
 398
 399    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 400    DUPLICATE_KEY_UPDATE_WITH_SET = True
 401
 402    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 403    LIMIT_IS_TOP = False
 404
 405    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 406    RETURNING_END = True
 407
 408    # Whether to generate an unquoted value for EXTRACT's date part argument
 409    EXTRACT_ALLOWS_QUOTES = True
 410
 411    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 412    TZ_TO_WITH_TIME_ZONE = False
 413
 414    # Whether the NVL2 function is supported
 415    NVL2_SUPPORTED = True
 416
 417    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 418    SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE")
 419
 420    # Whether VALUES statements can be used as derived tables.
 421    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 422    # SELECT * VALUES into SELECT UNION
 423    VALUES_AS_TABLE = True
 424
 425    # Whether COLUMN/COLUMNS keywords are included when adding columns with ALTER TABLE.
 426    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 427
 428    # Whether ALTER TABLE mutli-column additions are wrapped in parentheses.
 429    ALTER_TABLE_ADD_COLUMNS_WRAPPED = True
 430
 431    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 432    UNNEST_WITH_ORDINALITY = True
 433
 434    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 435    SEMI_ANTI_JOIN_WITH_SIDE = True
 436
 437    # Whether to include the type of a computed column in the CREATE DDL
 438    COMPUTED_COLUMN_WITH_TYPE = True
 439
 440    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 441    SUPPORTS_TABLE_COPY = True
 442
 443    # Whether parentheses are required around the table sample's expression
 444    TABLESAMPLE_REQUIRES_PARENS = True
 445
 446    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 447    TABLESAMPLE_SIZE_IS_ROWS = True
 448
 449    # The keyword(s) to use when generating a sample clause
 450    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 451
 452    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 453    TABLESAMPLE_WITH_METHOD = True
 454
 455    # The keyword to use when specifying the seed of a sample clause
 456    TABLESAMPLE_SEED_KEYWORD = "SEED"
 457
 458    # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias
 459    HISTORICAL_DATA_POST_ALIAS = False
 460
 461    # Whether COLLATE is a function instead of a binary operator
 462    COLLATE_IS_FUNC = False
 463
 464    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 465    DATA_TYPE_SPECIFIERS_ALLOWED = False
 466
 467    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 468    ENSURE_BOOLS = False
 469
 470    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 471    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 472
 473    # Whether CONCAT requires >1 arguments
 474    SUPPORTS_SINGLE_ARG_CONCAT = True
 475
 476    # Whether LAST_DAY function supports a date part argument
 477    LAST_DAY_SUPPORTS_DATE_PART = True
 478
 479    # Whether named columns are allowed in table aliases
 480    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 481
 482    # Whether named columns are allowed in CTE definitions
 483    SUPPORTS_NAMED_CTE_COLUMNS = True
 484
 485    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 486    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 487
 488    # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048)
 489    PIVOT_ALIAS_WITH_AS = True
 490
 491    # What delimiter to use for separating JSON key/value pairs
 492    JSON_KEY_VALUE_PAIR_SEP = ":"
 493
 494    # INSERT OVERWRITE TABLE x override
 495    INSERT_OVERWRITE = " OVERWRITE TABLE"
 496
 497    # Whether the SELECT .. INTO syntax is used instead of CTAS
 498    SUPPORTS_SELECT_INTO = False
 499
 500    # Whether UNLOGGED tables can be created
 501    SUPPORTS_UNLOGGED_TABLES = False
 502
 503    # Whether the CREATE TABLE LIKE statement is supported
 504    SUPPORTS_CREATE_TABLE_LIKE = True
 505
 506    # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 507    SUPPORTS_MODIFY_COLUMN = False
 508
 509    # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported
 510    SUPPORTS_CHANGE_COLUMN = False
 511
 512    # Whether ALTER COLUMN can set a column's nullability together with its type
 513    SUPPORTS_ALTER_COLUMN_NULLABILITY = False
 514
 515    # Whether ALTER COLUMN IF EXISTS is supported
 516    SUPPORTS_ALTER_COLUMN_IF_EXISTS = False
 517
 518    # Whether the LikeProperty needs to be specified inside of the schema clause
 519    LIKE_PROPERTY_INSIDE_SCHEMA = False
 520
 521    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 522    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 523    MULTI_ARG_DISTINCT = True
 524
 525    # Whether the JSON extraction operators expect a value of type JSON
 526    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 527
 528    # Whether bracketed keys like ["foo"] are supported in JSON paths
 529    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 530
 531    # Whether to escape keys using single quotes in JSON paths
 532    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 533
 534    # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be
 535    # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match
 536    # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks
 537    # where a bare colon key is case-insensitive but a bracketed key is case-sensitive.
 538    JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False
 539
 540    # The JSONPathPart expressions supported by this dialect
 541    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 542
 543    # Whether any(f(x) for x in array) can be implemented by this dialect
 544    CAN_IMPLEMENT_ARRAY_ANY = False
 545
 546    # Whether the function TO_NUMBER is supported
 547    SUPPORTS_TO_NUMBER = True
 548
 549    # Whether EXCLUDE in window specification is supported
 550    SUPPORTS_WINDOW_EXCLUDE = False
 551
 552    # Whether or not set op modifiers apply to the outer set op or select.
 553    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 554    # True means limit 1 happens after the set op, False means it it happens on y.
 555    SET_OP_MODIFIERS = True
 556
 557    # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses.
 558    SET_OP_LIMITS = False
 559
 560    # Whether set operation operands can be parenthesized without a SELECT wrapper.
 561    SET_OP_PARENTHESIZED_OPERANDS = True
 562
 563    # Whether parameters from COPY statement are wrapped in parentheses
 564    COPY_PARAMS_ARE_WRAPPED = True
 565
 566    # Whether values of params are set with "=" token or empty space
 567    COPY_PARAMS_EQ_REQUIRED = False
 568
 569    # Whether COPY statement has INTO keyword
 570    COPY_HAS_INTO_KEYWORD = True
 571
 572    # Whether the conditional TRY(expression) function is supported
 573    TRY_SUPPORTED = True
 574
 575    # Whether the UESCAPE syntax in unicode strings is supported
 576    SUPPORTS_UESCAPE = True
 577
 578    # Function used to replace escaped unicode codes in unicode strings
 579    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 580
 581    # The keyword to use when generating a star projection with excluded columns
 582    STAR_EXCEPT = "EXCEPT"
 583
 584    # The HEX function name
 585    HEX_FUNC = "HEX"
 586
 587    # Function that constructs a binary value from a hex-encoded byte string.
 588    BYTE_STRING_FUNCTION = ""
 589
 590    # The keywords to use when prefixing & separating WITH based properties
 591    WITH_PROPERTIES_PREFIX = "WITH"
 592
 593    # Whether to quote the generated expression of exp.JsonPath
 594    QUOTE_JSON_PATH = True
 595
 596    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 597    PAD_FILL_PATTERN_IS_REQUIRED = False
 598
 599    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 600    SUPPORTS_EXPLODING_PROJECTIONS = True
 601
 602    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 603    ARRAY_CONCAT_IS_VAR_LEN = True
 604
 605    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 606    SUPPORTS_CONVERT_TIMEZONE = False
 607
 608    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 609    SUPPORTS_MEDIAN = True
 610
 611    # Whether UNIX_SECONDS(timestamp) is supported
 612    SUPPORTS_UNIX_SECONDS = False
 613
 614    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 615    ALTER_SET_WRAPPED = False
 616
 617    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 618    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 619    # TODO: The normalization should be done by default once we've tested it across all dialects.
 620    NORMALIZE_EXTRACT_DATE_PARTS = False
 621
 622    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 623    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 624
 625    # The function name of the exp.ArraySize expression
 626    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 627
 628    # The syntax to use when altering the type of a column
 629    ALTER_SET_TYPE = "SET DATA TYPE"
 630
 631    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 632    # None -> Doesn't support it at all
 633    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 634    # True (Postgres) -> Explicitly requires it
 635    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 636
 637    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 638    SUPPORTS_DECODE_CASE = True
 639
 640    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 641    SUPPORTS_BETWEEN_FLAGS = False
 642
 643    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 644    SUPPORTS_LIKE_QUANTIFIERS = True
 645
 646    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 647    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 648
 649    # Whether to include the VARIABLE keyword for SET assignments
 650    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 651
 652    # The keyword to use for default value assignment in DECLARE statements
 653    DECLARE_DEFAULT_ASSIGNMENT = "="
 654
 655    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 656    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 657    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 658    UPDATE_STATEMENT_SUPPORTS_FROM = True
 659
 660    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 661    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 662
 663    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 664    # - Snowflake: DROP ICEBERG TABLE a.b;
 665    # - DuckDB:    DROP TABLE a.b;
 666    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 667
 668    TYPE_MAPPING: t.ClassVar = {
 669        exp.DType.DATETIME2: "TIMESTAMP",
 670        exp.DType.NCHAR: "CHAR",
 671        exp.DType.NVARCHAR: "VARCHAR",
 672        exp.DType.MEDIUMTEXT: "TEXT",
 673        exp.DType.LONGTEXT: "TEXT",
 674        exp.DType.TINYTEXT: "TEXT",
 675        exp.DType.BLOB: "VARBINARY",
 676        exp.DType.MEDIUMBLOB: "BLOB",
 677        exp.DType.LONGBLOB: "BLOB",
 678        exp.DType.TINYBLOB: "BLOB",
 679        exp.DType.INET: "INET",
 680        exp.DType.ROWVERSION: "VARBINARY",
 681        exp.DType.SMALLDATETIME: "TIMESTAMP",
 682    }
 683
 684    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 685
 686    # mapping of DType to its default parameters, bounds
 687    TYPE_PARAM_SETTINGS: t.ClassVar[
 688        dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]
 689    ] = {}
 690
 691    TIME_PART_SINGULARS: t.ClassVar = {
 692        "MICROSECONDS": "MICROSECOND",
 693        "SECONDS": "SECOND",
 694        "MINUTES": "MINUTE",
 695        "HOURS": "HOUR",
 696        "DAYS": "DAY",
 697        "WEEKS": "WEEK",
 698        "MONTHS": "MONTH",
 699        "QUARTERS": "QUARTER",
 700        "YEARS": "YEAR",
 701    }
 702
 703    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 704        "cluster": lambda self, e: self.sql(e, "cluster"),
 705        "distribute": lambda self, e: self.sql(e, "distribute"),
 706        "sort": lambda self, e: self.sql(e, "sort"),
 707        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 708    }
 709
 710    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 711
 712    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 713
 714    PARAMETER_TOKEN = "@"
 715    NAMED_PLACEHOLDER_TOKEN = ":"
 716
 717    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 718
 719    PROPERTIES_LOCATION: t.ClassVar = {
 720        exp.AggregateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 721        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 722        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 723        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 724        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 725        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 726        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 727        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 728        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 729        exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA,
 730        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 731        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 732        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 733        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 734        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 735        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 736        exp.CopyTagsProperty: exp.Properties.Location.POST_SCHEMA,
 737        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 738        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 739        exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA,
 740        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 741        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 742        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 743        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 744        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 745        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 746        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 747        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 748        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 749        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 750        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 751        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 752        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 753        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 754        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 755        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 756        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 757        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 758        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 759        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 760        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 761        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 762        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 763        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 764        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 765        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 766        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 767        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 768        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 769        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 770        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 771        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 772        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 773        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 774        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 775        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 776        exp.LogProperty: exp.Properties.Location.POST_NAME,
 777        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 778        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 779        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 780        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 781        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 782        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 783        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 784        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 785        exp.Order: exp.Properties.Location.POST_SCHEMA,
 786        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 787        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 788        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 789        exp.PhysicalProperty: exp.Properties.Location.UNSUPPORTED,
 790        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 791        exp.Property: exp.Properties.Location.POST_WITH,
 792        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 793        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 794        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 795        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 796        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 797        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 798        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 799        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 800        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 801        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 802        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 803        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 804        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 805        exp.Set: exp.Properties.Location.POST_SCHEMA,
 806        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 807        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 808        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 809        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 810        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 811        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 812        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 813        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 814        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 815        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 816        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 817        exp.StorageProperty: exp.Properties.Location.UNSUPPORTED,
 818        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 819        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 820        exp.Tags: exp.Properties.Location.POST_WITH,
 821        exp.TableSpaceProperty: exp.Properties.Location.UNSUPPORTED,
 822        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 823        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 824        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 825        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 826        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 827        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 828        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 829        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 830        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 831        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 832        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 833        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 834        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 835        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 836        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 837        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 838        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 839    }
 840
 841    # Keywords that can't be used as unquoted identifier names
 842    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 843
 844    # Exprs whose comments are separated from them for better formatting
 845    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 846        exp.Command,
 847        exp.Create,
 848        exp.Describe,
 849        exp.Delete,
 850        exp.Drop,
 851        exp.From,
 852        exp.Insert,
 853        exp.Join,
 854        exp.MultitableInserts,
 855        exp.Order,
 856        exp.Group,
 857        exp.Having,
 858        exp.Select,
 859        exp.SetOperation,
 860        exp.Update,
 861        exp.Where,
 862        exp.With,
 863    )
 864
 865    # Exprs that should not have their comments generated in maybe_comment
 866    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 867        exp.Binary,
 868        exp.SetOperation,
 869    )
 870
 871    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 872    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 873        exp.Column,
 874        exp.Literal,
 875        exp.Neg,
 876        exp.Paren,
 877    )
 878
 879    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 880        exp.DType.NVARCHAR,
 881        exp.DType.VARCHAR,
 882        exp.DType.CHAR,
 883        exp.DType.NCHAR,
 884    }
 885
 886    # Exprs that need to have all CTEs under them bubbled up to them
 887    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 888
 889    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 890
 891    MOD_OPERATOR = "%"
 892
 893    # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses
 894    MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 895        exp.Mul,
 896        exp.Div,
 897        exp.IntDiv,
 898        exp.Mod,
 899    )
 900
 901    # ALTER options that follow the others without a comma, e.g. MySQL's
 902    # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ...
 903    ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 904
 905    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 906
 907    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 908
 909    __slots__ = (
 910        "pretty",
 911        "identify",
 912        "normalize",
 913        "pad",
 914        "_indent",
 915        "normalize_functions",
 916        "unsupported_level",
 917        "max_unsupported",
 918        "leading_comma",
 919        "max_text_width",
 920        "comments",
 921        "dialect",
 922        "unsupported_messages",
 923        "_escaped_quote_end",
 924        "_escaped_byte_quote_end",
 925        "_escaped_identifier_end",
 926        "_identifier_escaped_sequences",
 927        "_next_name",
 928        "_identifier_start",
 929        "_identifier_end",
 930        "_quote_json_path_key_using_brackets",
 931        "_dispatch",
 932    )
 933
 934    def __init__(
 935        self,
 936        pretty: bool | int | None = None,
 937        identify: str | bool = False,
 938        normalize: bool = False,
 939        pad: int = 2,
 940        indent: int = 2,
 941        normalize_functions: str | bool | None = None,
 942        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 943        max_unsupported: int = 3,
 944        leading_comma: bool = False,
 945        max_text_width: int = 80,
 946        comments: bool = True,
 947        dialect: DialectType = None,
 948    ):
 949        import sqlglot
 950        import sqlglot.dialects.dialect
 951
 952        self.pretty = pretty if pretty is not None else sqlglot.pretty
 953        self.identify = identify
 954        self.normalize = normalize
 955        self.pad = pad
 956        self._indent = indent
 957        self.unsupported_level = unsupported_level
 958        self.max_unsupported = max_unsupported
 959        self.leading_comma = leading_comma
 960        self.max_text_width = max_text_width
 961        self.comments = comments
 962        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 963
 964        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 965        self.normalize_functions = (
 966            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 967        )
 968
 969        self.unsupported_messages: list[str] = []
 970        self._escaped_quote_end: str = (
 971            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 972        )
 973        self._escaped_byte_quote_end: str = (
 974            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 975            if self.dialect.BYTE_END
 976            else ""
 977        )
 978        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 979        self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES
 980
 981        self._next_name = name_sequence("_t")
 982
 983        self._identifier_start = self.dialect.IDENTIFIER_START
 984        self._identifier_end = self.dialect.IDENTIFIER_END
 985
 986        self._quote_json_path_key_using_brackets = True
 987
 988        cls = type(self)
 989        dispatch = _DISPATCH_CACHE.get(cls)
 990        if dispatch is None:
 991            dispatch = _build_dispatch(cls)
 992            _DISPATCH_CACHE[cls] = dispatch
 993        self._dispatch = dispatch
 994
 995    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 996        """
 997        Generates the SQL string corresponding to the given syntax tree.
 998
 999        Args:
1000            expression: The syntax tree.
1001            copy: Whether to copy the expression. The generator performs mutations so
1002                it is safer to copy.
1003
1004        Returns:
1005            The SQL string corresponding to `expression`.
1006        """
1007        if copy:
1008            expression = expression.copy()
1009
1010        expression = self.preprocess(expression)
1011
1012        self.unsupported_messages = []
1013        sql = self.sql(expression).strip()
1014
1015        if self.pretty:
1016            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
1017
1018        if self.unsupported_level == ErrorLevel.IGNORE:
1019            return sql
1020
1021        if self.unsupported_level == ErrorLevel.WARN:
1022            for msg in self.unsupported_messages:
1023                logger.warning(msg)
1024        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
1025            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
1026
1027        return sql
1028
1029    def preprocess(self, expression: exp.Expr) -> exp.Expr:
1030        """Apply generic preprocessing transformations to a given expression."""
1031        expression = self._move_ctes_to_top_level(expression)
1032
1033        if self.ENSURE_BOOLS:
1034            import sqlglot.transforms
1035
1036            expression = sqlglot.transforms.ensure_bools(expression)
1037
1038        return expression
1039
1040    def _move_ctes_to_top_level(self, expression: E) -> E:
1041        if (
1042            not expression.parent
1043            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
1044            and any(node.parent is not expression for node in expression.find_all(exp.With))
1045        ):
1046            import sqlglot.transforms
1047
1048            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
1049        return expression
1050
1051    def unsupported(self, message: str) -> None:
1052        if self.unsupported_level == ErrorLevel.IMMEDIATE:
1053            raise UnsupportedError(message)
1054        self.unsupported_messages.append(message)
1055
1056    def sep(self, sep: str = " ") -> str:
1057        return f"{sep.strip()}\n" if self.pretty else sep
1058
1059    def seg(self, sql: str, sep: str = " ") -> str:
1060        return f"{self.sep(sep)}{sql}"
1061
1062    def sanitize_comment(self, comment: str) -> str:
1063        comment = " " + comment if comment[0].strip() else comment
1064        comment = comment + " " if comment[-1].strip() else comment
1065
1066        # Escape block comment markers to prevent premature closure or unintended nesting.
1067        # This is necessary because single-line comments (--) are converted to block comments
1068        # (/* */) on output, and any */ in the original text would close the comment early.
1069        comment = comment.replace("*/", "* /").replace("/*", "/ *")
1070
1071        return comment
1072
1073    def maybe_comment(
1074        self,
1075        sql: str,
1076        expression: exp.Expr | None = None,
1077        comments: list[str] | None = None,
1078        separated: bool = False,
1079    ) -> str:
1080        comments = (
1081            ((expression and expression.comments) if comments is None else comments)  # type: ignore
1082            if self.comments
1083            else None
1084        )
1085
1086        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
1087            return sql
1088
1089        comments_list = [
1090            f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/"
1091            for comment in comments
1092            if comment
1093        ]
1094
1095        if not comments_list:
1096            return sql
1097
1098        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1099            comments_sql = self.sep().join(comments_list)
1100            return (
1101                f"{self.sep()}{comments_sql}{sql}"
1102                if not sql or sql[0].isspace()
1103                else f"{comments_sql}{self.sep()}{sql}"
1104            )
1105
1106        return f"{sql} {' '.join(comments_list)}"
1107
1108    def wrap(self, expression: exp.Expr | str) -> str:
1109        this_sql = (
1110            self.sql(expression)
1111            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1112            else self.sql(expression, "this")
1113        )
1114        if not this_sql:
1115            return "()"
1116
1117        this_sql = self.indent(this_sql, level=1, pad=0)
1118        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1119
1120    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1121        original = self.identify
1122        self.identify = False
1123        result = func(*args, **kwargs)
1124        self.identify = original
1125        return result
1126
1127    def normalize_func(self, name: str) -> str:
1128        if self.normalize_functions == "upper" or self.normalize_functions is True:
1129            return name.upper()
1130        if self.normalize_functions == "lower":
1131            return name.lower()
1132        return name
1133
1134    def indent(
1135        self,
1136        sql: str,
1137        level: int = 0,
1138        pad: int | None = None,
1139        skip_first: bool = False,
1140        skip_last: bool = False,
1141    ) -> str:
1142        if not self.pretty or not sql:
1143            return sql
1144
1145        pad = self.pad if pad is None else pad
1146        lines = sql.split("\n")
1147
1148        return "\n".join(
1149            (
1150                line
1151                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1152                else f"{' ' * (level * self._indent + pad)}{line}"
1153            )
1154            for i, line in enumerate(lines)
1155        )
1156
1157    def sql(
1158        self,
1159        expression: str | exp.Expr | None,
1160        key: str | None = None,
1161        comment: bool = True,
1162    ) -> str:
1163        if not expression:
1164            return ""
1165
1166        if isinstance(expression, str):
1167            return expression
1168
1169        if key:
1170            value = expression.args.get(key)
1171            if value:
1172                return self.sql(value)
1173            return ""
1174
1175        handler = self._dispatch.get(expression.__class__)
1176
1177        if handler:
1178            sql = handler(self, expression)
1179        elif isinstance(expression, exp.Func):
1180            sql = self.function_fallback_sql(expression)
1181        elif isinstance(expression, exp.Property):
1182            sql = self.property_sql(expression)
1183        else:
1184            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1185
1186        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1187
1188    def uncache_sql(self, expression: exp.Uncache) -> str:
1189        table = self.sql(expression, "this")
1190        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1191        return f"UNCACHE TABLE{exists_sql} {table}"
1192
1193    def cache_sql(self, expression: exp.Cache) -> str:
1194        lazy = " LAZY" if expression.args.get("lazy") else ""
1195        table = self.sql(expression, "this")
1196        options = expression.args.get("options")
1197        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1198        sql = self.sql(expression, "expression")
1199        sql = f" AS{self.sep()}{sql}" if sql else ""
1200        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1201        return self.prepend_ctes(expression, sql)
1202
1203    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1204        default = "DEFAULT " if expression.args.get("default") else ""
1205        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1206
1207    def column_parts(self, expression: exp.Column) -> str:
1208        if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES:
1209            # The qualifier would be captured by a colliding projection alias (see qualify_columns)
1210            return self.sql(expression, "this")
1211
1212        return ".".join(
1213            self.sql(part)
1214            for part in (
1215                expression.args.get("catalog"),
1216                expression.args.get("db"),
1217                expression.args.get("table"),
1218                expression.args.get("this"),
1219            )
1220            if part
1221        )
1222
1223    def column_sql(self, expression: exp.Column) -> str:
1224        join_mark = " (+)" if expression.args.get("join_mark") else ""
1225
1226        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1227            join_mark = ""
1228            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1229
1230        return f"{self.column_parts(expression)}{join_mark}"
1231
1232    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1233        return self.column_sql(expression)
1234
1235    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1236        this = self.sql(expression, "this")
1237        this = f" {this}" if this else ""
1238        position = self.sql(expression, "position")
1239        return f"{position}{this}"
1240
1241    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1242        column = self.sql(expression, "this")
1243        kind = self.sql(expression, "kind")
1244        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1245        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1246        kind = f"{sep}{kind}" if kind else ""
1247        constraints = f" {constraints}" if constraints else ""
1248        position = self.sql(expression, "position")
1249        position = f" {position}" if position else ""
1250
1251        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1252            kind = ""
1253
1254        return f"{exists}{column}{kind}{constraints}{position}"
1255
1256    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1257        this = self.sql(expression, "this")
1258        kind_sql = self.sql(expression, "kind").strip()
1259        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1260
1261    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1262        this = self.sql(expression, "this")
1263        if expression.args.get("not_null"):
1264            persisted = " PERSISTED NOT NULL"
1265        elif expression.args.get("persisted"):
1266            persisted = " PERSISTED"
1267        else:
1268            persisted = ""
1269
1270        return f"AS {this}{persisted}"
1271
1272    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1273        return self.token_sql(TokenType.AUTO_INCREMENT)
1274
1275    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1276        if isinstance(expression.this, list):
1277            this = self.wrap(self.expressions(expression, key="this", flat=True))
1278        else:
1279            this = self.sql(expression, "this")
1280
1281        return f"COMPRESS {this}"
1282
1283    def generatedasidentitycolumnconstraint_sql(
1284        self, expression: exp.GeneratedAsIdentityColumnConstraint
1285    ) -> str:
1286        this = ""
1287        if expression.this is not None:
1288            on_null = " ON NULL" if expression.args.get("on_null") else ""
1289            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1290
1291        start = expression.args.get("start")
1292        start = f"START WITH {start}" if start else ""
1293        increment = expression.args.get("increment")
1294        increment = f" INCREMENT BY {increment}" if increment else ""
1295        minvalue = expression.args.get("minvalue")
1296        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1297        maxvalue = expression.args.get("maxvalue")
1298        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1299        cycle = expression.args.get("cycle")
1300        cycle_sql = ""
1301
1302        if cycle is not None:
1303            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1304            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1305
1306        sequence_opts = ""
1307        if start or increment or cycle_sql:
1308            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1309            sequence_opts = f" ({sequence_opts.strip()})"
1310
1311        expr = self.sql(expression, "expression")
1312        expr = f"({expr})" if expr else "IDENTITY"
1313
1314        return f"GENERATED{this} AS {expr}{sequence_opts}"
1315
1316    def generatedasrowcolumnconstraint_sql(
1317        self, expression: exp.GeneratedAsRowColumnConstraint
1318    ) -> str:
1319        start = "START" if expression.args.get("start") else "END"
1320        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1321        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1322
1323    def periodforsystemtimeconstraint_sql(
1324        self, expression: exp.PeriodForSystemTimeConstraint
1325    ) -> str:
1326        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1327
1328    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1329        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1330
1331    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1332        desc = expression.args.get("desc")
1333        if desc is not None:
1334            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1335        options = self.expressions(expression, key="options", flat=True, sep=" ")
1336        options = f" {options}" if options else ""
1337        include = self.sql(expression, "include")
1338        return f"PRIMARY KEY{include}{options}"
1339
1340    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1341        this = self.sql(expression, "this")
1342        this = f" {this}" if this else ""
1343        index_type = expression.args.get("index_type")
1344        index_type = f" USING {index_type}" if index_type else ""
1345        on_conflict = self.sql(expression, "on_conflict")
1346        on_conflict = f" {on_conflict}" if on_conflict else ""
1347        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1348        options = self.expressions(expression, key="options", flat=True, sep=" ")
1349        options = f" {options}" if options else ""
1350        include = self.sql(expression, "include")
1351        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{include}{options}"
1352
1353    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1354        input_ = expression.args.get("input_")
1355        output = expression.args.get("output")
1356        variadic = expression.args.get("variadic")
1357
1358        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1359        if variadic:
1360            return "VARIADIC"
1361
1362        if input_ and output:
1363            return f"IN{self.INOUT_SEPARATOR}OUT"
1364        if input_:
1365            return "IN"
1366        if output:
1367            return "OUT"
1368
1369        return ""
1370
1371    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1372        return self.sql(expression, "this")
1373
1374    def create_sql(self, expression: exp.Create) -> str:
1375        kind = self.sql(expression, "kind")
1376        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1377
1378        properties = expression.args.get("properties")
1379
1380        if (
1381            kind == "TRIGGER"
1382            and properties
1383            and properties.expressions
1384            and isinstance(properties.expressions[0], exp.TriggerProperties)
1385            and properties.expressions[0].args.get("constraint")
1386        ):
1387            kind = f"CONSTRAINT {kind}"
1388
1389        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1390
1391        this = self.createable_sql(expression, properties_locs)
1392
1393        properties_sql = ""
1394        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1395            exp.Properties.Location.POST_WITH
1396        ):
1397            props_ast = exp.Properties(
1398                expressions=[
1399                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1400                    *properties_locs[exp.Properties.Location.POST_WITH],
1401                ]
1402            )
1403            props_ast.parent = expression
1404            properties_sql = self.sql(props_ast)
1405
1406            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1407                properties_sql = self.sep() + properties_sql
1408            elif not self.pretty:
1409                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1410                properties_sql = f" {properties_sql}"
1411
1412        begin = " BEGIN" if expression.args.get("begin") else ""
1413
1414        expression_sql = self.sql(expression, "expression")
1415        if expression_sql:
1416            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1417
1418            if not isinstance(expression.expression, exp.MacroOverloads) and (
1419                self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return)
1420            ):
1421                postalias_props_sql = ""
1422                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1423                    postalias_props_sql = self.properties(
1424                        exp.Properties(
1425                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1426                        ),
1427                        wrapped=False,
1428                    )
1429                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1430                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1431
1432        postindex_props_sql = ""
1433        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1434            postindex_props_sql = self.properties(
1435                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1436                wrapped=False,
1437                prefix=" ",
1438            )
1439
1440        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1441        indexes = f" {indexes}" if indexes else ""
1442        index_sql = indexes + postindex_props_sql
1443
1444        replace = " OR REPLACE" if expression.args.get("replace") else ""
1445        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1446        unique = " UNIQUE" if expression.args.get("unique") else ""
1447
1448        clustered = expression.args.get("clustered")
1449        if clustered is None:
1450            clustered_sql = ""
1451        elif clustered:
1452            clustered_sql = " CLUSTERED COLUMNSTORE"
1453        else:
1454            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1455
1456        postcreate_props_sql = ""
1457        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1458            postcreate_props_sql = self.properties(
1459                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1460                sep=" ",
1461                prefix=" ",
1462                wrapped=False,
1463            )
1464
1465        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1466
1467        postexpression_props_sql = ""
1468        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1469            postexpression_props_sql = self.properties(
1470                exp.Properties(
1471                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1472                ),
1473                sep=" ",
1474                prefix=" ",
1475                wrapped=False,
1476            )
1477
1478        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1479        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1480        no_schema_binding = (
1481            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1482        )
1483
1484        clone = self.sql(expression, "clone")
1485        clone = f" {clone}" if clone else ""
1486
1487        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1488            properties_expression = f"{expression_sql}{properties_sql}"
1489        else:
1490            properties_expression = f"{properties_sql}{expression_sql}"
1491
1492        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1493        return self.prepend_ctes(expression, expression_sql)
1494
1495    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1496        start = self.sql(expression, "start")
1497        start = f"START WITH {start}" if start else ""
1498        increment = self.sql(expression, "increment")
1499        increment = f" INCREMENT BY {increment}" if increment else ""
1500        minvalue = self.sql(expression, "minvalue")
1501        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1502        maxvalue = self.sql(expression, "maxvalue")
1503        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1504        owned = self.sql(expression, "owned")
1505        owned = f" OWNED BY {owned}" if owned else ""
1506
1507        cache = expression.args.get("cache")
1508        if cache is None:
1509            cache_str = ""
1510        elif cache is True:
1511            cache_str = " CACHE"
1512        else:
1513            cache_str = f" CACHE {cache}"
1514
1515        options = self.expressions(expression, key="options", flat=True, sep=" ")
1516        options = f" {options}" if options else ""
1517
1518        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1519
1520    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1521        timing = expression.args.get("timing", "")
1522        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1523        timing_events = f"{timing} {events}".strip() if timing or events else ""
1524
1525        parts = [timing_events, "ON", self.sql(expression, "table")]
1526
1527        if referenced_table := expression.args.get("referenced_table"):
1528            parts.extend(["FROM", self.sql(referenced_table)])
1529
1530        if deferrable := expression.args.get("deferrable"):
1531            parts.append(deferrable)
1532
1533        if initially := expression.args.get("initially"):
1534            parts.append(f"INITIALLY {initially}")
1535
1536        if referencing := expression.args.get("referencing"):
1537            parts.append(self.sql(referencing))
1538
1539        if for_each := expression.args.get("for_each"):
1540            parts.append(f"FOR EACH {for_each}")
1541
1542        if when := expression.args.get("when"):
1543            parts.append(f"WHEN ({self.sql(when)})")
1544
1545        parts.append(self.sql(expression, "execute"))
1546
1547        return self.sep().join(parts)
1548
1549    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1550        parts = []
1551
1552        if old_alias := expression.args.get("old"):
1553            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1554
1555        if new_alias := expression.args.get("new"):
1556            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1557
1558        return f"REFERENCING {' '.join(parts)}"
1559
1560    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1561        columns = expression.args.get("columns")
1562        if columns:
1563            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1564
1565        return self.sql(expression, "this")
1566
1567    def clone_sql(self, expression: exp.Clone) -> str:
1568        this = self.sql(expression, "this")
1569        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1570        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1571        return f"{shallow}{keyword} {this}"
1572
1573    def describe_sql(self, expression: exp.Describe) -> str:
1574        style = expression.args.get("style")
1575        style = f" {style}" if style else ""
1576        partition = self.sql(expression, "partition")
1577        partition = f" {partition}" if partition else ""
1578        format = self.sql(expression, "format")
1579        format = f" {format}" if format else ""
1580        as_json = " AS JSON" if expression.args.get("as_json") else ""
1581
1582        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1583
1584    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1585        tag = self.sql(expression, "tag")
1586        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1587
1588    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1589        with_ = self.sql(expression, "with_")
1590        if with_:
1591            sql = f"{with_}{self.sep()}{sql}"
1592        return sql
1593
1594    def with_sql(self, expression: exp.With) -> str:
1595        udfs = self.expressions(expression, key="udfs", flat=True)
1596        udfs = f"WITH {udfs}" if udfs else ""
1597
1598        sql = self.expressions(expression, flat=True)
1599
1600        recursive = (
1601            "RECURSIVE "
1602            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1603            else ""
1604        )
1605        search = self.sql(expression, "search")
1606        search = f" {search}" if search else ""
1607        cycle = self.sql(expression, "cycle")
1608        cycle = f" {cycle}" if cycle else ""
1609
1610        sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else ""
1611        return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1612
1613    def cte_sql(self, expression: exp.CTE) -> str:
1614        alias = expression.args.get("alias")
1615        if alias:
1616            alias.add_comments(expression.pop_comments())
1617
1618        alias_sql = self.sql(expression, "alias")
1619
1620        materialized = expression.args.get("materialized")
1621        if materialized is False:
1622            materialized = "NOT MATERIALIZED "
1623        elif materialized:
1624            materialized = "MATERIALIZED "
1625
1626        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1627        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1628
1629        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1630
1631    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1632        alias = self.sql(expression, "this")
1633        columns = self.expressions(expression, key="columns", flat=True)
1634        columns = f"({columns})" if columns else ""
1635
1636        if (
1637            columns
1638            and not self.SUPPORTS_TABLE_ALIAS_COLUMNS
1639            and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE))
1640        ):
1641            columns = ""
1642            self.unsupported("Named columns are not supported in table alias.")
1643
1644        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1645            alias = self._next_name()
1646
1647        return f"{alias}{columns}"
1648
1649    def bitstring_sql(self, expression: exp.BitString) -> str:
1650        this = self.sql(expression, "this")
1651        if self.dialect.BIT_START:
1652            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1653        return f"{int(this, 2)}"
1654
1655    def hexstring_sql(
1656        self, expression: exp.HexString, binary_function_repr: str | None = None
1657    ) -> str:
1658        this = self.sql(expression, "this")
1659        is_integer_type = expression.args.get("is_integer")
1660
1661        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1662            not self.dialect.HEX_START and not binary_function_repr
1663        ):
1664            # Integer representation will be returned if:
1665            # - The read dialect treats the hex value as integer literal but not the write
1666            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1667            return f"{int(this, 16)}"
1668
1669        if not is_integer_type:
1670            # Read dialect treats the hex value as BINARY/BLOB
1671            if binary_function_repr:
1672                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1673                return self.func(binary_function_repr, exp.Literal.string(this))
1674            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1675                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1676                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1677
1678        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1679
1680    def bytestring_sql(self, expression: exp.ByteString) -> str:
1681        this = self.sql(expression, "this")
1682        is_bytes = bool(expression.args.get("is_bytes"))
1683        if is_bytes and self.BYTE_STRING_FUNCTION:
1684            # Hex digits can be quoted directly without escaping.
1685            return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'")
1686
1687        if self.dialect.BYTE_START:
1688            escaped_byte_string = self.escape_str(
1689                this,
1690                escape_backslash=bool(
1691                    self.dialect.tokenizer_class.NUMERIC_ESCAPES
1692                    or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES
1693                ),
1694                delimiter=self.dialect.BYTE_END,
1695                escaped_delimiter=self._escaped_byte_quote_end,
1696                is_byte_string=True,
1697                is_bytes=is_bytes,
1698            )
1699            delimited_byte_string = (
1700                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1701            )
1702            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1703                return self.sql(
1704                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1705                )
1706            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1707                return self.sql(
1708                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1709                )
1710
1711            return delimited_byte_string
1712
1713        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1714            return self.sql(exp.Literal.string(this))
1715
1716        self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}")
1717        return ""
1718
1719    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1720        this = self.sql(expression, "this")
1721        escape = expression.args.get("escape")
1722        unicode_start = self.dialect.UNICODE_START
1723
1724        if unicode_start:
1725            escape_substitute = r"\\\1"
1726            left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or ""
1727        else:
1728            escape_substitute = r"\\u\1"
1729            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1730
1731        if escape:
1732            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1733            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1734        else:
1735            escape_pattern = ESCAPED_UNICODE_RE
1736            escape_sql = ""
1737
1738        if not unicode_start or (escape and not self.SUPPORTS_UESCAPE):
1739            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1740
1741        if unicode_start:
1742            # A Unicode literal only escapes its delimiter by doubling it; the escape character
1743            # introduces a code point, so the dialect's ordinary string escapes don't apply here
1744            this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2)
1745        else:
1746            this = self.escape_str(this, escape_backslash=False)
1747
1748        return f"{left_quote}{this}{right_quote}{escape_sql}"
1749
1750    def rawstring_sql(self, expression: exp.RawString) -> str:
1751        string = expression.this
1752        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1753            string = string.replace("\\", "\\\\")
1754
1755        string = self.escape_str(string, escape_backslash=False)
1756        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1757
1758    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1759        this = self.sql(expression, "this")
1760        specifier = self.sql(expression, "expression")
1761        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1762        return f"{this}{specifier}"
1763
1764    def datatype_param_bound_limiter(
1765        self,
1766        expression: exp.DataType,
1767        type_value: exp.DType,
1768        defaults: tuple[int, ...],
1769        bounds: tuple[int | None, ...],
1770    ) -> exp.DataType:
1771        params = expression.expressions
1772
1773        if not params:
1774            if defaults:
1775                expression.set(
1776                    "expressions",
1777                    [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults],
1778                )
1779            return expression
1780
1781        if not bounds:
1782            return expression
1783
1784        for i, param in enumerate(params):
1785            bound = bounds[i] if i < len(bounds) else None
1786            if bound is None:
1787                continue
1788
1789            param_value = param.this if isinstance(param, exp.DataTypeParam) else param
1790            value = (
1791                param_value.to_py()
1792                if isinstance(param_value, exp.Literal) and param_value.is_number
1793                else None
1794            )
1795            if isinstance(value, (int, Decimal)) and value > bound:
1796                self.unsupported(
1797                    f"{type_value.value} parameter {param_value.name} exceeds "
1798                    f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping"
1799                )
1800                params[i] = exp.DataTypeParam(this=exp.Literal.number(bound))
1801
1802        return expression
1803
1804    def datatype_sql(self, expression: exp.DataType) -> str:
1805        nested = ""
1806        values = ""
1807
1808        expr_nested = expression.args.get("nested")
1809        type_value = expression.this
1810
1811        if (
1812            not expr_nested
1813            and isinstance(type_value, exp.DType)
1814            and (settings := self.TYPE_PARAM_SETTINGS.get(type_value))
1815        ):
1816            expression = self.datatype_param_bound_limiter(expression, type_value, *settings)
1817
1818        interior = (
1819            self.expressions(
1820                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1821            )
1822            if expr_nested and self.pretty
1823            else self.expressions(expression, flat=True)
1824        )
1825
1826        if type_value in self.UNSUPPORTED_TYPES:
1827            self.unsupported(
1828                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1829            )
1830
1831        type_sql: t.Any = ""
1832        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1833            type_sql = self.sql(expression, "kind")
1834        elif type_value == exp.DType.CHARACTER_SET:
1835            char = f"CHAR({interior})" if interior else "CHAR"
1836            return f"{char} CHARACTER SET {self.sql(expression, 'kind')}"
1837        else:
1838            type_sql = (
1839                self.TYPE_MAPPING.get(type_value, type_value.value)
1840                if isinstance(type_value, exp.DType)
1841                else type_value
1842            )
1843
1844        if interior:
1845            if expr_nested:
1846                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1847                if expression.args.get("values") is not None:
1848                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1849                    values = self.expressions(expression, key="values", flat=True)
1850                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1851            elif type_value == exp.DType.INTERVAL:
1852                nested = f" {interior}"
1853            else:
1854                nested = f"({interior})"
1855
1856        type_sql = f"{type_sql}{nested}{values}"
1857        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1858            exp.DType.TIMETZ,
1859            exp.DType.TIMESTAMPTZ,
1860        ):
1861            type_sql = f"{type_sql} WITH TIME ZONE"
1862
1863        collate = self.sql(expression, "collate")
1864        if collate:
1865            type_sql = f"{type_sql} COLLATE {collate}"
1866
1867        return type_sql
1868
1869    def directory_sql(self, expression: exp.Directory) -> str:
1870        local = "LOCAL " if expression.args.get("local") else ""
1871        row_format = self.sql(expression, "row_format")
1872        row_format = f" {row_format}" if row_format else ""
1873        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1874
1875    def delete_sql(self, expression: exp.Delete) -> str:
1876        hint = self.sql(expression, "hint")
1877        this = self.sql(expression, "this")
1878        this = f" FROM {this}" if this else ""
1879        using = self.expressions(expression, key="using")
1880        using = f" USING {using}" if using else ""
1881        cluster = self.sql(expression, "cluster")
1882        cluster = f" {cluster}" if cluster else ""
1883        where = self.sql(expression, "where")
1884        returning = self.sql(expression, "returning")
1885        order = self.sql(expression, "order")
1886        limit = self.sql(expression, "limit")
1887        tables = self.expressions(expression, key="tables")
1888        tables = f" {tables}" if tables else ""
1889        if self.RETURNING_END:
1890            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1891        else:
1892            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1893        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1894
1895    def drop_sql(self, expression: exp.Drop) -> str:
1896        tables = self.expressions(expression, key="tables", flat=True)
1897        expressions = self.expressions(expression, flat=True)
1898        expressions = f" ({expressions})" if expressions else ""
1899        kind = expression.args["kind"]
1900        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1901        iceberg = (
1902            " ICEBERG"
1903            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1904            else ""
1905        )
1906        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1907        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1908        on_cluster = self.sql(expression, "cluster")
1909        on_cluster = f" {on_cluster}" if on_cluster else ""
1910        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1911        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1912        cascade = " CASCADE" if expression.args.get("cascade") else ""
1913        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1914        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1915        purge = " PURGE" if expression.args.get("purge") else ""
1916        sync = " SYNC" if expression.args.get("sync") else ""
1917        force = " FORCE" if expression.args.get("force") else ""
1918        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1919
1920    def set_operation(self, expression: exp.SetOperation) -> str:
1921        op_type = type(expression)
1922        op_name = op_type.key.upper()
1923
1924        distinct = expression.args.get("distinct")
1925        if (
1926            distinct is False
1927            and op_type in (exp.Except, exp.Intersect)
1928            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1929        ):
1930            self.unsupported(f"{op_name} ALL is not supported")
1931
1932        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1933
1934        if distinct is None:
1935            distinct = default_distinct
1936            if distinct is None:
1937                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1938
1939        if distinct is default_distinct:
1940            distinct_or_all = ""
1941        else:
1942            distinct_or_all = " DISTINCT" if distinct else " ALL"
1943
1944        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1945        side_kind = f"{side_kind} " if side_kind else ""
1946
1947        by_name = " BY NAME" if expression.args.get("by_name") else ""
1948        on = self.expressions(expression, key="on", flat=True)
1949        on = f" ON ({on})" if on else ""
1950
1951        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1952
1953    def set_operations(self, expression: exp.SetOperation) -> str:
1954        if not self.SET_OP_MODIFIERS:
1955            limit = expression.args.get("limit")
1956            order = expression.args.get("order")
1957            offset = expression.args.get("offset")
1958
1959            if limit or order or offset:
1960                select = self._move_ctes_to_top_level(
1961                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1962                )
1963
1964                for arg in ("limit", "order", "offset"):
1965                    if value := expression.args.get(arg):
1966                        select.set(arg, value.pop())
1967                return self.sql(select)
1968
1969        sqls: list[str] = []
1970        stack: list[str | exp.Expr | int | None] = [expression]
1971        depth = 0
1972        open_parens = ""
1973
1974        while stack:
1975            node = stack.pop()
1976
1977            if isinstance(node, int):
1978                # Closes a plain grouping opened below, restoring its depth
1979                depth = node
1980                sqls[-1] += self.sep("") + self.indent(")", level=depth, pad=0)
1981            elif isinstance(node, exp.SetOperation):
1982                stack.append(node.expression)
1983                stack.append(
1984                    self.maybe_comment(
1985                        self.set_operation(node), comments=node.comments, separated=True
1986                    )
1987                )
1988                left = node.this
1989                if (
1990                    not self.SET_OP_PARENTHESIZED_OPERANDS
1991                    and not self.dialect.INTERSECT_BINDS_TIGHTER_THAN_UNION_AND_EXCEPT
1992                ):
1993                    # Left grouping is implicit, so skip the `SELECT * FROM (...)` wrapper:
1994                    # it renames duplicate columns and deepens recursion. Inner CTEs and
1995                    # modifiers still need the wrapper.
1996                    wrappers: list[exp.Subquery] = []
1997                    inner = left
1998                    while isinstance(inner, exp.Subquery) and inner.is_wrapper:
1999                        wrappers.append(inner)
2000                        inner = inner.this
2001
2002                    if (
2003                        isinstance(inner, exp.SetOperation)
2004                        and not inner.args.get("with_")
2005                        and not any(inner.args.get(key) for key in exp.QUERY_MODIFIERS)
2006                    ):
2007                        for wrapper in wrappers:
2008                            inner.add_comments(wrapper.pop_comments())
2009                        left = inner
2010                elif (
2011                    self.SET_OP_PARENTHESIZED_OPERANDS
2012                    and isinstance(left, exp.Subquery)
2013                    and left.is_wrapper
2014                    and not left.comments
2015                    and isinstance(left.this, exp.SetOperation)
2016                    and not left.this.args.get("with_")
2017                    and not any(left.this.args.get(key) for key in exp.QUERY_MODIFIERS)
2018                ):
2019                    # Write a plain grouping's parentheses here rather than recursing through
2020                    # subquery_sql: left-to-right chains nest one grouping per INTERSECT, and
2021                    # long ones would exhaust the stack. The depth pushed here marks the close.
2022                    open_parens += self.indent("(", level=depth, pad=0) + self.sep("")
2023                    stack.append(depth)
2024                    depth += 1
2025                    left = left.this
2026                stack.append(left)
2027            else:
2028                if (
2029                    not self.SET_OP_LIMITS
2030                    and isinstance(node, exp.Select)
2031                    and node.args.get("limit")
2032                ):
2033                    node = node.subquery(copy=False)
2034                    if not self.SET_OP_PARENTHESIZED_OPERANDS:
2035                        node = exp.select("*").from_(node, copy=False)
2036                elif (
2037                    not self.SET_OP_PARENTHESIZED_OPERANDS
2038                    and isinstance(node, exp.Subquery)
2039                    and isinstance(node.unnest(), exp.SetOperation)
2040                ):
2041                    node = exp.select("*").from_(node, copy=False)
2042                sqls.append(open_parens + self.indent(self.sql(node), level=depth, pad=0))
2043                open_parens = ""
2044
2045        this = self.sep().join(sqls)
2046        this = self.query_modifiers(expression, this)
2047        return self.prepend_ctes(expression, this)
2048
2049    def fetch_sql(self, expression: exp.Fetch) -> str:
2050        direction = expression.args.get("direction")
2051        direction = f" {direction}" if direction else ""
2052        count = self.sql(expression, "count")
2053        count = f" {count}" if count else ""
2054        limit_options = self.sql(expression, "limit_options")
2055        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
2056        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
2057
2058    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
2059        percent = " PERCENT" if expression.args.get("percent") else ""
2060        rows = " ROWS" if expression.args.get("rows") else ""
2061        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
2062        if not with_ties and rows:
2063            with_ties = " ONLY"
2064        return f"{percent}{rows}{with_ties}"
2065
2066    def filter_sql(self, expression: exp.Filter) -> str:
2067        this = self.sql(expression, "this")
2068        where = self.sql(expression, "expression").strip()
2069        return f"{this} FILTER({where})"
2070
2071    def per_sql(self, expression: exp.Per) -> str:
2072        return f"{self.sql(expression, 'this')} PER ({self.expressions(expression, flat=True)})"
2073
2074    def hint_sql(self, expression: exp.Hint) -> str:
2075        if not self.QUERY_HINTS:
2076            self.unsupported("Hints are not supported")
2077            return ""
2078
2079        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
2080
2081    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
2082        using = self.sql(expression, "using")
2083        using = f" USING {using}" if using else ""
2084        columns = self.expressions(expression, key="columns", flat=True)
2085        columns = f"({columns})" if columns else ""
2086        partition_by = self.expressions(expression, key="partition_by", flat=True)
2087        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
2088        where = self.sql(expression, "where")
2089        include = self.expressions(expression, key="include", flat=True)
2090        if include:
2091            include = f" INCLUDE ({include})"
2092        with_storage = self.expressions(expression, key="with_storage", flat=True)
2093        with_storage = f" WITH ({with_storage})" if with_storage else ""
2094        tablespace = self.sql(expression, "tablespace")
2095        if tablespace:
2096            prefix = "" if isinstance(expression.parent, exp.Index) else " USING INDEX"
2097            tablespace = f"{prefix} TABLESPACE {tablespace}"
2098        on = self.sql(expression, "on")
2099        on = f" ON {on}" if on else ""
2100
2101        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
2102
2103    def index_sql(self, expression: exp.Index) -> str:
2104        unique = "UNIQUE " if expression.args.get("unique") else ""
2105        primary = "PRIMARY " if expression.args.get("primary") else ""
2106        amp = "AMP " if expression.args.get("amp") else ""
2107        name = self.sql(expression, "this")
2108        name = f"{name} " if name else ""
2109        table = self.sql(expression, "table")
2110        table = f"{self.INDEX_ON} {table}" if table else ""
2111
2112        index = "INDEX " if not table else ""
2113
2114        params = self.sql(expression, "params")
2115        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
2116
2117    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
2118        this = expression.this
2119        if this and this.is_string:
2120            resolved = maybe_parse(this.name).sql(self.dialect)
2121            if "expressions" in expression.args:
2122                # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
2123                # We can't safely emit the call to other dialects since name/arg semantics may differ
2124                self.unsupported(
2125                    "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported"
2126                )
2127            return resolved
2128        self.unsupported("IDENTIFIER() with non-literal arguments is not supported")
2129        return self.func("IDENTIFIER", this)
2130
2131    def identifier_sql(self, expression: exp.Identifier) -> str:
2132        text = expression.name
2133        lower = text.lower()
2134        quoted = expression.quoted
2135        text = lower if self.normalize and not quoted else text
2136        if self._identifier_escaped_sequences:
2137            for char, escaped in self._identifier_escaped_sequences.items():
2138                text = text.replace(char, escaped)
2139        text = text.replace(self._identifier_end, self._escaped_identifier_end)
2140        if (
2141            quoted
2142            or self.dialect.can_quote(expression, self.identify)
2143            or lower in self.RESERVED_KEYWORDS
2144            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
2145        ):
2146            text = (
2147                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
2148            )
2149        return text
2150
2151    def hex_sql(self, expression: exp.Hex) -> str:
2152        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2153        if self.dialect.HEX_LOWERCASE:
2154            text = self.func("LOWER", text)
2155
2156        return text
2157
2158    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
2159        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
2160        if not self.dialect.HEX_LOWERCASE:
2161            text = self.func("LOWER", text)
2162        return text
2163
2164    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
2165        input_format = self.sql(expression, "input_format")
2166        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
2167        output_format = self.sql(expression, "output_format")
2168        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
2169        return self.sep().join((input_format, output_format))
2170
2171    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
2172        string = self.sql(exp.Literal.string(expression.name))
2173        return f"{prefix}{string}"
2174
2175    def partition_sql(self, expression: exp.Partition) -> str:
2176        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
2177        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
2178
2179    def properties_sql(self, expression: exp.Properties) -> str:
2180        root_properties = []
2181        with_properties = []
2182
2183        for p in expression.expressions:
2184            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2185            if p_loc == exp.Properties.Location.POST_WITH:
2186                with_properties.append(p)
2187            elif p_loc == exp.Properties.Location.POST_SCHEMA:
2188                root_properties.append(p)
2189
2190        root_props_ast = exp.Properties(expressions=root_properties)
2191        root_props_ast.parent = expression.parent
2192
2193        with_props_ast = exp.Properties(expressions=with_properties)
2194        with_props_ast.parent = expression.parent
2195
2196        root_props = self.root_properties(root_props_ast)
2197        with_props = self.with_properties(with_props_ast)
2198
2199        if root_props and with_props and not self.pretty:
2200            with_props = " " + with_props
2201
2202        return root_props + with_props
2203
2204    def root_properties(self, properties: exp.Properties) -> str:
2205        if properties.expressions:
2206            return self.expressions(properties, indent=False, sep=" ")
2207        return ""
2208
2209    def properties(
2210        self,
2211        properties: exp.Properties,
2212        prefix: str = "",
2213        sep: str = ", ",
2214        suffix: str = "",
2215        wrapped: bool = True,
2216    ) -> str:
2217        if properties.expressions:
2218            expressions = self.expressions(properties, sep=sep, indent=False)
2219            if expressions:
2220                expressions = self.wrap(expressions) if wrapped else expressions
2221                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
2222        return ""
2223
2224    def with_properties(self, properties: exp.Properties) -> str:
2225        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
2226
2227    def locate_properties(self, properties: exp.Properties) -> defaultdict:
2228        properties_locs = defaultdict(list)
2229        for p in properties.expressions:
2230            p_loc = self.PROPERTIES_LOCATION[p.__class__]
2231            if p_loc != exp.Properties.Location.UNSUPPORTED:
2232                properties_locs[p_loc].append(p)
2233            else:
2234                self.unsupported(f"Unsupported property {p.key}")
2235
2236        return properties_locs
2237
2238    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
2239        if isinstance(expression.this, exp.Dot):
2240            return self.sql(expression, "this")
2241        return self.sql(exp.Literal.string(expression.name)) if string_key else expression.name
2242
2243    def property_sql(self, expression: exp.Property) -> str:
2244        property_cls = expression.__class__
2245        if property_cls == exp.Property:
2246            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
2247
2248        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
2249        if not property_name:
2250            self.unsupported(f"Unsupported property {expression.key}")
2251            return ""
2252
2253        return f"{property_name}={self.sql(expression, 'this')}"
2254
2255    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
2256        return f"UUID {self.sql(expression, 'this')}"
2257
2258    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
2259        if self.SUPPORTS_CREATE_TABLE_LIKE:
2260            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2261            options = f" {options}" if options else ""
2262
2263            like = f"LIKE {self.sql(expression, 'this')}{options}"
2264            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2265                like = f"({like})"
2266
2267            return like
2268
2269        if expression.expressions:
2270            self.unsupported("Transpilation of LIKE property options is unsupported")
2271
2272        select = exp.select("*").from_(expression.this).limit(0)
2273        return f"AS {self.sql(select)}"
2274
2275    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2276        no = "NO " if expression.args.get("no") else ""
2277        protection = " PROTECTION" if expression.args.get("protection") else ""
2278        return f"{no}FALLBACK{protection}"
2279
2280    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2281        no = "NO " if expression.args.get("no") else ""
2282        local = expression.args.get("local")
2283        local = f"{local} " if local else ""
2284        dual = "DUAL " if expression.args.get("dual") else ""
2285        before = "BEFORE " if expression.args.get("before") else ""
2286        after = "AFTER " if expression.args.get("after") else ""
2287        return f"{no}{local}{dual}{before}{after}JOURNAL"
2288
2289    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2290        freespace = self.sql(expression, "this")
2291        percent = " PERCENT" if expression.args.get("percent") else ""
2292        return f"FREESPACE={freespace}{percent}"
2293
2294    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2295        if expression.args.get("default"):
2296            property = "DEFAULT"
2297        elif expression.args.get("on"):
2298            property = "ON"
2299        else:
2300            property = "OFF"
2301        return f"CHECKSUM={property}"
2302
2303    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2304        if expression.args.get("no"):
2305            return "NO MERGEBLOCKRATIO"
2306        if expression.args.get("default"):
2307            return "DEFAULT MERGEBLOCKRATIO"
2308
2309        percent = " PERCENT" if expression.args.get("percent") else ""
2310        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2311
2312    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2313        expressions = self.expressions(expression, flat=True)
2314        expressions = f"({expressions})" if expressions else ""
2315        return f"USING {self.sql(expression, 'this')}{expressions}"
2316
2317    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2318        default = expression.args.get("default")
2319        minimum = expression.args.get("minimum")
2320        maximum = expression.args.get("maximum")
2321        if default or minimum or maximum:
2322            if default:
2323                prop = "DEFAULT"
2324            elif minimum:
2325                prop = "MINIMUM"
2326            else:
2327                prop = "MAXIMUM"
2328            return f"{prop} DATABLOCKSIZE"
2329        units = expression.args.get("units")
2330        units = f" {units}" if units else ""
2331        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2332
2333    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2334        autotemp = expression.args.get("autotemp")
2335        always = expression.args.get("always")
2336        default = expression.args.get("default")
2337        manual = expression.args.get("manual")
2338        never = expression.args.get("never")
2339
2340        if autotemp is not None:
2341            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2342        elif always:
2343            prop = "ALWAYS"
2344        elif default:
2345            prop = "DEFAULT"
2346        elif manual:
2347            prop = "MANUAL"
2348        elif never:
2349            prop = "NEVER"
2350        return f"BLOCKCOMPRESSION={prop}"
2351
2352    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2353        no = expression.args.get("no")
2354        no = " NO" if no else ""
2355        concurrent = expression.args.get("concurrent")
2356        concurrent = " CONCURRENT" if concurrent else ""
2357        target = self.sql(expression, "target")
2358        target = f" {target}" if target else ""
2359        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2360
2361    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2362        if isinstance(expression.this, list):
2363            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2364        if expression.this:
2365            modulus = self.sql(expression, "this")
2366            remainder = self.sql(expression, "expression")
2367            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2368
2369        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2370        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2371        return f"FROM ({from_expressions}) TO ({to_expressions})"
2372
2373    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2374        this = self.sql(expression, "this")
2375
2376        for_values_or_default = expression.expression
2377        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2378            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2379        else:
2380            for_values_or_default = " DEFAULT"
2381
2382        return f"PARTITION OF {this}{for_values_or_default}"
2383
2384    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2385        kind = expression.args.get("kind")
2386        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2387        for_or_in = expression.args.get("for_or_in")
2388        for_or_in = f" {for_or_in}" if for_or_in else ""
2389        lock_type = expression.args.get("lock_type")
2390        override = " OVERRIDE" if expression.args.get("override") else ""
2391        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2392
2393    def lockingstatement_sql(self, expression: exp.LockingStatement) -> str:
2394        self.unsupported("LOCKING request modifier is not supported")
2395        return self.sql(expression, "expression")
2396
2397    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2398        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2399        statistics = expression.args.get("statistics")
2400        statistics_sql = ""
2401        if statistics is not None:
2402            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2403        return f"{data_sql}{statistics_sql}"
2404
2405    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2406        this = self.sql(expression, "this")
2407        this = f"HISTORY_TABLE={this}" if this else ""
2408        data_consistency: str | None = self.sql(expression, "data_consistency")
2409        data_consistency = (
2410            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2411        )
2412        retention_period: str | None = self.sql(expression, "retention_period")
2413        retention_period = (
2414            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2415        )
2416
2417        if this:
2418            on_sql = self.func("ON", this, data_consistency, retention_period)
2419        else:
2420            on_sql = "ON" if expression.args.get("on") else "OFF"
2421
2422        sql = f"SYSTEM_VERSIONING={on_sql}"
2423
2424        return f"WITH({sql})" if expression.args.get("with_") else sql
2425
2426    def insert_sql(self, expression: exp.Insert) -> str:
2427        hint = self.sql(expression, "hint")
2428        overwrite = expression.args.get("overwrite")
2429
2430        if isinstance(expression.this, exp.Directory):
2431            this = " OVERWRITE" if overwrite else " INTO"
2432        else:
2433            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2434
2435        stored = self.sql(expression, "stored")
2436        stored = f" {stored}" if stored else ""
2437        alternative = expression.args.get("alternative")
2438        alternative = f" OR {alternative}" if alternative else ""
2439        ignore = " IGNORE" if expression.args.get("ignore") else ""
2440        is_function = expression.args.get("is_function")
2441        if is_function:
2442            this = f"{this} FUNCTION"
2443        this = f"{this} {self.sql(expression, 'this')}"
2444
2445        exists = " IF EXISTS" if expression.args.get("exists") else ""
2446        where = self.sql(expression, "where")
2447        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2448        using = self.expressions(expression, key="using", flat=True)
2449        using = f"{self.sep()}REPLACE USING ({using})" if using else ""
2450        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2451        on_conflict = self.sql(expression, "conflict")
2452        on_conflict = f" {on_conflict}" if on_conflict else ""
2453        by_name = " BY NAME" if expression.args.get("by_name") else ""
2454        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2455        returning = self.sql(expression, "returning")
2456
2457        if self.RETURNING_END:
2458            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2459        else:
2460            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2461
2462        partition_by = self.sql(expression, "partition")
2463        partition_by = f" {partition_by}" if partition_by else ""
2464        settings = self.sql(expression, "settings")
2465        settings = f" {settings}" if settings else ""
2466
2467        source = self.sql(expression, "source")
2468        source = f"TABLE {source}" if source else ""
2469
2470        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}"
2471        return self.prepend_ctes(expression, sql)
2472
2473    def introducer_sql(self, expression: exp.Introducer) -> str:
2474        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2475
2476    def kill_sql(self, expression: exp.Kill) -> str:
2477        kind = self.sql(expression, "kind")
2478        kind = f" {kind}" if kind else ""
2479        this = self.sql(expression, "this")
2480        this = f" {this}" if this else ""
2481        return f"KILL{kind}{this}"
2482
2483    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2484        return expression.name
2485
2486    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2487        return expression.name
2488
2489    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2490        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2491
2492        constraint = self.sql(expression, "constraint")
2493        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2494
2495        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2496        if conflict_keys:
2497            conflict_keys = f"({conflict_keys})"
2498
2499        index_predicate = self.sql(expression, "index_predicate")
2500        conflict_keys = f"{conflict_keys}{index_predicate} "
2501
2502        action = self.sql(expression, "action")
2503
2504        expressions = self.expressions(expression, flat=True)
2505        if expressions:
2506            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2507            expressions = f" {set_keyword}{expressions}"
2508
2509        where = self.sql(expression, "where")
2510        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2511
2512    def returning_sql(self, expression: exp.Returning) -> str:
2513        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2514
2515    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2516        fields = self.sql(expression, "fields")
2517        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2518        escaped = self.sql(expression, "escaped")
2519        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2520        items = self.sql(expression, "collection_items")
2521        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2522        keys = self.sql(expression, "map_keys")
2523        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2524        lines = self.sql(expression, "lines")
2525        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2526        null = self.sql(expression, "null")
2527        null = f" NULL DEFINED AS {null}" if null else ""
2528        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2529
2530    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2531        return f"WITH ({self.expressions(expression, flat=True)})"
2532
2533    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2534        this = f"{self.sql(expression, 'this')} INDEX"
2535        target = self.sql(expression, "target")
2536        target = f" FOR {target}" if target else ""
2537        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2538
2539    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2540        this = self.sql(expression, "this")
2541        kind = self.sql(expression, "kind")
2542        expr = self.sql(expression, "expression")
2543        return f"{this} ({kind} => {expr})"
2544
2545    def table_parts(self, expression: exp.Table) -> str:
2546        return ".".join(
2547            self.sql(part)
2548            for part in (
2549                expression.args.get("catalog"),
2550                expression.args.get("db"),
2551                expression.args.get("this"),
2552            )
2553            if part is not None
2554        )
2555
2556    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2557        table = self.table_parts(expression)
2558        only = "ONLY " if expression.args.get("only") else ""
2559        partition = self.sql(expression, "partition")
2560        partition = f" {partition}" if partition else ""
2561        version = self.sql(expression, "version")
2562        version = f" {version}" if version else ""
2563        alias = self.sql(expression, "alias")
2564        alias = f"{sep}{alias}" if alias else ""
2565
2566        sample = self.sql(expression, "sample")
2567        post_alias = ""
2568        pre_alias = ""
2569
2570        if self.dialect.ALIAS_POST_TABLESAMPLE:
2571            pre_alias = sample
2572        else:
2573            post_alias = sample
2574
2575        if self.dialect.ALIAS_POST_VERSION:
2576            pre_alias = f"{pre_alias}{version}"
2577        else:
2578            post_alias = f"{post_alias}{version}"
2579
2580        hints = self.expressions(expression, key="hints", sep=" ")
2581        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2582        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2583        joins = self.indent(
2584            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2585        )
2586        laterals = self.expressions(expression, key="laterals", sep="")
2587
2588        file_format = self.sql(expression, "format")
2589        pattern = self.sql(expression, "pattern")
2590        if file_format:
2591            pattern = f", PATTERN => {pattern}" if pattern else ""
2592            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2593        elif pattern:
2594            file_format = f" (PATTERN => {pattern})"
2595
2596        ordinality = expression.args.get("ordinality") or ""
2597        if ordinality:
2598            ordinality = f" WITH ORDINALITY{alias}"
2599            alias = ""
2600
2601        when = self.sql(expression, "when")
2602        if when:
2603            if self.HISTORICAL_DATA_POST_ALIAS:
2604                alias = f"{alias} {when}"
2605            else:
2606                table = f"{table} {when}"
2607
2608        changes = self.sql(expression, "changes")
2609        changes = f" {changes}" if changes else ""
2610
2611        rows_from = self.expressions(expression, key="rows_from")
2612        if rows_from:
2613            table = f"ROWS FROM {self.wrap(rows_from)}"
2614
2615        indexed = expression.args.get("indexed")
2616        if indexed is not None:
2617            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2618        else:
2619            indexed = ""
2620
2621        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2622
2623    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2624        table = self.func("TABLE", expression.this)
2625        alias = self.sql(expression, "alias")
2626        alias = f" AS {alias}" if alias else ""
2627        sample = self.sql(expression, "sample")
2628        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2629        joins = self.indent(
2630            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2631        )
2632        return f"{table}{alias}{pivots}{sample}{joins}"
2633
2634    def tablesample_sql(
2635        self,
2636        expression: exp.TableSample,
2637        tablesample_keyword: str | None = None,
2638    ) -> str:
2639        if expression.expressions:
2640            self.unsupported("Multiple sample sets are not supported")
2641            return ""
2642
2643        method = self.sql(expression, "method")
2644        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2645        numerator = self.sql(expression, "bucket_numerator")
2646        denominator = self.sql(expression, "bucket_denominator")
2647        field = self.sql(expression, "bucket_field")
2648        field = f" ON {field}" if field else ""
2649        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2650        seed = self.sql(expression, "seed")
2651        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2652
2653        size = self.sql(expression, "size")
2654        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2655            size = f"{size} ROWS"
2656
2657        percent = self.sql(expression, "percent")
2658        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2659            percent = f"{percent} PERCENT"
2660
2661        expr = f"{bucket}{percent}{size}"
2662        if self.TABLESAMPLE_REQUIRES_PARENS:
2663            expr = f"({expr})"
2664
2665        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2666
2667    def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None:
2668        # Returns the rewritten field.expressions list with PivotAlias wrappers injected where
2669        # the stored column name differs from the target dialect's natural output.
2670        columns = expression.args.get("columns")
2671        if not columns or len(expression.fields) != 1:
2672            return None
2673
2674        args = expression.args
2675        parser_cls = self.dialect.parser_class
2676
2677        tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS
2678        tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS
2679        tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING
2680
2681        src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings)
2682        src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns)
2683        src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming)
2684
2685        if (
2686            src_identify_pivot_strings == tgt_identify_pivot_strings
2687            and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns
2688            and src_pivot_column_naming == tgt_pivot_column_naming
2689        ):
2690            return None
2691
2692        in_exprs = expression.fields[0].expressions
2693        step = len(columns) // len(in_exprs)
2694
2695        # Derive the per-value suffix from the first stored column vs the first IN-list value.
2696        # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases.
2697        first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name
2698        first_stored = columns[0].name
2699
2700        # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed)
2701        if not first_stored.startswith(first_base):
2702            return None
2703
2704        suffix = first_stored[len(first_base) :]
2705
2706        # Whether the target dialect would append an agg-name suffix for this pivot.
2707        # Spark single-agg uniquely drops the agg alias entirely.
2708        target_has_suffix = (
2709            len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple"
2710        ) and any(a.alias for a in expression.expressions)
2711        source_has_suffix = suffix != ""
2712
2713        new_exprs: list[exp.Expression] = []
2714        modified = False
2715        for val_idx, e in enumerate(in_exprs):
2716            if isinstance(e, exp.PivotAlias):
2717                new_exprs.append(e)
2718                continue
2719
2720            i = val_idx * step
2721            stored_full = columns[i].name
2722            stored_value = stored_full[: -len(suffix)] if suffix else stored_full
2723            target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name
2724
2725            # Source had a suffix, but target won't apply one
2726            if source_has_suffix and not target_has_suffix:
2727                new_exprs.append(
2728                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True))
2729                )
2730                modified = True
2731            # Value-part mismatch (e.g. Snowflake's literal-style values vs others).
2732            elif stored_value != target_value:
2733                new_exprs.append(
2734                    exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True))
2735                )
2736                modified = True
2737            else:
2738                new_exprs.append(e)
2739
2740        return new_exprs if modified else None
2741
2742    def pivot_sql(self, expression: exp.Pivot) -> str:
2743        expressions = self.expressions(expression, flat=True)
2744        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2745
2746        group = self.sql(expression, "group")
2747
2748        if expression.this:
2749            this = self.sql(expression, "this")
2750            if not expressions:
2751                sql = f"UNPIVOT {this}"
2752            else:
2753                on = f"{self.seg('ON')} {expressions}"
2754                into = self.sql(expression, "into")
2755                into = f"{self.seg('INTO')} {into}" if into else ""
2756                using = self.expressions(expression, key="using", flat=True)
2757                using = f"{self.seg('USING')} {using}" if using else ""
2758                sql = f"{direction} {this}{on}{into}{using}{group}"
2759            return self.prepend_ctes(expression, sql)
2760
2761        if not expression.unpivot:
2762            # Wrap IN-list values with explicit aliases where the target dialect would differ
2763            new_field_exprs = self._pivot_in_value_aliases(expression)
2764            if new_field_exprs is not None:
2765                expression.fields[0].set("expressions", new_field_exprs)
2766
2767        alias = self.sql(expression, "alias")
2768        if alias:
2769            alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}"
2770
2771        fields = self.expressions(
2772            expression,
2773            "fields",
2774            sep=" ",
2775            dynamic=True,
2776            new_line=True,
2777            skip_first=True,
2778            skip_last=True,
2779        )
2780
2781        include_nulls = expression.args.get("include_nulls")
2782        if include_nulls is not None:
2783            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2784        else:
2785            nulls = ""
2786
2787        default_on_null = self.sql(expression, "default_on_null")
2788        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2789        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2790        return self.prepend_ctes(expression, sql)
2791
2792    def version_sql(self, expression: exp.Version) -> str:
2793        this = f"FOR {expression.name}"
2794        kind = expression.text("kind")
2795        expr = self.sql(expression, "expression")
2796        return f"{this} {kind} {expr}"
2797
2798    def tuple_sql(self, expression: exp.Tuple) -> str:
2799        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2800
2801    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2802        """
2803        Returns (join_sql, from_sql) for UPDATE statements.
2804        - join_sql: placed after UPDATE table, before SET
2805        - from_sql: placed after SET clause (standard position)
2806        Dialects like MySQL need to convert FROM to JOIN syntax.
2807        """
2808        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2809            return ("", self.sql(expression, "from_"))
2810
2811        # Qualify unqualified columns in SET clause with the target table
2812        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2813        target_table = expression.this
2814        if isinstance(target_table, exp.Table):
2815            target_name = exp.to_identifier(target_table.alias_or_name)
2816            for eq in expression.expressions:
2817                col = eq.this
2818                if isinstance(col, exp.Column) and not col.table:
2819                    col.set("table", target_name)
2820
2821        table = from_expr.this
2822        if nested_joins := table.args.get("joins", []):
2823            table.set("joins", None)
2824
2825        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2826        for nested in nested_joins:
2827            if not nested.args.get("on") and not nested.args.get("using"):
2828                nested.set("on", exp.true())
2829            join_sql += self.sql(nested)
2830
2831        return (join_sql, "")
2832
2833    def update_sql(self, expression: exp.Update) -> str:
2834        hint = self.sql(expression, "hint")
2835        this = self.sql(expression, "this")
2836        join_sql, from_sql = self._update_from_joins_sql(expression)
2837        set_sql = self.expressions(expression, flat=True)
2838        where_sql = self.sql(expression, "where")
2839        returning = self.sql(expression, "returning")
2840        order = self.sql(expression, "order")
2841        limit = self.sql(expression, "limit")
2842        if self.RETURNING_END:
2843            expression_sql = f"{from_sql}{where_sql}{returning}"
2844        else:
2845            expression_sql = f"{returning}{from_sql}{where_sql}"
2846        options = self.expressions(expression, key="options")
2847        options = f" OPTION({options})" if options else ""
2848        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2849        return self.prepend_ctes(expression, sql)
2850
2851    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2852        values_as_table = values_as_table and self.VALUES_AS_TABLE
2853
2854        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2855        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2856            args = self.expressions(expression)
2857            alias = self.sql(expression, "alias")
2858            values = f"VALUES{self.seg('')}{args}"
2859            values = (
2860                f"({values})"
2861                if self.WRAP_DERIVED_VALUES
2862                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2863                else values
2864            )
2865            values = self.query_modifiers(expression, values)
2866            return f"{values} AS {alias}" if alias else values
2867
2868        # Converts `VALUES...` expression into a series of select unions.
2869        alias_node = expression.args.get("alias")
2870        column_names = alias_node and alias_node.columns
2871
2872        selects: list[exp.Query] = []
2873
2874        for i, tup in enumerate(expression.expressions):
2875            row = tup.expressions
2876
2877            if i == 0 and column_names:
2878                row = [
2879                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2880                ]
2881
2882            selects.append(exp.Select(expressions=row))
2883
2884        if self.pretty:
2885            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2886            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2887            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2888            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2889            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2890
2891        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2892        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2893        return f"({unions}){alias}"
2894
2895    def var_sql(self, expression: exp.Var) -> str:
2896        return self.sql(expression, "this")
2897
2898    @unsupported_args("expressions")
2899    def into_sql(self, expression: exp.Into) -> str:
2900        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2901        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2902        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2903
2904    def from_sql(self, expression: exp.From) -> str:
2905        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2906
2907    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2908        grouping_sets = self.expressions(expression, indent=False)
2909        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2910
2911    def rollup_sql(self, expression: exp.Rollup) -> str:
2912        expressions = self.expressions(expression, indent=False)
2913        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2914
2915    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2916        this = self.sql(expression, "this")
2917
2918        columns = self.expressions(expression, flat=True)
2919
2920        from_sql = self.sql(expression, "from_index")
2921        from_sql = f" FROM {from_sql}" if from_sql else ""
2922
2923        properties = expression.args.get("properties")
2924        properties_sql = (
2925            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2926        )
2927
2928        return f"{this}({columns}){from_sql}{properties_sql}"
2929
2930    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2931        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2932
2933    def cube_sql(self, expression: exp.Cube) -> str:
2934        expressions = self.expressions(expression, indent=False)
2935        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2936
2937    def group_sql(self, expression: exp.Group) -> str:
2938        group_by_all = expression.args.get("all")
2939        if group_by_all is True:
2940            modifier = " ALL"
2941        elif group_by_all is False:
2942            modifier = " DISTINCT"
2943        else:
2944            modifier = ""
2945
2946        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2947
2948        grouping_sets = self.expressions(expression, key="grouping_sets")
2949        cube = self.expressions(expression, key="cube")
2950        rollup = self.expressions(expression, key="rollup")
2951
2952        groupings = csv(
2953            self.seg(grouping_sets) if grouping_sets else "",
2954            self.seg(cube) if cube else "",
2955            self.seg(rollup) if rollup else "",
2956            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2957            sep=self.GROUPINGS_SEP,
2958        )
2959
2960        if (
2961            expression.expressions
2962            and groupings
2963            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2964        ):
2965            add_separator = True
2966
2967            if grouping_sets:
2968                if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX:
2969                    add_separator = False
2970                else:
2971                    self.unsupported(
2972                        "GROUPING SETS without a comma after GROUP BY expressions is not supported"
2973                    )
2974
2975            if add_separator:
2976                group_by = f"{group_by}{self.GROUPINGS_SEP}"
2977
2978        return f"{group_by}{groupings}"
2979
2980    def having_sql(self, expression: exp.Having) -> str:
2981        this = self.indent(self.sql(expression, "this"))
2982        return f"{self.seg('HAVING')}{self.sep()}{this}"
2983
2984    def connectto_sql(self, expression: exp.ConnectTo) -> str:
2985        return f"CONNECT {self.sql(expression, 'this')}"
2986
2987    def disconnect_sql(self, expression: exp.Disconnect) -> str:
2988        return "DISCONNECT"
2989
2990    def connect_sql(self, expression: exp.Connect) -> str:
2991        start = self.sql(expression, "start")
2992        start = self.seg(f"START WITH {start}") if start else ""
2993        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2994        connect = self.sql(expression, "connect")
2995        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2996        return start + connect
2997
2998    def prior_sql(self, expression: exp.Prior) -> str:
2999        return f"PRIOR {self.sql(expression, 'this')}"
3000
3001    def join_sql(self, expression: exp.Join, join_op: str | None = None) -> str:
3002        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
3003            side = None
3004        else:
3005            side = expression.side
3006
3007        op_sql = " ".join(
3008            op
3009            for op in (
3010                expression.method,
3011                "GLOBAL" if expression.args.get("global_") else None,
3012                side,
3013                expression.kind,
3014                expression.hint if self.JOIN_HINTS else None,
3015                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
3016            )
3017            if op
3018        )
3019        match_cond = self.sql(expression, "match_condition")
3020        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
3021        nearest = self.sql(expression, "nearest")
3022        nearest = f" {nearest}" if nearest else ""
3023        on_sql = self.sql(expression, "on")
3024        using = expression.args.get("using")
3025
3026        if not on_sql and using:
3027            on_sql = csv(*(self.sql(column) for column in using))
3028
3029        this = expression.this
3030        this_sql = self.sql(this)
3031
3032        exprs = self.expressions(expression)
3033        if exprs:
3034            this_sql = f"{this_sql},{self.seg(exprs)}"
3035
3036        if on_sql:
3037            on_sql = self.indent(on_sql, skip_first=True)
3038            space = self.seg(" " * self.pad) if self.pretty else " "
3039            if using:
3040                on_sql = f"{space}USING ({on_sql})"
3041            else:
3042                on_sql = f"{space}ON {on_sql}"
3043        elif not op_sql and not nearest:
3044            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
3045                return f" {this_sql}"
3046
3047            return f", {this_sql}"
3048
3049        if join_op:
3050            op_sql = join_op
3051        elif op_sql != "STRAIGHT_JOIN":
3052            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
3053
3054        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3055        return f"{self.seg(op_sql)} {this_sql}{match_cond}{nearest}{on_sql}{pivots}"
3056
3057    def nearest_sql(self, expression: exp.Nearest) -> str:
3058        if not self.SUPPORTS_NEAREST_JOIN:
3059            self.unsupported("NEAREST joins are not supported")
3060
3061        approximate = "APPROX " if expression.args.get("approximate") else ""
3062        metric = "SIMILARITY" if expression.args.get("similarity") else "DISTANCE"
3063        return f"{approximate}NEAREST {self.sql(expression, 'count')} BY {metric} {self.sql(expression, 'this')}"
3064
3065    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
3066        args = self.expressions(expression, flat=True)
3067        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
3068        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
3069
3070    def lateral_op(self, expression: exp.Lateral) -> str:
3071        cross_apply = expression.args.get("cross_apply")
3072
3073        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
3074        if cross_apply is True:
3075            op = "INNER JOIN "
3076        elif cross_apply is False:
3077            op = "LEFT JOIN "
3078        else:
3079            op = ""
3080
3081        return f"{op}LATERAL"
3082
3083    def lateral_sql(self, expression: exp.Lateral) -> str:
3084        this = self.sql(expression, "this")
3085
3086        if expression.args.get("view"):
3087            alias = expression.args["alias"]
3088            columns = self.expressions(alias, key="columns", flat=True)
3089            table = f" {alias.name}" if alias.name else ""
3090            columns = f" AS {columns}" if columns else ""
3091            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
3092            return f"{op_sql}{self.sep()}{this}{table}{columns}"
3093
3094        table_alias = expression.args.get("alias")
3095        offset = expression.this.args.get("offset")
3096
3097        if (
3098            self.UNNEST_WITH_ORDINALITY
3099            and table_alias
3100            and isinstance(expression.this, exp.Unnest)
3101            and isinstance(offset, exp.Identifier)
3102        ):
3103            # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset
3104            table_alias = table_alias.copy()
3105            table_alias.append("columns", offset.copy())
3106
3107        alias = self.sql(table_alias)
3108        alias = f" AS {alias}" if alias else ""
3109
3110        ordinality = expression.args.get("ordinality") or ""
3111        if ordinality:
3112            ordinality = f" WITH ORDINALITY{alias}"
3113            alias = ""
3114
3115        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
3116
3117    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
3118        this = self.sql(expression, "this")
3119
3120        if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL:
3121            return this
3122
3123        args = [
3124            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
3125            for e in (expression.args.get(k) for k in ("offset", "expression"))
3126            if e
3127        ]
3128
3129        args_sql = ", ".join(self.sql(e) for e in args)
3130        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
3131        expressions = self.expressions(expression, flat=True)
3132        limit_options = self.sql(expression, "limit_options")
3133        expressions = f" BY {expressions}" if expressions else ""
3134
3135        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
3136
3137    def offset_sql(self, expression: exp.Offset) -> str:
3138        this = self.sql(expression, "this")
3139        value = expression.expression
3140        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
3141        expressions = self.expressions(expression, flat=True)
3142        expressions = f" BY {expressions}" if expressions else ""
3143        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
3144
3145    def setitem_sql(self, expression: exp.SetItem) -> str:
3146        kind = self.sql(expression, "kind")
3147        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
3148            kind = ""
3149        else:
3150            kind = f"{kind} " if kind else ""
3151        this = self.sql(expression, "this")
3152        expressions = self.expressions(expression)
3153        collate = self.sql(expression, "collate")
3154        collate = f" COLLATE {collate}" if collate else ""
3155        global_ = "GLOBAL " if expression.args.get("global_") else ""
3156        return f"{global_}{kind}{this}{expressions}{collate}"
3157
3158    def set_sql(self, expression: exp.Set) -> str:
3159        expressions = f" {self.expressions(expression, flat=True)}"
3160        tag = " TAG" if expression.args.get("tag") else ""
3161        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
3162
3163    def queryband_sql(self, expression: exp.QueryBand) -> str:
3164        this = self.sql(expression, "this")
3165        update = " UPDATE" if expression.args.get("update") else ""
3166        scope = self.sql(expression, "scope")
3167        scope = f" FOR {scope}" if scope else ""
3168
3169        return f"QUERY_BAND = {this}{update}{scope}"
3170
3171    def pragma_sql(self, expression: exp.Pragma) -> str:
3172        return f"PRAGMA {self.sql(expression, 'this')}"
3173
3174    def lock_sql(self, expression: exp.Lock) -> str:
3175        if not self.LOCKING_READS_SUPPORTED:
3176            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
3177            return ""
3178
3179        update = expression.args["update"]
3180        key = expression.args.get("key")
3181        if update:
3182            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
3183        else:
3184            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
3185        expressions = self.expressions(expression, flat=True)
3186        expressions = f" OF {expressions}" if expressions else ""
3187        wait = expression.args.get("wait")
3188
3189        if wait is not None:
3190            if isinstance(wait, exp.Literal):
3191                wait = f" WAIT {self.sql(wait)}"
3192            else:
3193                wait = " NOWAIT" if wait else " SKIP LOCKED"
3194
3195        return f"{lock_type}{expressions}{wait or ''}"
3196
3197    def literal_sql(self, expression: exp.Literal) -> str:
3198        text = expression.this or ""
3199        if expression.is_string:
3200            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
3201        return text
3202
3203    def escape_str(
3204        self,
3205        text: str,
3206        escape_backslash: bool = True,
3207        delimiter: str | None = None,
3208        escaped_delimiter: str | None = None,
3209        is_byte_string: bool = False,
3210        is_bytes: bool = False,
3211    ) -> str:
3212        if is_byte_string:
3213            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
3214        else:
3215            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
3216
3217        if supports_escape_sequences:
3218            text = "".join(
3219                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
3220                for ch in text
3221            )
3222
3223            if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES:
3224                # Escape high bytes so they aren't re-encoded as UTF-8 characters.
3225                text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text)
3226
3227        delimiter = delimiter or self.dialect.QUOTE_END
3228        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
3229
3230        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3231
3232    def loaddata_sql(self, expression: exp.LoadData) -> str:
3233        is_overwrite = expression.args.get("overwrite")
3234        overwrite = " OVERWRITE" if is_overwrite else ""
3235        this = self.sql(expression, "this")
3236
3237        files = expression.args.get("files")
3238        if files:
3239            files_sql = self.expressions(files, flat=True)
3240            files_sql = f"FILES{self.wrap(files_sql)}"
3241            if is_overwrite:
3242                this = f" {this}"
3243            elif expression.args.get("temp"):
3244                this = f" INTO TEMP TABLE {this}"
3245            else:
3246                this = f" INTO TABLE {this}"
3247            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
3248
3249        local = " LOCAL" if expression.args.get("local") else ""
3250        inpath = f" INPATH {self.sql(expression, 'inpath')}"
3251        this = f" INTO TABLE {this}"
3252        partition = self.sql(expression, "partition")
3253        partition = f" {partition}" if partition else ""
3254        input_format = self.sql(expression, "input_format")
3255        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
3256        serde = self.sql(expression, "serde")
3257        serde = f" SERDE {serde}" if serde else ""
3258        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3259
3260    def null_sql(self, *_) -> str:
3261        return "NULL"
3262
3263    def boolean_sql(self, expression: exp.Boolean) -> str:
3264        return "TRUE" if expression.this else "FALSE"
3265
3266    def booland_sql(self, expression: exp.Booland) -> str:
3267        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
3268
3269    def boolor_sql(self, expression: exp.Boolor) -> str:
3270        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
3271
3272    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
3273        this = self.sql(expression, "this")
3274        this = f"{this} " if this else this
3275        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
3276        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3277
3278    def withfill_sql(self, expression: exp.WithFill) -> str:
3279        from_sql = self.sql(expression, "from_")
3280        from_sql = f" FROM {from_sql}" if from_sql else ""
3281        to_sql = self.sql(expression, "to")
3282        to_sql = f" TO {to_sql}" if to_sql else ""
3283        step_sql = self.sql(expression, "step")
3284        step_sql = f" STEP {step_sql}" if step_sql else ""
3285        interpolated_values = [
3286            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
3287            if isinstance(e, exp.Alias)
3288            else self.sql(e, "this")
3289            for e in expression.args.get("interpolate") or []
3290        ]
3291        interpolate = (
3292            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
3293        )
3294        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3295
3296    def cluster_sql(self, expression: exp.Cluster) -> str:
3297        return self.op_expressions("CLUSTER BY", expression)
3298
3299    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
3300        if expression.this:
3301            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
3302            return ""
3303        expressions = self.expressions(expression, flat=True)
3304        return f"CLUSTER BY ({expressions})"
3305
3306    def distribute_sql(self, expression: exp.Distribute) -> str:
3307        return self.op_expressions("DISTRIBUTE BY", expression)
3308
3309    def sort_sql(self, expression: exp.Sort) -> str:
3310        return self.op_expressions("SORT BY", expression)
3311
3312    def _resolve_ordered_for_null_ordering_simulation(
3313        self, expression: exp.Ordered
3314    ) -> exp.Expr | None:
3315        """Resolve a bare ORDER BY name against the enclosing SELECT projection.
3316
3317        Returns the underlying expression of the uniquely-matching projection
3318        (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE
3319        simulation, since the CASE is evaluated in FROM-clause scope rather
3320        than alias scope (MySQL error 1052). Returns None if no safe
3321        substitution applies, leaving the original behaviour unchanged.
3322        """
3323        this = expression.this
3324        if not (isinstance(this, exp.Column) and not this.table):
3325            return None
3326
3327        # Only a query's own ORDER BY can reference its projection aliases
3328        order = expression.parent
3329        ancestor = order.parent if isinstance(order, exp.Order) else None
3330        if not isinstance(ancestor, exp.Select):
3331            return None
3332
3333        column_name = this.name
3334        matched: list[exp.Expr] = [
3335            p.this if isinstance(p, exp.Alias) else p
3336            for p in ancestor.selects
3337            if p.output_name == column_name
3338        ]
3339        match = matched[0] if len(matched) == 1 else None
3340
3341        # Skip the substitution when it would be identical to the existing
3342        # reference (e.g. ``SELECT col FROM t ORDER BY col``).
3343        if isinstance(match, exp.Column) and not match.table and match.name == column_name:
3344            return None
3345
3346        return match
3347
3348    def ordered_sql(self, expression: exp.Ordered) -> str:
3349        desc = expression.args.get("desc")
3350        asc = not desc
3351
3352        nulls_first = expression.args.get("nulls_first")
3353        nulls_last = not nulls_first
3354        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
3355        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
3356        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
3357
3358        this = self.sql(expression, "this")
3359
3360        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
3361        nulls_sort_change = ""
3362        if nulls_first and (
3363            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
3364        ):
3365            nulls_sort_change = " NULLS FIRST"
3366        elif (
3367            nulls_last
3368            and ((asc and nulls_are_small) or (desc and nulls_are_large))
3369            and not nulls_are_last
3370        ):
3371            nulls_sort_change = " NULLS LAST"
3372
3373        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
3374        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
3375            window = expression.find_ancestor(exp.Window, exp.Select)
3376
3377            if isinstance(window, exp.Window):
3378                window_this = window.this
3379                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
3380                    window_this = window_this.this
3381                spec = window.args.get("spec")
3382            else:
3383                window_this = None
3384                spec = None
3385
3386            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
3387            # without a spec or with a ROWS spec, but not with RANGE
3388            if not (
3389                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
3390                and (not spec or spec.text("kind").upper() == "ROWS")
3391            ):
3392                if window_this and spec:
3393                    self.unsupported(
3394                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
3395                    )
3396                    nulls_sort_change = ""
3397                elif self.NULL_ORDERING_SUPPORTED is False and (
3398                    (asc and nulls_sort_change == " NULLS LAST")
3399                    or (desc and nulls_sort_change == " NULLS FIRST")
3400                ):
3401                    # BigQuery does not allow these ordering/nulls combinations when used under
3402                    # an aggregation func or under a window containing one
3403                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
3404
3405                    if isinstance(ancestor, exp.Window):
3406                        ancestor = ancestor.this
3407                    if isinstance(ancestor, exp.AggFunc):
3408                        self.unsupported(
3409                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
3410                        )
3411                        nulls_sort_change = ""
3412                elif self.NULL_ORDERING_SUPPORTED is None:
3413                    if expression.this.is_int:
3414                        self.unsupported(
3415                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
3416                        )
3417                    elif not isinstance(expression.this, exp.Rand):
3418                        resolved = self._resolve_ordered_for_null_ordering_simulation(expression)
3419                        target = self.sql(resolved) if resolved is not None else this
3420                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
3421                        this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}"
3422                    nulls_sort_change = ""
3423
3424        with_fill = self.sql(expression, "with_fill")
3425        with_fill = f" {with_fill}" if with_fill else ""
3426
3427        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
3428
3429    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
3430        window_frame = self.sql(expression, "window_frame")
3431        window_frame = f"{window_frame} " if window_frame else ""
3432
3433        this = self.sql(expression, "this")
3434
3435        return f"{window_frame}{this}"
3436
3437    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
3438        partition = self.partition_by_sql(expression)
3439        order = self.sql(expression, "order")
3440        measures = self.expressions(expression, key="measures")
3441        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
3442        rows = self.sql(expression, "rows")
3443        rows = self.seg(rows) if rows else ""
3444        after = self.sql(expression, "after")
3445        after = self.seg(after) if after else ""
3446        pattern = self.sql(expression, "pattern")
3447        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
3448        definition_sqls = [
3449            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
3450            for definition in expression.args.get("define", [])
3451        ]
3452        definitions = self.expressions(sqls=definition_sqls)
3453        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
3454        body = "".join(
3455            (
3456                partition,
3457                order,
3458                measures,
3459                rows,
3460                after,
3461                pattern,
3462                define,
3463            )
3464        )
3465        alias = self.sql(expression, "alias")
3466        alias = f" {alias}" if alias else ""
3467        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3468
3469    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3470        limit = expression.args.get("limit")
3471
3472        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3473            count = limit.args.get("count")
3474            # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL
3475            # standard; emitting a bare "LIMIT" here would produce invalid SQL.
3476            limit = exp.Limit(
3477                expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1)
3478            )
3479        elif (
3480            self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all
3481        ):
3482            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3483
3484        return csv(
3485            *sqls,
3486            *[self.sql(join) for join in expression.args.get("joins") or []],
3487            self.sql(expression, "match"),
3488            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3489            self.sql(expression, "prewhere"),
3490            self.sql(expression, "where"),
3491            self.sql(expression, "connect"),
3492            self.sql(expression, "group"),
3493            self.sql(expression, "having"),
3494            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3495            self.sql(expression, "order"),
3496            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3497            *self.after_limit_modifiers(expression),
3498            self.sql(expression, "for_"),
3499            self.options_modifier(expression),
3500            sep="",
3501        )
3502
3503    def options_modifier(self, expression: exp.Expr) -> str:
3504        options = self.expressions(expression, key="options")
3505        return f" {options}" if options else ""
3506
3507    def forclause_sql(self, expression: exp.ForClause) -> str:
3508        kind = expression.args["kind"]
3509        if kind == "BROWSE":
3510            return f"{self.sep()}FOR BROWSE"
3511        # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means
3512        # the target dialect doesn't support QueryOption, so we drop the clause.
3513        options = self.expressions(expression, key="expressions")
3514        if not options:
3515            return ""
3516        return f"{self.sep()}FOR {kind}{self.seg(options)}"
3517
3518    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3519        self.unsupported("Unsupported query option.")
3520        return ""
3521
3522    def offset_limit_modifiers(
3523        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3524    ) -> list[str]:
3525        return [
3526            self.sql(expression, "offset") if fetch else self.sql(limit),
3527            self.sql(limit) if fetch else self.sql(expression, "offset"),
3528        ]
3529
3530    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3531        locks = self.expressions(expression, key="locks", sep=" ")
3532        locks = f" {locks}" if locks else ""
3533        return [locks, self.sql(expression, "sample")]
3534
3535    def select_sql(self, expression: exp.Select) -> str:
3536        into = expression.args.get("into")
3537        if not self.SUPPORTS_SELECT_INTO and into:
3538            into.pop()
3539
3540        hint = self.sql(expression, "hint")
3541        distinct = self.sql(expression, "distinct")
3542        distinct = f" {distinct}" if distinct else ""
3543        kind = self.sql(expression, "kind")
3544
3545        limit = expression.args.get("limit")
3546        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3547            top = self.limit_sql(limit, top=True)
3548            limit.pop()
3549        else:
3550            top = ""
3551
3552        expressions = self.expressions(expression)
3553
3554        if kind:
3555            if kind in self.SELECT_KINDS:
3556                kind = f" AS {kind}"
3557            else:
3558                if kind == "STRUCT":
3559                    expressions = self.expressions(
3560                        sqls=[
3561                            self.sql(
3562                                exp.Struct(
3563                                    expressions=[
3564                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3565                                        if isinstance(e, exp.Alias)
3566                                        else e
3567                                        for e in expression.expressions
3568                                    ]
3569                                )
3570                            )
3571                        ]
3572                    )
3573                kind = ""
3574
3575        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3576        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3577
3578        exclude = expression.args.get("exclude")
3579
3580        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3581            exclude_sql = self.expressions(sqls=exclude, flat=True)
3582            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3583
3584        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3585        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3586        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3587        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3588        sql = self.query_modifiers(
3589            expression,
3590            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3591            self.sql(expression, "into", comment=False),
3592            self.sql(expression, "from_", comment=False),
3593        )
3594
3595        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3596        if expression.args.get("with_"):
3597            sql = self.maybe_comment(sql, expression)
3598            expression.pop_comments()
3599
3600        sql = self.prepend_ctes(expression, sql)
3601
3602        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3603            expression.set("exclude", None)
3604            subquery = expression.subquery(copy=False)
3605            star = exp.Star(except_=exclude)
3606            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3607
3608        if not self.SUPPORTS_SELECT_INTO and into:
3609            if into.args.get("temporary"):
3610                table_kind = " TEMPORARY"
3611            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3612                table_kind = " UNLOGGED"
3613            else:
3614                table_kind = ""
3615            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3616
3617        return sql
3618
3619    def schema_sql(self, expression: exp.Schema) -> str:
3620        this = self.sql(expression, "this")
3621        sql = self.schema_columns_sql(expression)
3622        return f"{this} {sql}" if this and sql else this or sql
3623
3624    def schema_columns_sql(self, expression: exp.Expr) -> str:
3625        if expression.expressions:
3626            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3627        return ""
3628
3629    def star_sql(self, expression: exp.Star) -> str:
3630        except_ = self.expressions(expression, key="except_", flat=True)
3631        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3632        replace = self.expressions(expression, key="replace", flat=True)
3633        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3634        rename = self.expressions(expression, key="rename", flat=True)
3635        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3636        ilike = self.sql(expression, "ilike")
3637        ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else ""
3638        return f"*{ilike}{except_}{replace}{rename}"
3639
3640    def parameter_sql(self, expression: exp.Parameter) -> str:
3641        this = self.sql(expression, "this")
3642        return f"{self.PARAMETER_TOKEN}{this}"
3643
3644    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3645        this = self.sql(expression, "this")
3646        kind = expression.text("kind")
3647        if kind:
3648            kind = f"{kind}."
3649        return f"@@{kind}{this}"
3650
3651    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3652        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3653
3654    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3655        alias = self.sql(expression, "alias")
3656        alias = f"{sep}{alias}" if alias else ""
3657        sample = self.sql(expression, "sample")
3658        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3659            alias = f"{sample}{alias}"
3660
3661            # Set to None so it's not generated again by self.query_modifiers()
3662            expression.set("sample", None)
3663
3664        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3665        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3666        return self.prepend_ctes(expression, sql)
3667
3668    def qualify_sql(self, expression: exp.Qualify) -> str:
3669        this = self.indent(self.sql(expression, "this"))
3670        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3671
3672    def unnest_sql(self, expression: exp.Unnest) -> str:
3673        args = self.expressions(expression, flat=True)
3674
3675        alias = expression.args.get("alias")
3676        offset = expression.args.get("offset")
3677
3678        if self.UNNEST_WITH_ORDINALITY:
3679            if alias and isinstance(offset, exp.Expr):
3680                alias.append("columns", offset)
3681                expression.set("offset", None)
3682
3683        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3684            columns = alias.columns
3685            alias = self.sql(columns[0]) if columns else ""
3686        else:
3687            alias = self.sql(alias)
3688
3689        alias = f" AS {alias}" if alias else alias
3690        if self.UNNEST_WITH_ORDINALITY:
3691            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3692        else:
3693            if isinstance(offset, exp.Expr):
3694                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3695            elif offset:
3696                suffix = f"{alias} WITH OFFSET"
3697            else:
3698                suffix = alias
3699
3700        return f"UNNEST({args}){suffix}"
3701
3702    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3703        return ""
3704
3705    def where_sql(self, expression: exp.Where) -> str:
3706        this = self.indent(self.sql(expression, "this"))
3707        return f"{self.seg('WHERE')}{self.sep()}{this}"
3708
3709    def window_sql(self, expression: exp.Window) -> str:
3710        this = self.sql(expression, "this")
3711        partition = self.partition_by_sql(expression)
3712        order = expression.args.get("order")
3713        order = self.order_sql(order, flat=True) if order else ""
3714        spec = self.sql(expression, "spec")
3715        alias = self.sql(expression, "alias")
3716        over = self.sql(expression, "over") or "OVER"
3717
3718        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3719
3720        first = expression.args.get("first")
3721        if first is None:
3722            first = ""
3723        else:
3724            first = "FIRST" if first else "LAST"
3725
3726        if not partition and not order and not spec and alias:
3727            # WINDOW w2 AS w1 is unparseable in DuckDB/Postgres; parens are required.
3728            if expression.arg_key == "windows":
3729                return f"{this} ({alias})"
3730            return f"{this} {alias}"
3731
3732        args = self.format_args(
3733            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3734        )
3735        return f"{this} ({args})"
3736
3737    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3738        partition = self.expressions(expression, key="partition_by", flat=True)
3739        return f"PARTITION BY {partition}" if partition else ""
3740
3741    def partitionbyexcluding_sql(self, expression: exp.PartitionByExcluding) -> str:
3742        return f"EXCLUDING {self.expressions(expression, flat=True)}"
3743
3744    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3745        kind = self.sql(expression, "kind")
3746        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3747        end = (
3748            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3749            or "CURRENT ROW"
3750        )
3751
3752        window_spec = f"{kind} BETWEEN {start} AND {end}"
3753
3754        exclude = self.sql(expression, "exclude")
3755        if exclude:
3756            if self.SUPPORTS_WINDOW_EXCLUDE:
3757                window_spec += f" EXCLUDE {exclude}"
3758            else:
3759                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3760
3761        return window_spec
3762
3763    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3764        this = self.sql(expression, "this")
3765        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3766        return f"{this} WITHIN GROUP ({expression_sql})"
3767
3768    def between_sql(self, expression: exp.Between) -> str:
3769        this = self.sql(expression, "this")
3770        low = self.sql(expression, "low")
3771        high = self.sql(expression, "high")
3772        symmetric = expression.args.get("symmetric")
3773
3774        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3775            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3776
3777        flag = (
3778            " SYMMETRIC"
3779            if symmetric
3780            else " ASYMMETRIC"
3781            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3782            else ""  # silently drop ASYMMETRIC – semantics identical
3783        )
3784        return f"{this} BETWEEN{flag} {low} AND {high}"
3785
3786    def bracket_offset_expressions(
3787        self, expression: exp.Bracket, index_offset: int | None = None
3788    ) -> list[exp.Expr]:
3789        if expression.args.get("json_access"):
3790            return expression.expressions
3791
3792        return apply_index_offset(
3793            expression.this,
3794            expression.expressions,
3795            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3796            dialect=self.dialect,
3797        )
3798
3799    def bracket_sql(self, expression: exp.Bracket) -> str:
3800        expressions = self.bracket_offset_expressions(expression)
3801        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3802        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3803
3804    def all_sql(self, expression: exp.All) -> str:
3805        this = self.sql(expression, "this")
3806        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3807            this = self.wrap(this)
3808        return f"ALL {this}"
3809
3810    def any_sql(self, expression: exp.Any) -> str:
3811        this = self.sql(expression, "this")
3812        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3813            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3814                this = self.wrap(this)
3815            return f"ANY{this}"
3816        return f"ANY {this}"
3817
3818    def exists_sql(self, expression: exp.Exists) -> str:
3819        return f"EXISTS{self.wrap(expression)}"
3820
3821    def case_sql(self, expression: exp.Case) -> str:
3822        this = self.sql(expression, "this")
3823        statements = [f"CASE {this}" if this else "CASE"]
3824
3825        for e in expression.args["ifs"]:
3826            statements.append(f"WHEN {self.sql(e, 'this')}")
3827            statements.append(f"THEN {self.sql(e, 'true')}")
3828
3829        default = self.sql(expression, "default")
3830
3831        if default:
3832            statements.append(f"ELSE {default}")
3833
3834        statements.append("END")
3835
3836        if self.pretty and self.too_wide(statements):
3837            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3838
3839        return " ".join(statements)
3840
3841    def constraint_sql(self, expression: exp.Constraint) -> str:
3842        this = self.sql(expression, "this")
3843        expressions = self.expressions(expression, flat=True)
3844        return f"CONSTRAINT {this} {expressions}"
3845
3846    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3847        order = expression.args.get("order")
3848        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3849        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3850
3851    def extract_sql(self, expression: exp.Extract) -> str:
3852        import sqlglot.dialects.dialect
3853
3854        this = (
3855            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3856            if self.NORMALIZE_EXTRACT_DATE_PARTS
3857            else expression.this
3858        )
3859        if self.EXTRACT_ALLOWS_QUOTES:
3860            this_sql = self.sql(this)
3861        elif isinstance(this, exp.WeekStart):
3862            this_sql = self.weekstart_name(this)
3863        else:
3864            this_sql = this.name
3865        expression_sql = self.sql(expression, "expression")
3866
3867        return f"EXTRACT({this_sql} FROM {expression_sql})"
3868
3869    def trim_sql(self, expression: exp.Trim) -> str:
3870        trim_type = self.sql(expression, "position")
3871
3872        if trim_type == "LEADING":
3873            func_name = "LTRIM"
3874        elif trim_type == "TRAILING":
3875            func_name = "RTRIM"
3876        else:
3877            func_name = "TRIM"
3878
3879        return self.func(func_name, expression.this, expression.expression)
3880
3881    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3882        args = expression.expressions
3883        if isinstance(expression, exp.ConcatWs):
3884            args = args[1:]  # Skip the delimiter
3885
3886        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3887            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3888
3889        concat_coalesce = (
3890            self.dialect.CONCAT_WS_COALESCE
3891            if isinstance(expression, exp.ConcatWs)
3892            else self.dialect.CONCAT_COALESCE
3893        )
3894
3895        if not concat_coalesce and expression.args.get("coalesce"):
3896
3897            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3898                if not e.type:
3899                    import sqlglot.optimizer.annotate_types
3900
3901                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3902
3903                if e.is_string or e.is_type(exp.DType.ARRAY):
3904                    return e
3905
3906                return exp.func("coalesce", e, exp.Literal.string(""))
3907
3908            args = [_wrap_with_coalesce(e) for e in args]
3909
3910        return args
3911
3912    def concat_sql(self, expression: exp.Concat) -> str:
3913        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3914            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3915            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3916            # instead of coalescing them to empty string.
3917            import sqlglot.dialects.dialect
3918
3919            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3920
3921        expressions = self.convert_concat_args(expression)
3922
3923        # Some dialects don't allow a single-argument CONCAT call
3924        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3925            return self.sql(expressions[0])
3926
3927        return self.func("CONCAT", *expressions)
3928
3929    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3930        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3931            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3932            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3933            all_args = expression.expressions
3934            expression.set("coalesce", True)
3935            return self.sql(
3936                exp.case()
3937                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3938                .else_(expression)
3939            )
3940
3941        return self.func(
3942            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3943        )
3944
3945    def check_sql(self, expression: exp.Check) -> str:
3946        this = self.sql(expression, key="this")
3947        return f"CHECK ({this})"
3948
3949    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3950        this = self.sql(expression, "this")
3951        this = f" {this}" if this else ""
3952        expressions = self.expressions(expression, flat=True)
3953        expressions = f" ({expressions})" if expressions else ""
3954        reference = self.sql(expression, "reference")
3955        reference = f" {reference}" if reference else ""
3956        delete = self.sql(expression, "delete")
3957        delete = f" ON DELETE {delete}" if delete else ""
3958        update = self.sql(expression, "update")
3959        update = f" ON UPDATE {update}" if update else ""
3960        options = self.expressions(expression, key="options", flat=True, sep=" ")
3961        options = f" {options}" if options else ""
3962        return f"FOREIGN KEY{this}{expressions}{reference}{delete}{update}{options}"
3963
3964    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3965        this = self.sql(expression, "this")
3966        this = f" {this}" if this else ""
3967        expressions = self.expressions(expression, flat=True)
3968        include = self.sql(expression, "include")
3969        options = self.expressions(expression, key="options", flat=True, sep=" ")
3970        options = f" {options}" if options else ""
3971        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3972
3973    def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str:
3974        self.unsupported("TIMESERIES primary key columns are not supported")
3975        return self.sql(expression, "this")
3976
3977    def if_sql(self, expression: exp.If) -> str:
3978        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3979
3980    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3981        if self.MATCH_AGAINST_TABLE_PREFIX:
3982            expressions = []
3983            for expr in expression.expressions:
3984                if isinstance(expr, exp.Table):
3985                    expressions.append(f"TABLE {self.sql(expr)}")
3986                else:
3987                    expressions.append(expr)
3988        else:
3989            expressions = expression.expressions
3990
3991        modifier = expression.args.get("modifier")
3992        modifier = f" {modifier}" if modifier else ""
3993        return (
3994            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3995        )
3996
3997    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3998        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3999
4000    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
4001        path = self.expressions(expression, sep="", flat=True).lstrip(".")
4002
4003        if self.QUOTE_JSON_PATH:
4004            path = self.escape_str(path)
4005            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
4006
4007        return path
4008
4009    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
4010        if isinstance(expression, exp.JSONPathPart):
4011            transform = self.TRANSFORMS.get(expression.__class__)
4012            if not callable(transform):
4013                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
4014                return ""
4015
4016            return transform(self, expression)
4017
4018        if isinstance(expression, int):
4019            return str(expression)
4020
4021        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
4022            escaped = expression.replace("'", "\\'")
4023            escaped = f"'{escaped}'"
4024        else:
4025            escaped = expression.replace('"', '\\"')
4026            escaped = f'"{escaped}"'
4027
4028        return escaped
4029
4030    def formatjson_sql(self, expression: exp.FormatJson) -> str:
4031        return f"{self.sql(expression, 'this')} FORMAT JSON"
4032
4033    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
4034        # Output the Teradata column FORMAT override.
4035        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
4036        this = self.sql(expression, "this")
4037        fmt = self.sql(expression, "format")
4038        return f"{this} (FORMAT {fmt})"
4039
4040    def _jsonobject_sql(
4041        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
4042    ) -> str:
4043        null_handling = expression.args.get("null_handling")
4044        null_handling = f" {null_handling}" if null_handling else ""
4045
4046        unique_keys = expression.args.get("unique_keys")
4047        if unique_keys is not None:
4048            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
4049        else:
4050            unique_keys = ""
4051
4052        return_type = self.sql(expression, "return_type")
4053        return_type = f" RETURNING {return_type}" if return_type else ""
4054        encoding = self.sql(expression, "encoding")
4055        encoding = f" ENCODING {encoding}" if encoding else ""
4056
4057        if not name:
4058            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
4059
4060        return self.func(
4061            name,
4062            *expression.expressions,
4063            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
4064        )
4065
4066    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
4067        null_handling = expression.args.get("null_handling")
4068        null_handling = f" {null_handling}" if null_handling else ""
4069        return_type = self.sql(expression, "return_type")
4070        return_type = f" RETURNING {return_type}" if return_type else ""
4071        strict = " STRICT" if expression.args.get("strict") else ""
4072        return self.func(
4073            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
4074        )
4075
4076    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
4077        this = self.sql(expression, "this")
4078        order = self.sql(expression, "order")
4079        null_handling = expression.args.get("null_handling")
4080        null_handling = f" {null_handling}" if null_handling else ""
4081        return_type = self.sql(expression, "return_type")
4082        return_type = f" RETURNING {return_type}" if return_type else ""
4083        strict = " STRICT" if expression.args.get("strict") else ""
4084        return self.func(
4085            "JSON_ARRAYAGG",
4086            this,
4087            suffix=f"{order}{null_handling}{return_type}{strict})",
4088        )
4089
4090    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
4091        path = self.sql(expression, "path")
4092        path = f" PATH {path}" if path else ""
4093        nested_schema = self.sql(expression, "nested_schema")
4094
4095        if nested_schema:
4096            return f"NESTED{path} {nested_schema}"
4097
4098        this = self.sql(expression, "this")
4099        kind = self.sql(expression, "kind")
4100        kind = f" {kind}" if kind else ""
4101        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
4102
4103        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
4104        return f"{this}{kind}{format_json}{path}{ordinality}"
4105
4106    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
4107        return self.func("COLUMNS", *expression.expressions)
4108
4109    def jsontable_sql(self, expression: exp.JSONTable) -> str:
4110        this = self.sql(expression, "this")
4111        path = self.sql(expression, "path")
4112        path = f", {path}" if path else ""
4113        error_handling = expression.args.get("error_handling")
4114        error_handling = f" {error_handling}" if error_handling else ""
4115        empty_handling = expression.args.get("empty_handling")
4116        empty_handling = f" {empty_handling}" if empty_handling else ""
4117        schema = self.sql(expression, "schema")
4118        return self.func(
4119            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
4120        )
4121
4122    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
4123        this = self.sql(expression, "this")
4124        kind = self.sql(expression, "kind")
4125        path = self.sql(expression, "path")
4126        path = f" {path}" if path else ""
4127        as_json = " AS JSON" if expression.args.get("as_json") else ""
4128        return f"{this} {kind}{path}{as_json}"
4129
4130    def openjson_sql(self, expression: exp.OpenJSON) -> str:
4131        this = self.sql(expression, "this")
4132        path = self.sql(expression, "path")
4133        path = f", {path}" if path else ""
4134        expressions = self.expressions(expression)
4135        with_ = (
4136            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
4137            if expressions
4138            else ""
4139        )
4140        return f"OPENJSON({this}{path}){with_}"
4141
4142    def in_sql(self, expression: exp.In) -> str:
4143        query = expression.args.get("query")
4144        unnest = expression.args.get("unnest")
4145        field = expression.args.get("field")
4146        is_global = " GLOBAL" if expression.args.get("is_global") else ""
4147
4148        if query:
4149            in_sql = self.sql(query)
4150        elif unnest:
4151            in_sql = self.in_unnest_op(unnest)
4152        elif field:
4153            in_sql = self.sql(field)
4154        else:
4155            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
4156
4157        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
4158
4159    def in_unnest_op(self, unnest: exp.Unnest) -> str:
4160        return f"(SELECT {self.sql(unnest)})"
4161
4162    def interval_sql(self, expression: exp.Interval) -> str:
4163        include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance(
4164            expression.find_ancestor(exp.AutoRefreshProperty, exp.Select),
4165            exp.AutoRefreshProperty,
4166        )
4167        interval_keyword = "INTERVAL" if include_keyword else ""
4168        unit_expression = expression.args.get("unit")
4169        unit = self.sql(unit_expression) if unit_expression else ""
4170        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
4171            unit = self.TIME_PART_SINGULARS.get(unit, unit)
4172        unit = f" {unit}" if unit else ""
4173
4174        if self.SINGLE_STRING_INTERVAL:
4175            this = expression.this.name if expression.this else ""
4176            if this:
4177                interval_keyword = f"{interval_keyword} " if interval_keyword else ""
4178                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
4179                    return f"{interval_keyword}{self.sql(exp.Literal.string(this))}{unit}"
4180                return f"{interval_keyword}{self.sql(exp.Literal.string(f'{this}{unit}'))}"
4181            return f"{interval_keyword}{unit}"
4182
4183        this = self.sql(expression, "this")
4184        if this:
4185            if not include_keyword and expression.this.is_string:
4186                this = expression.this.name
4187            if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES):
4188                this = f"({this})"
4189            if include_keyword:
4190                this = f" {this}"
4191
4192        return f"{interval_keyword}{this}{unit}"
4193
4194    def return_sql(self, expression: exp.Return) -> str:
4195        return f"RETURN {self.sql(expression, 'this')}"
4196
4197    def reference_sql(self, expression: exp.Reference) -> str:
4198        this = self.sql(expression, "this")
4199        expressions = self.expressions(expression, flat=True)
4200        expressions = f"({expressions})" if expressions else ""
4201        options = self.expressions(expression, key="options", flat=True, sep=" ")
4202        options = f" {options}" if options else ""
4203        return f"REFERENCES {this}{expressions}{options}"
4204
4205    def anonymous_sql(self, expression: exp.Anonymous) -> str:
4206        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
4207        parent = expression.parent
4208        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
4209
4210        return self.func(
4211            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
4212        )
4213
4214    def paren_sql(self, expression: exp.Paren) -> str:
4215        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
4216        return f"({sql}{self.seg(')', sep='')}"
4217
4218    def neg_sql(self, expression: exp.Neg) -> str:
4219        # This makes sure we don't convert "- - 5" to "--5", which is a comment
4220        this_sql = self.sql(expression, "this")
4221        sep = " " if this_sql[0] == "-" else ""
4222        return f"-{sep}{this_sql}"
4223
4224    def not_sql(self, expression: exp.Not) -> str:
4225        return f"NOT {self.sql(expression, 'this')}"
4226
4227    def alias_sql(self, expression: exp.Alias) -> str:
4228        alias = self.sql(expression, "alias")
4229        alias = f" AS {alias}" if alias else ""
4230        return f"{self.sql(expression, 'this')}{alias}"
4231
4232    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
4233        alias = expression.args["alias"]
4234
4235        parent = expression.parent
4236        pivot = parent and parent.parent
4237
4238        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
4239            identifier_alias = isinstance(alias, exp.Identifier)
4240            literal_alias = isinstance(alias, exp.Literal)
4241
4242            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4243                alias.replace(exp.Literal.string(alias.output_name))
4244            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
4245                alias.replace(exp.to_identifier(alias.output_name))
4246
4247        return self.alias_sql(expression)
4248
4249    def aliases_sql(self, expression: exp.Aliases) -> str:
4250        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
4251
4252    def atindex_sql(self, expression: exp.AtIndex) -> str:
4253        this = self.sql(expression, "this")
4254        index = self.sql(expression, "expression")
4255        return f"{this} AT {index}"
4256
4257    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
4258        this = self.sql(expression, "this")
4259        zone = self.sql(expression, "zone")
4260        return f"{this} AT TIME ZONE {zone}"
4261
4262    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
4263        this = self.sql(expression, "this")
4264        zone = self.sql(expression, "zone")
4265        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
4266
4267    def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str:
4268        return self.sql(exp.cast(expression.this, exp.DType.DATE))
4269
4270    def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:
4271        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4272
4273    def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str:
4274        return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))
4275
4276    def add_sql(self, expression: exp.Add) -> str:
4277        return self.binary(expression, "+")
4278
4279    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
4280        return self.connector_sql(expression, "AND", stack)
4281
4282    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
4283        return self.connector_sql(expression, "OR", stack)
4284
4285    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
4286        return self.connector_sql(expression, "XOR", stack)
4287
4288    def connector_sql(
4289        self,
4290        expression: exp.Connector,
4291        op: str,
4292        stack: list[str | exp.Expr] | None = None,
4293    ) -> str:
4294        if stack is not None:
4295            stack.append(expression.right)
4296            if expression.comments and self.comments:
4297                op = self.maybe_comment(op, comments=expression.comments)
4298
4299            stack.extend((op, expression.left))
4300            return op
4301
4302        stack = [expression]
4303        sqls: list[str] = []
4304        ops = set()
4305
4306        while stack:
4307            node = stack.pop()
4308            if isinstance(node, exp.Connector):
4309                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
4310            else:
4311                sql = self.sql(node)
4312                if sqls and sqls[-1] in ops:
4313                    sqls[-1] += f" {sql}"
4314                else:
4315                    sqls.append(sql)
4316
4317        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
4318        return sep.join(sqls)
4319
4320    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
4321        return self.binary(expression, "&")
4322
4323    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
4324        return self.binary(expression, "<<")
4325
4326    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
4327        return f"~{self.sql(expression, 'this')}"
4328
4329    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
4330        return self.binary(expression, "|")
4331
4332    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
4333        return self.binary(expression, ">>")
4334
4335    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
4336        return self.binary(expression, "^")
4337
4338    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
4339        format_sql = self.sql(expression, "format")
4340        format_sql = f" FORMAT {format_sql}" if format_sql else ""
4341        to_sql = self.sql(expression, "to")
4342        to_sql = f" {to_sql}" if to_sql else ""
4343        action = self.sql(expression, "action")
4344        action = f" {action}" if action else ""
4345        default = self.sql(expression, "default")
4346        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
4347        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4348
4349    # Base implementation that excludes safe, zone, and target_type metadata args
4350    def strtotime_sql(self, expression: exp.StrToTime) -> str:
4351        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
4352
4353    # Base implementation that excludes the safe and default_year metadata args
4354    def strtodate_sql(self, expression: exp.StrToDate) -> str:
4355        return self.func("STR_TO_DATE", expression.this, expression.args.get("format"))
4356
4357    def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str:
4358        return self.func(
4359            "PARSE_DATETIME",
4360            expression.this,
4361            expression.args.get("format"),
4362            expression.args.get("zone"),
4363        )
4364
4365    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
4366        zone = self.sql(expression, "this")
4367        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
4368
4369    def collate_sql(self, expression: exp.Collate) -> str:
4370        if self.COLLATE_IS_FUNC:
4371            return self.function_fallback_sql(expression)
4372        return self.binary(expression, "COLLATE")
4373
4374    def command_sql(self, expression: exp.Command) -> str:
4375        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
4376
4377    def comment_sql(self, expression: exp.Comment) -> str:
4378        this = self.sql(expression, "this")
4379        kind = expression.args["kind"]
4380        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
4381        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
4382        expression_sql = self.sql(expression, "expression")
4383        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4384
4385    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
4386        this = self.sql(expression, "this")
4387        delete = " DELETE" if expression.args.get("delete") else ""
4388        recompress = self.sql(expression, "recompress")
4389        recompress = f" RECOMPRESS {recompress}" if recompress else ""
4390        to_disk = self.sql(expression, "to_disk")
4391        to_disk = f" TO DISK {to_disk}" if to_disk else ""
4392        to_volume = self.sql(expression, "to_volume")
4393        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
4394        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4395
4396    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
4397        where = self.sql(expression, "where")
4398        group = self.sql(expression, "group")
4399        aggregates = self.expressions(expression, key="aggregates")
4400        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
4401
4402        if not (where or group or aggregates) and len(expression.expressions) == 1:
4403            return f"TTL {self.expressions(expression, flat=True)}"
4404
4405        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4406
4407    def transaction_sql(self, expression: exp.Transaction) -> str:
4408        modes = self.expressions(expression, key="modes")
4409        modes = f" {modes}" if modes else ""
4410        return f"BEGIN{modes}"
4411
4412    def commit_sql(self, expression: exp.Commit) -> str:
4413        chain = expression.args.get("chain")
4414        if chain is not None:
4415            chain = " AND CHAIN" if chain else " AND NO CHAIN"
4416
4417        return f"COMMIT{chain or ''}"
4418
4419    def rollback_sql(self, expression: exp.Rollback) -> str:
4420        savepoint = expression.args.get("savepoint")
4421        savepoint = f" TO {savepoint}" if savepoint else ""
4422        return f"ROLLBACK{savepoint}"
4423
4424    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
4425        this = self.sql(expression, "this")
4426
4427        exists = ""
4428        if expression.args.get("exists"):
4429            if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS:
4430                exists = " IF EXISTS"
4431            else:
4432                self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect")
4433
4434        dtype = self.sql(expression, "dtype")
4435        if dtype:
4436            collate = self.sql(expression, "collate")
4437            collate = f" COLLATE {collate}" if collate else ""
4438            using = self.sql(expression, "using")
4439            using = f" USING {using}" if using else ""
4440            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
4441            null_constraint = self._alter_column_null_constraint_sql(expression)
4442
4443            return (
4444                f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}"
4445                f"{collate}{using}{null_constraint}"
4446            )
4447
4448        default = self.sql(expression, "default")
4449        if default:
4450            return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}"
4451
4452        comment = self.sql(expression, "comment")
4453        if comment:
4454            return f"ALTER COLUMN{exists} {this} COMMENT {comment}"
4455
4456        visible = expression.args.get("visible")
4457        if visible:
4458            return f"ALTER COLUMN{exists} {this} SET {visible}"
4459
4460        allow_null = expression.args.get("allow_null")
4461        drop = expression.args.get("drop")
4462
4463        if not drop and not allow_null:
4464            self.unsupported("Unsupported ALTER COLUMN syntax")
4465
4466        if allow_null is not None:
4467            keyword = "DROP" if drop else "SET"
4468            return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL"
4469
4470        return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4471
4472    def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str:
4473        allow_null = expression.args.get("allow_null")
4474        if allow_null is None:
4475            return ""
4476
4477        if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY:
4478            self.unsupported("ALTER COLUMN cannot set nullability along with a type")
4479            return ""
4480
4481        return " NULL" if allow_null else " NOT NULL"
4482
4483    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
4484        this = self.sql(expression, "this")
4485        rename_from = self.sql(expression, "rename_from")
4486        if rename_from:
4487            if not self.SUPPORTS_CHANGE_COLUMN:
4488                self.unsupported("CHANGE COLUMN is not supported in this dialect")
4489            return f"CHANGE COLUMN {rename_from} {this}"
4490        if not self.SUPPORTS_MODIFY_COLUMN:
4491            self.unsupported("MODIFY COLUMN is not supported in this dialect")
4492        return f"MODIFY COLUMN {this}"
4493
4494    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
4495        this = self.sql(expression, "this")
4496
4497        visible = expression.args.get("visible")
4498        visible_sql = "VISIBLE" if visible else "INVISIBLE"
4499
4500        return f"ALTER INDEX {this} {visible_sql}"
4501
4502    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
4503        this = self.sql(expression, "this")
4504        if not isinstance(expression.this, exp.Var):
4505            this = f"KEY DISTKEY {this}"
4506        return f"ALTER DISTSTYLE {this}"
4507
4508    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
4509        compound = " COMPOUND" if expression.args.get("compound") else ""
4510        this = self.sql(expression, "this")
4511        expressions = self.expressions(expression, flat=True)
4512        expressions = f"({expressions})" if expressions else ""
4513        return f"ALTER{compound} SORTKEY {this or expressions}"
4514
4515    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
4516        if not self.RENAME_TABLE_WITH_DB:
4517            # Remove db from tables
4518            expression = expression.transform(
4519                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
4520            ).assert_is(exp.AlterRename)
4521        this = self.sql(expression, "this")
4522        to_kw = " TO" if include_to else ""
4523        return f"RENAME{to_kw} {this}"
4524
4525    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
4526        exists = " IF EXISTS" if expression.args.get("exists") else ""
4527        old_column = self.sql(expression, "this")
4528        new_column = self.sql(expression, "to")
4529        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
4530
4531    def alterset_sql(self, expression: exp.AlterSet) -> str:
4532        exprs = self.expressions(expression, flat=True)
4533        if self.ALTER_SET_WRAPPED:
4534            exprs = f"({exprs})"
4535
4536        return f"SET {exprs}"
4537
4538    def alter_sql(self, expression: exp.Alter) -> str:
4539        actions = expression.actions
4540
4541        if (
4542            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
4543            and actions
4544            and isinstance(actions[0], exp.ColumnDef)
4545        ):
4546            actions_sql = self.expressions(expression, key="actions", flat=True)
4547            actions_sql = f"ADD {actions_sql}"
4548        else:
4549            actions_list = []
4550            for action in actions:
4551                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4552                    action_sql = self.add_column_sql(action)
4553                else:
4554                    action_sql = self.sql(action)
4555                    if isinstance(action, exp.Query):
4556                        action_sql = f"AS {action_sql}"
4557
4558                actions_list.append(action_sql)
4559
4560            actions_sql = self.format_args(*actions_list).lstrip("\n")
4561
4562        iceberg = (
4563            "ICEBERG "
4564            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4565            else ""
4566        )
4567        exists = " IF EXISTS" if expression.args.get("exists") else ""
4568        on_cluster = self.sql(expression, "cluster")
4569        on_cluster = f" {on_cluster}" if on_cluster else ""
4570        only = " ONLY" if expression.args.get("only") else ""
4571        options = expression.args.get("options") or []
4572        options_sql = self.expressions(
4573            sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)]
4574        )
4575        options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql
4576        partitions_sql = self.expressions(
4577            sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)],
4578            sep=" ",
4579            flat=True,
4580        )
4581        if partitions_sql and (actions_sql or options_sql):
4582            partitions_sql = f" {partitions_sql}"
4583        kind = self.sql(expression, "kind")
4584        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4585        check = " WITH CHECK" if expression.args.get("check") else ""
4586        cascade = (
4587            " CASCADE"
4588            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4589            else ""
4590        )
4591        this = self.sql(expression, "this")
4592        this = f" {this}" if this else ""
4593
4594        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}"
4595
4596    def altersession_sql(self, expression: exp.AlterSession) -> str:
4597        items_sql = self.expressions(expression, flat=True)
4598        keyword = "UNSET" if expression.args.get("unset") else "SET"
4599        return f"{keyword} {items_sql}"
4600
4601    def add_column_sql(self, expression: exp.Expr) -> str:
4602        column_text = ""
4603        if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4604            if isinstance(expression, exp.ColumnDef):
4605                column_text = " COLUMN"
4606            elif isinstance(expression, exp.Schema):
4607                column_text = " COLUMNS"
4608
4609        if isinstance(expression, exp.Schema) and not self.ALTER_TABLE_ADD_COLUMNS_WRAPPED:
4610            sql = self.expressions(expression, flat=True)
4611        else:
4612            sql = self.sql(expression)
4613
4614        return f"ADD{column_text} {sql}"
4615
4616    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4617        expressions = self.expressions(expression)
4618        exists = " IF EXISTS " if expression.args.get("exists") else " "
4619        return f"DROP{exists}{expressions}"
4620
4621    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4622        return "DROP PRIMARY KEY"
4623
4624    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4625        return f"ADD {self.expressions(expression, indent=False)}"
4626
4627    def replacecolumns_sql(self, expression: exp.ReplaceColumns) -> str:
4628        return f"REPLACE COLUMNS ({self.expressions(expression, flat=True)})"
4629
4630    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4631        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4632        location = self.sql(expression, "location")
4633        location = f" {location}" if location else ""
4634        return f"ADD {exists}{self.sql(expression.this)}{location}"
4635
4636    def distinct_sql(self, expression: exp.Distinct) -> str:
4637        this = self.expressions(expression, flat=True)
4638
4639        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4640            case = exp.case()
4641            for arg in expression.expressions:
4642                case = case.when(arg.is_(exp.null()), exp.null())
4643            this = self.sql(case.else_(f"({this})"))
4644
4645        this = f" {this}" if this else ""
4646
4647        on = self.sql(expression, "on")
4648        on = f" ON {on}" if on else ""
4649        return f"DISTINCT{this}{on}"
4650
4651    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4652        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4653
4654    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4655        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4656
4657    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4658        this_sql = self.sql(expression, "this")
4659        expression_sql = self.sql(expression, "expression")
4660        kind = "MAX" if expression.args.get("max") else "MIN"
4661        return f"{this_sql} HAVING {kind} {expression_sql}"
4662
4663    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4664        return self._truncated_div_sql(expression.this, expression.expression)
4665
4666    def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str:
4667        div: exp.Expr = exp.Div(this=this, expression=divisor)
4668        if self.CAST_TO_INT_ROUNDS:
4669            div = exp.Trunc(this=div)
4670
4671        return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT)))
4672
4673    def dpipe_sql(self, expression: exp.DPipe) -> str:
4674        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4675            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4676        return self.binary(expression, "||")
4677
4678    def div_sql(self, expression: exp.Div) -> str:
4679        l, r = expression.left, expression.right
4680
4681        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4682            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4683
4684        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4685            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4686                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4687
4688        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4689            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4690                # Native integer division depends on the target's operand types, which may differ
4691                # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are
4692                # integer literals or casts
4693                if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and (
4694                    expression.args.get("safe") or not self.SAFE_INT_DIVISION
4695                ):
4696                    return self.sql(exp.IntDiv(this=l, expression=r))
4697                return self._truncated_div_sql(l, r)
4698
4699        return self.binary(expression, "/")
4700
4701    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4702        n = exp._wrap(expression.this, exp.Binary)
4703        d = exp._wrap(expression.expression, exp.Binary)
4704        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4705
4706    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4707        return self.binary(expression, "OVERLAPS")
4708
4709    def distance_sql(self, expression: exp.Distance) -> str:
4710        return self.binary(expression, "<->")
4711
4712    def distancend_sql(self, expression: exp.DistanceNd) -> str:
4713        return self.binary(expression, "<<->>")
4714
4715    def dot_sql(self, expression: exp.Dot) -> str:
4716        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4717
4718    def eq_sql(self, expression: exp.EQ) -> str:
4719        return self.binary(expression, "=")
4720
4721    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4722        return self.binary(expression, ":=")
4723
4724    def escape_sql(self, expression: exp.Escape) -> str:
4725        this = expression.this
4726        if (
4727            isinstance(this, (exp.Like, exp.ILike))
4728            and isinstance(this.expression, (exp.All, exp.Any))
4729            and not self.SUPPORTS_LIKE_QUANTIFIERS
4730        ):
4731            return self._like_sql(this, escape=expression)
4732        return self.binary(expression, "ESCAPE")
4733
4734    def glob_sql(self, expression: exp.Glob) -> str:
4735        return self.binary(expression, "GLOB")
4736
4737    def gt_sql(self, expression: exp.GT) -> str:
4738        return self.binary(expression, ">")
4739
4740    def gte_sql(self, expression: exp.GTE) -> str:
4741        return self.binary(expression, ">=")
4742
4743    def is_sql(self, expression: exp.Is) -> str:
4744        negate = expression.args.get("negate")
4745        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4746            positive = bool(expression.expression.this) != bool(negate)
4747            return self.sql(expression.this if positive else exp.not_(expression.this))
4748        return self.binary(expression, "IS NOT" if negate else "IS")
4749
4750    def _like_sql(
4751        self,
4752        expression: exp.Like | exp.ILike,
4753        escape: exp.Escape | None = None,
4754    ) -> str:
4755        this = expression.this
4756        rhs = expression.expression
4757
4758        if isinstance(expression, exp.Like):
4759            exp_class: type[exp.Like | exp.ILike] = exp.Like
4760            op = "LIKE"
4761        else:
4762            exp_class = exp.ILike
4763            op = "ILIKE"
4764
4765        if expression.args.get("negate"):
4766            op = f"NOT {op}"
4767
4768        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4769            exprs = rhs.this.unnest()
4770
4771            if isinstance(exprs, exp.Tuple):
4772                exprs = exprs.expressions
4773            else:
4774                exprs = [exprs]
4775
4776            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4777
4778            def _make_like(expr: exp.Expression) -> exp.Expression:
4779                like: exp.Expression = exp_class(
4780                    this=this, expression=expr, negate=expression.args.get("negate")
4781                )
4782                if escape:
4783                    like = exp.Escape(this=like, expression=escape.expression.copy())
4784                return like
4785
4786            like_expr: exp.Expr = _make_like(exprs[0])
4787            for expr in exprs[1:]:
4788                like_expr = connective(like_expr, _make_like(expr), copy=False)
4789
4790            parent = escape.parent if escape else expression.parent
4791            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4792                parent, exp.Condition
4793            ):
4794                like_expr = exp.paren(like_expr, copy=False)
4795
4796            return self.sql(like_expr)
4797
4798        return self.binary(expression, op)
4799
4800    def like_sql(self, expression: exp.Like) -> str:
4801        return self._like_sql(expression)
4802
4803    def ilike_sql(self, expression: exp.ILike) -> str:
4804        return self._like_sql(expression)
4805
4806    def match_sql(self, expression: exp.Match) -> str:
4807        if expression.args.get("kind") or expression.args.get("analyzer"):
4808            self.unsupported("MATCH variants and USING ANALYZER are not supported in this dialect")
4809        return self.binary(expression, "MATCH")
4810
4811    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4812        return self.binary(expression, "SIMILAR TO")
4813
4814    def lt_sql(self, expression: exp.LT) -> str:
4815        return self.binary(expression, "<")
4816
4817    def lte_sql(self, expression: exp.LTE) -> str:
4818        return self.binary(expression, "<=")
4819
4820    def mod_sql(self, expression: exp.Mod) -> str:
4821        this = self.sql(expression, "this")
4822        expr = self.sql(expression, "expression")
4823        sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}"
4824
4825        parent = expression.parent
4826        if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression:
4827            return f"({sql})"
4828
4829        return sql
4830
4831    def mul_sql(self, expression: exp.Mul) -> str:
4832        return self.binary(expression, "*")
4833
4834    def neq_sql(self, expression: exp.NEQ) -> str:
4835        return self.binary(expression, "<>")
4836
4837    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4838        return self.binary(expression, "IS NOT DISTINCT FROM")
4839
4840    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4841        return self.binary(expression, "IS DISTINCT FROM")
4842
4843    def sub_sql(self, expression: exp.Sub) -> str:
4844        return self.binary(expression, "-")
4845
4846    def trycast_sql(self, expression: exp.TryCast) -> str:
4847        return self.cast_sql(expression, safe_prefix="TRY_")
4848
4849    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4850        return self.cast_sql(expression)
4851
4852    def try_sql(self, expression: exp.Try) -> str:
4853        if not self.TRY_SUPPORTED:
4854            self.unsupported("Unsupported TRY function")
4855            return self.sql(expression, "this")
4856
4857        return self.func("TRY", expression.this)
4858
4859    def log_sql(self, expression: exp.Log) -> str:
4860        this = expression.this
4861        expr = expression.expression
4862
4863        if self.dialect.LOG_BASE_FIRST is False:
4864            this, expr = expr, this
4865        elif self.dialect.LOG_BASE_FIRST is None and expr:
4866            if this.name in ("2", "10"):
4867                return self.func(f"LOG{this.name}", expr)
4868
4869            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4870
4871        return self.func("LOG", this, expr)
4872
4873    def use_sql(self, expression: exp.Use) -> str:
4874        kind = self.sql(expression, "kind")
4875        kind = f" {kind}" if kind else ""
4876        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4877        this = f" {this}" if this else ""
4878        return f"USE{kind}{this}"
4879
4880    def binary(self, expression: exp.Binary, op: str) -> str:
4881        sqls: list[str] = []
4882        stack: list[None | str | exp.Expr] = [expression]
4883        binary_type = type(expression)
4884        is_is = binary_type is exp.Is and op in ("IS", "IS NOT")
4885        negate = expression.args.get("negate")
4886
4887        while stack:
4888            node = stack.pop()
4889
4890            if type(node) is binary_type and (
4891                node is expression
4892                or (is_is and not isinstance(node.expression, exp.Boolean))
4893                or node.args.get("negate") == negate
4894            ):
4895                op_func = node.args.get("operator")
4896                if op_func:
4897                    op = f"OPERATOR({self.sql(op_func)})"
4898                elif is_is:
4899                    op = "IS NOT" if node.args.get("negate") else "IS"
4900
4901                stack.append(node.args.get("expression"))
4902                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4903                stack.append(node.args.get("this"))
4904            else:
4905                sqls.append(self.sql(node))
4906
4907        return "".join(sqls)
4908
4909    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4910        to_clause = self.sql(expression, "to")
4911        if to_clause:
4912            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4913
4914        return self.function_fallback_sql(expression)
4915
4916    def function_fallback_sql(self, expression: exp.Func) -> str:
4917        args = []
4918
4919        for key in expression.arg_types:
4920            arg_value = expression.args.get(key)
4921
4922            if isinstance(arg_value, list):
4923                for value in arg_value:
4924                    args.append(value)
4925            elif arg_value is not None:
4926                args.append(arg_value)
4927
4928        return self.func(self.function_name(expression), *args)
4929
4930    def function_name(self, expression: exp.Func) -> str:
4931        name_key = self.dialect.ORIGINAL_NAME_META_KEY
4932        return (expression.meta_get(name_key) if name_key else None) or expression.sql_name()
4933
4934    def func(
4935        self,
4936        name: str,
4937        *args: t.Any,
4938        prefix: str = "(",
4939        suffix: str = ")",
4940        normalize: bool = True,
4941    ) -> str:
4942        name = self.normalize_func(name) if normalize else name
4943        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4944
4945    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4946        arg_sqls = tuple(
4947            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4948        )
4949        if self.pretty and self.too_wide(arg_sqls):
4950            return self.indent(
4951                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4952            )
4953        return sep.join(arg_sqls)
4954
4955    def too_wide(self, args: t.Iterable) -> bool:
4956        return sum(len(arg) for arg in args) > self.max_text_width
4957
4958    def format_time(
4959        self,
4960        expression: exp.Expr,
4961        inverse_time_mapping: dict[str, str] | None = None,
4962        inverse_time_trie: dict | None = None,
4963    ) -> str | None:
4964        return format_time(
4965            self.sql(expression, "format"),
4966            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4967            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4968        )
4969
4970    def expressions(
4971        self,
4972        expression: exp.Expr | None = None,
4973        key: str | None = None,
4974        sqls: t.Collection[str | exp.Expr] | None = None,
4975        flat: bool = False,
4976        indent: bool = True,
4977        skip_first: bool = False,
4978        skip_last: bool = False,
4979        sep: str = ", ",
4980        prefix: str = "",
4981        dynamic: bool = False,
4982        new_line: bool = False,
4983    ) -> str:
4984        expressions = expression.args.get(key or "expressions") if expression else sqls
4985
4986        if not expressions:
4987            return ""
4988
4989        if flat:
4990            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4991
4992        num_sqls = len(expressions)
4993        result_sqls = []
4994
4995        for i, e in enumerate(expressions):
4996            sql = self.sql(e, comment=False)
4997            if not sql:
4998                continue
4999
5000            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
5001
5002            if self.pretty:
5003                if self.leading_comma:
5004                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
5005                else:
5006                    result_sqls.append(
5007                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
5008                    )
5009            else:
5010                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
5011
5012        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
5013            if new_line:
5014                result_sqls.insert(0, "")
5015                result_sqls.append("")
5016            result_sql = "\n".join(s.rstrip() for s in result_sqls)
5017        else:
5018            result_sql = "".join(result_sqls)
5019
5020        return (
5021            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
5022            if indent
5023            else result_sql
5024        )
5025
5026    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
5027        flat = flat or isinstance(expression.parent, exp.Properties)
5028        expressions_sql = self.expressions(expression, flat=flat)
5029        if flat:
5030            return f"{op} {expressions_sql}"
5031        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
5032
5033    def naked_property(self, expression: exp.Property) -> str:
5034        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
5035        if not property_name:
5036            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
5037        return f"{property_name} {self.sql(expression, 'this')}"
5038
5039    def tag_sql(self, expression: exp.Tag) -> str:
5040        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
5041
5042    def token_sql(self, token_type: TokenType) -> str:
5043        return self.TOKEN_MAPPING.get(token_type, token_type.name)
5044
5045    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
5046        this = self.sql(expression, "this")
5047        expressions = self.no_identify(self.expressions, expression)
5048        expressions = (
5049            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
5050        )
5051        return f"{this}{expressions}" if expressions.strip() != "" else this
5052
5053    def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str:
5054        return self.expressions(expression, flat=True)
5055
5056    def macrooverload_sql(self, expression: exp.MacroOverload) -> str:
5057        params = self.no_identify(self.expressions, expression, flat=True)
5058        body = self.sql(expression, "this")
5059        prefix = "TABLE " if expression.args.get("is_table") else ""
5060        return f"({params}) AS {prefix}{body}"
5061
5062    def joinhint_sql(self, expression: exp.JoinHint) -> str:
5063        this = self.sql(expression, "this")
5064        expressions = self.expressions(expression, flat=True)
5065        return f"{this}({expressions})"
5066
5067    def kwarg_sql(self, expression: exp.Kwarg) -> str:
5068        return self.binary(expression, "=>")
5069
5070    def when_sql(self, expression: exp.When) -> str:
5071        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
5072        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
5073        condition = self.sql(expression, "condition")
5074        condition = f" AND {condition}" if condition else ""
5075
5076        then_expression = expression.args.get("then")
5077        if isinstance(then_expression, exp.Insert):
5078            this = (
5079                "ALL BY NAME"
5080                if self.MERGE_ALL_BY_NAME and isinstance(then_expression.this, exp.Star)
5081                else self.sql(then_expression, "this")
5082            )
5083            this = f"INSERT {this}" if this else "INSERT"
5084            then = self.sql(then_expression, "expression")
5085            then = f"{this} VALUES {then}" if then else this
5086        elif isinstance(then_expression, exp.Update):
5087            if isinstance(then_expression.args.get("expressions"), exp.Star):
5088                then = (
5089                    "UPDATE ALL BY NAME"
5090                    if self.MERGE_ALL_BY_NAME
5091                    else f"UPDATE {self.sql(then_expression, 'expressions')}"
5092                )
5093            elif (
5094                self.MERGE_ALL_BY_NAME
5095                and len(then_expression.expressions) == 1
5096                and isinstance(then_expression.expressions[0], exp.Star)
5097            ):
5098                then = "UPDATE ALL BY NAME"
5099            else:
5100                expressions_sql = self.expressions(then_expression)
5101                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
5102        else:
5103            then = self.sql(then_expression)
5104
5105        if isinstance(then_expression, (exp.Insert, exp.Update)):
5106            where = self.sql(then_expression, "where")
5107            if where and not self.SUPPORTS_MERGE_WHERE:
5108                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
5109                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
5110                where = ""
5111            then = f"{then}{where}"
5112        return f"WHEN {matched}{source}{condition} THEN {then}"
5113
5114    def whens_sql(self, expression: exp.Whens) -> str:
5115        return self.expressions(expression, sep=" ", indent=False)
5116
5117    def merge_sql(self, expression: exp.Merge) -> str:
5118        table = expression.this
5119        table_alias = ""
5120
5121        hints = table.args.get("hints")
5122        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
5123            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
5124            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
5125
5126        this = self.sql(table)
5127        using = f"USING {self.sql(expression, 'using')}"
5128        whens = self.sql(expression, "whens")
5129
5130        on = self.sql(expression, "on")
5131        on = f"ON {on}" if on else ""
5132
5133        if not on:
5134            on = self.expressions(expression, key="using_cond")
5135            on = f"USING ({on})" if on else ""
5136
5137        returning = self.sql(expression, "returning")
5138        if returning:
5139            whens = f"{whens}{returning}"
5140
5141        sep = self.sep()
5142
5143        return self.prepend_ctes(
5144            expression,
5145            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
5146        )
5147
5148    @unsupported_args("format")
5149    def tochar_sql(self, expression: exp.ToChar) -> str:
5150        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
5151
5152    @unsupported_args("default")
5153    def tonumber_sql(self, expression: exp.ToNumber) -> str:
5154        if not self.SUPPORTS_TO_NUMBER:
5155            self.unsupported("Unsupported TO_NUMBER function")
5156            return self._tonumber_cast_sql(expression)
5157
5158        fmt = expression.args.get("format")
5159        if not fmt:
5160            self.unsupported("Conversion format is required for TO_NUMBER")
5161            return self._tonumber_cast_sql(expression)
5162
5163        return self.func("TO_NUMBER", expression.this, fmt)
5164
5165    def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str:
5166        if expression.args.get("safe"):
5167            return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE")))
5168        return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
5169
5170    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
5171        this = self.sql(expression, "this")
5172        kind = self.sql(expression, "kind")
5173        settings_sql = self.expressions(expression, key="settings", sep=" ")
5174        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
5175        return f"{this}({kind}{args})"
5176
5177    def dictrange_sql(self, expression: exp.DictRange) -> str:
5178        this = self.sql(expression, "this")
5179        max = self.sql(expression, "max")
5180        min = self.sql(expression, "min")
5181        return f"{this}(MIN {min} MAX {max})"
5182
5183    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
5184        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
5185
5186    def aggregatekeyproperty_sql(self, expression: exp.AggregateKeyProperty) -> str:
5187        return f"AGGREGATE KEY ({self.expressions(expression, flat=True)})"
5188
5189    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
5190        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
5191
5192    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
5193    def uniquekeyproperty_sql(
5194        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
5195    ) -> str:
5196        return f"{prefix} ({self.expressions(expression, flat=True)})"
5197
5198    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
5199    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
5200        expressions = self.expressions(expression, flat=True)
5201        expressions = f" {self.wrap(expressions)}" if expressions else ""
5202        buckets = self.sql(expression, "buckets")
5203        kind = self.sql(expression, "kind")
5204        buckets = f" BUCKETS {buckets}" if buckets else ""
5205        order = self.sql(expression, "order")
5206        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
5207
5208    def oncluster_sql(self, expression: exp.OnCluster) -> str:
5209        return ""
5210
5211    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
5212        expressions = self.expressions(expression, key="expressions", flat=True)
5213        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
5214        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
5215        buckets = self.sql(expression, "buckets")
5216        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
5217
5218    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
5219        this = self.sql(expression, "this")
5220        having = self.sql(expression, "having")
5221
5222        if having:
5223            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
5224
5225        return self.func("ANY_VALUE", this)
5226
5227    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
5228        transform = self.func("TRANSFORM", *expression.expressions)
5229        row_format_before = self.sql(expression, "row_format_before")
5230        row_format_before = f" {row_format_before}" if row_format_before else ""
5231        record_writer = self.sql(expression, "record_writer")
5232        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
5233        using = f" USING {self.sql(expression, 'command_script')}"
5234        schema = self.sql(expression, "schema")
5235        schema = f" AS {schema}" if schema else ""
5236        row_format_after = self.sql(expression, "row_format_after")
5237        row_format_after = f" {row_format_after}" if row_format_after else ""
5238        record_reader = self.sql(expression, "record_reader")
5239        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
5240        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
5241
5242    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
5243        key_block_size = self.sql(expression, "key_block_size")
5244        if key_block_size:
5245            return f"KEY_BLOCK_SIZE = {key_block_size}"
5246
5247        using = self.sql(expression, "using")
5248        if using:
5249            return f"USING {using}"
5250
5251        parser = self.sql(expression, "parser")
5252        if parser:
5253            return f"WITH PARSER {parser}"
5254
5255        comment = self.sql(expression, "comment")
5256        if comment:
5257            return f"COMMENT {comment}"
5258
5259        visible = expression.args.get("visible")
5260        if visible is not None:
5261            return "VISIBLE" if visible else "INVISIBLE"
5262
5263        engine_attr = self.sql(expression, "engine_attr")
5264        if engine_attr:
5265            return f"ENGINE_ATTRIBUTE = {engine_attr}"
5266
5267        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
5268        if secondary_engine_attr:
5269            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
5270
5271        self.unsupported("Unsupported index constraint option.")
5272        return ""
5273
5274    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
5275        enforced = " ENFORCED" if expression.args.get("enforced") else ""
5276        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
5277
5278    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
5279        kind = self.sql(expression, "kind")
5280        kind = f"{kind} INDEX" if kind else "INDEX"
5281        this = self.sql(expression, "this")
5282        this = f" {this}" if this else ""
5283        index_type = self.sql(expression, "index_type")
5284        index_type = f" USING {index_type}" if index_type else ""
5285        expressions = self.expressions(expression, flat=True)
5286        expressions = f" ({expressions})" if expressions else ""
5287        options = self.expressions(expression, key="options", sep=" ")
5288        options = f" {options}" if options else ""
5289        return f"{kind}{this}{index_type}{expressions}{options}"
5290
5291    def nvl2_sql(self, expression: exp.Nvl2) -> str:
5292        if self.NVL2_SUPPORTED:
5293            return self.function_fallback_sql(expression)
5294
5295        case = exp.Case().when(
5296            expression.this.is_(exp.null()).not_(copy=False),
5297            expression.args["true"],
5298            copy=False,
5299        )
5300        else_cond = expression.args.get("false")
5301        if else_cond:
5302            case.else_(else_cond, copy=False)
5303
5304        return self.sql(case)
5305
5306    def nthvalue_sql(self, expression: exp.NthValue) -> str:
5307        if expression.args.get("from_first") is False:
5308            self.unsupported("NTH_VALUE FROM LAST is not supported")
5309
5310        return self.function_fallback_sql(expression)
5311
5312    def comprehension_sql(self, expression: exp.Comprehension) -> str:
5313        this = self.sql(expression, "this")
5314        expr = self.sql(expression, "expression")
5315        position = self.sql(expression, "position")
5316        position = f", {position}" if position else ""
5317        iterator = self.sql(expression, "iterator")
5318        condition = self.sql(expression, "condition")
5319        condition = f" IF {condition}" if condition else ""
5320        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
5321
5322    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
5323        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
5324
5325    def opclass_sql(self, expression: exp.Opclass) -> str:
5326        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
5327
5328    def _ml_sql(self, expression: exp.Func, name: str) -> str:
5329        model = self.sql(expression, "this")
5330        model = f"MODEL {model}"
5331        expr = expression.expression
5332        if expr:
5333            expr_sql = self.sql(expression, "expression")
5334            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
5335        else:
5336            expr_sql = None
5337
5338        parameters = self.sql(expression, "params_struct") or None
5339
5340        return self.func(name, model, expr_sql, parameters)
5341
5342    def predict_sql(self, expression: exp.Predict) -> str:
5343        return self._ml_sql(expression, "PREDICT")
5344
5345    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
5346        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
5347        return self._ml_sql(expression, name)
5348
5349    def generatetext_sql(self, expression: exp.GenerateText) -> str:
5350        return self._ml_sql(expression, "GENERATE_TEXT")
5351
5352    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
5353        return self._ml_sql(expression, "GENERATE_TABLE")
5354
5355    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
5356        return self._ml_sql(expression, "GENERATE_BOOL")
5357
5358    def generateint_sql(self, expression: exp.GenerateInt) -> str:
5359        return self._ml_sql(expression, "GENERATE_INT")
5360
5361    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
5362        return self._ml_sql(expression, "GENERATE_DOUBLE")
5363
5364    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
5365        return self._ml_sql(expression, "TRANSLATE")
5366
5367    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
5368        return self._ml_sql(expression, "FORECAST")
5369
5370    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
5371        this_sql = self.sql(expression, "this")
5372        if isinstance(expression.this, exp.Table):
5373            this_sql = f"TABLE {this_sql}"
5374
5375        return self.func(
5376            "FORECAST",
5377            this_sql,
5378            expression.args.get("data_col"),
5379            expression.args.get("timestamp_col"),
5380            expression.args.get("model"),
5381            expression.args.get("id_cols"),
5382            expression.args.get("horizon"),
5383            expression.args.get("forecast_end_timestamp"),
5384            expression.args.get("confidence_level"),
5385            expression.args.get("output_historical_time_series"),
5386            expression.args.get("context_window"),
5387        )
5388
5389    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
5390        this_sql = self.sql(expression, "this")
5391        if isinstance(expression.this, exp.Table):
5392            this_sql = f"TABLE {this_sql}"
5393
5394        return self.func(
5395            "FEATURES_AT_TIME",
5396            this_sql,
5397            expression.args.get("time"),
5398            expression.args.get("num_rows"),
5399            expression.args.get("ignore_feature_nulls"),
5400        )
5401
5402    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
5403        this_sql = self.sql(expression, "this")
5404        if isinstance(expression.this, exp.Table):
5405            this_sql = f"TABLE {this_sql}"
5406
5407        query_table = self.sql(expression, "query_table")
5408        if isinstance(expression.args["query_table"], exp.Table):
5409            query_table = f"TABLE {query_table}"
5410
5411        return self.func(
5412            "VECTOR_SEARCH",
5413            this_sql,
5414            expression.args.get("column_to_search"),
5415            query_table,
5416            expression.args.get("query_column_to_search"),
5417            expression.args.get("top_k"),
5418            expression.args.get("distance_type"),
5419            expression.args.get("options"),
5420        )
5421
5422    def forin_sql(self, expression: exp.ForIn) -> str:
5423        this = self.sql(expression, "this")
5424        expression_sql = self.sql(expression, "expression")
5425        return f"FOR {this} DO {expression_sql}"
5426
5427    @unsupported_args("partition_start", "partition_end", "force", "mode")
5428    def refresh_sql(self, expression: exp.Refresh) -> str:
5429        this = self.sql(expression, "this")
5430        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
5431        return f"REFRESH {kind}{this}"
5432
5433    def toarray_sql(self, expression: exp.ToArray) -> str:
5434        arg = expression.this
5435        if not arg.type:
5436            import sqlglot.optimizer.annotate_types
5437
5438            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
5439
5440        if arg.is_type(exp.DType.ARRAY):
5441            return self.sql(arg)
5442
5443        cond_for_null = arg.is_(exp.null())
5444        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5445
5446    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
5447        this = expression.this
5448        time_format = self.format_time(expression)
5449
5450        if time_format:
5451            return self.sql(
5452                exp.cast(
5453                    exp.StrToTime(this=this, format=expression.args["format"]),
5454                    exp.DType.TIME,
5455                )
5456            )
5457
5458        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
5459            return self.sql(this)
5460
5461        return self.sql(exp.cast(this, exp.DType.TIME))
5462
5463    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
5464        this = expression.this
5465        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
5466            return self.sql(this)
5467
5468        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5469
5470    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
5471        this = expression.this
5472        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
5473            return self.sql(this)
5474
5475        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5476
5477    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
5478        this = expression.this
5479        time_format = self.format_time(expression)
5480        safe = expression.args.get("safe")
5481        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
5482            return self.sql(
5483                exp.cast(
5484                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
5485                    exp.DType.DATE,
5486                )
5487            )
5488
5489        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
5490            return self.sql(this)
5491
5492        if safe:
5493            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
5494
5495        return self.sql(exp.cast(this, exp.DType.DATE))
5496
5497    def unixdate_sql(self, expression: exp.UnixDate) -> str:
5498        return self.sql(
5499            exp.func(
5500                "DATEDIFF",
5501                expression.this,
5502                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5503                "day",
5504            )
5505        )
5506
5507    def lastday_sql(self, expression: exp.LastDay) -> str:
5508        if self.LAST_DAY_SUPPORTS_DATE_PART:
5509            return self.function_fallback_sql(expression)
5510
5511        unit = expression.args.get("unit")
5512        if unit and unit.name.upper() != "MONTH":
5513            self.unsupported("Date parts are not supported in LAST_DAY.")
5514
5515        return self.func("LAST_DAY", expression.this)
5516
5517    def dateadd_sql(self, expression: exp.DateAdd) -> str:
5518        import sqlglot.dialects.dialect
5519
5520        return self.func(
5521            "DATE_ADD",
5522            expression.this,
5523            expression.expression,
5524            sqlglot.dialects.dialect.unit_to_str(expression),
5525        )
5526
5527    def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str:
5528        this = expression.this
5529        position = expression.args["position"]
5530        offset = index_offset - (expression.args.get("offset") or 0)
5531
5532        if offset:
5533            if position.is_int:
5534                value = position.to_py()
5535                if value >= 0:
5536                    position = exp.Literal.number(value + offset)
5537                elif offset < 0 and value == -1:
5538                    # 1-based -1 appends, which a 0-based position can only express as the size
5539                    position = exp.ArraySize(this=this.copy())
5540                else:
5541                    # Negative positions count from the end, so they shift in the opposite
5542                    # direction, e.g. 0-based -1 (before the last element) is 1-based -2
5543                    position = exp.Literal.number(value - offset)
5544            else:
5545                self.unsupported("ARRAY_INSERT position can only be converted if it's a literal")
5546
5547        return self.func("ARRAY_INSERT", this, position, expression.expression)
5548
5549    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
5550        if self.CAN_IMPLEMENT_ARRAY_ANY:
5551            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
5552            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
5553            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
5554            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
5555
5556        import sqlglot.dialects.dialect
5557
5558        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
5559        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
5560            self.unsupported("ARRAY_ANY is unsupported")
5561
5562        return self.function_fallback_sql(expression)
5563
5564    def struct_sql(self, expression: exp.Struct) -> str:
5565        expression.set(
5566            "expressions",
5567            [
5568                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
5569                if isinstance(e, exp.PropertyEQ)
5570                else e
5571                for e in expression.expressions
5572            ],
5573        )
5574
5575        return self.function_fallback_sql(expression)
5576
5577    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
5578        low = self.sql(expression, "this")
5579        high = self.sql(expression, "expression")
5580
5581        return f"{low} TO {high}"
5582
5583    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
5584        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
5585        tables = f" {self.expressions(expression)}"
5586
5587        exists = " IF EXISTS" if expression.args.get("exists") else ""
5588
5589        on_cluster = self.sql(expression, "cluster")
5590        on_cluster = f" {on_cluster}" if on_cluster else ""
5591
5592        identity = self.sql(expression, "identity")
5593        identity = f" {identity} IDENTITY" if identity else ""
5594
5595        option = self.sql(expression, "option")
5596        option = f" {option}" if option else ""
5597
5598        partition = self.sql(expression, "partition")
5599        partition = f" {partition}" if partition else ""
5600
5601        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5602
5603    # This transpiles T-SQL's CONVERT function
5604    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
5605    def convert_sql(self, expression: exp.Convert) -> str:
5606        to = expression.this
5607        value = expression.expression
5608        style = expression.args.get("style")
5609        safe = expression.args.get("safe")
5610        strict = expression.args.get("strict")
5611
5612        if not to or not value:
5613            return ""
5614
5615        # Retrieve length of datatype and override to default if not specified
5616        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5617            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
5618
5619        transformed: exp.Expr | None = None
5620        cast = exp.Cast if strict else exp.TryCast
5621
5622        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
5623        if isinstance(style, exp.Literal) and style.is_int:
5624            import sqlglot.dialects.tsql
5625
5626            style_value = style.name
5627            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
5628            if not converted_style:
5629                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
5630
5631            fmt = exp.Literal.string(converted_style)
5632
5633            if to.this == exp.DType.DATE:
5634                transformed = exp.StrToDate(this=value, format=fmt)
5635            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
5636                transformed = exp.StrToTime(this=value, format=fmt)
5637            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
5638                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
5639            elif to.this == exp.DType.TEXT:
5640                transformed = exp.TimeToStr(this=value, format=fmt)
5641
5642        if not transformed:
5643            transformed = cast(this=value, to=to, safe=safe)
5644
5645        return self.sql(transformed)
5646
5647    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
5648        this = expression.this
5649        if isinstance(this, exp.JSONPathWildcard):
5650            this = self.json_path_part(this)
5651            return f".{this}" if this else ""
5652
5653        quoted = expression.args.get("quoted")
5654        if not (
5655            quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
5656        ) and self.SAFE_JSON_PATH_KEY_RE.match(this):
5657            return f".{this}"
5658
5659        this = self.json_path_part(this)
5660
5661        return (
5662            f"[{this}]"
5663            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5664            else f".{this}"
5665        )
5666
5667    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5668        this = self.json_path_part(expression.this)
5669        return f"[{this}]" if this else ""
5670
5671    def _simplify_unless_literal(self, expression: E) -> E:
5672        if not isinstance(expression, exp.Literal):
5673            import sqlglot.optimizer.simplify
5674
5675            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5676
5677        return expression
5678
5679    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5680        this = expression.this
5681        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5682            self.unsupported(
5683                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5684            )
5685            return self.sql(this)
5686
5687        if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"):
5688            if self.IGNORE_NULLS_BEFORE_ORDER:
5689                from sqlglot.optimizer.scope import find_all_in_scope
5690
5691                # The first modifier here will be the one closest to the AggFunc's arg
5692                mods = sorted(
5693                    find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit),
5694                    key=lambda x: (
5695                        0
5696                        if isinstance(x, exp.HavingMax)
5697                        else (1 if isinstance(x, exp.Order) else 2)
5698                    ),
5699                )
5700
5701                if mods:
5702                    mod = mods[0]
5703                    this = expression.__class__(this=mod.this.copy())
5704                    this.meta["inline"] = True
5705                    mod.this.replace(this)
5706                    return self.sql(expression.this)
5707
5708            agg_func = expression.find(exp.AggFunc)
5709
5710            if agg_func:
5711                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5712                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5713
5714        return f"{self.sql(expression, 'this')} {text}"
5715
5716    def _replace_line_breaks(self, string: str) -> str:
5717        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5718        if self.pretty:
5719            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5720        return string
5721
5722    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5723        option = self.sql(expression, "this")
5724
5725        if expression.expressions:
5726            upper = option.upper()
5727
5728            # Snowflake FILE_FORMAT options are separated by whitespace
5729            sep = " " if upper == "FILE_FORMAT" else ", "
5730
5731            # Databricks copy/format options do not set their list of values with EQ
5732            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5733            values = self.expressions(expression, flat=True, sep=sep)
5734            return f"{option}{op}({values})"
5735
5736        value = self.sql(expression, "expression")
5737
5738        if not value:
5739            return option
5740
5741        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5742
5743        return f"{option}{op}{value}"
5744
5745    def credentials_sql(self, expression: exp.Credentials) -> str:
5746        cred_expr = expression.args.get("credentials")
5747        if isinstance(cred_expr, exp.Literal):
5748            # Redshift case: CREDENTIALS <string>
5749            credentials = self.sql(expression, "credentials")
5750            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5751        else:
5752            # Snowflake case: CREDENTIALS = (...)
5753            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5754            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5755
5756        storage = self.sql(expression, "storage")
5757        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5758
5759        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5760        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5761
5762        iam_role = self.sql(expression, "iam_role")
5763        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5764
5765        region = self.sql(expression, "region")
5766        region = f" REGION {region}" if region else ""
5767
5768        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5769
5770    def copy_sql(self, expression: exp.Copy) -> str:
5771        this = self.sql(expression, "this")
5772        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5773
5774        credentials = self.sql(expression, "credentials")
5775        credentials = self.seg(credentials) if credentials else ""
5776        files = self.expressions(expression, key="files", flat=True)
5777        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5778
5779        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5780        params = self.expressions(
5781            expression,
5782            key="params",
5783            sep=sep,
5784            new_line=True,
5785            skip_last=True,
5786            skip_first=True,
5787            indent=self.COPY_PARAMS_ARE_WRAPPED,
5788        )
5789
5790        if params:
5791            if self.COPY_PARAMS_ARE_WRAPPED:
5792                params = f" WITH ({params})"
5793            elif not self.pretty and (files or credentials):
5794                params = f" {params}"
5795
5796        return f"COPY{this}{kind} {files}{credentials}{params}"
5797
5798    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5799        return ""
5800
5801    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5802        on_sql = "ON" if expression.args.get("on") else "OFF"
5803        filter_col: str | None = self.sql(expression, "filter_column")
5804        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5805        retention_period: str | None = self.sql(expression, "retention_period")
5806        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5807
5808        if filter_col or retention_period:
5809            on_sql = self.func("ON", filter_col, retention_period)
5810
5811        return f"DATA_DELETION={on_sql}"
5812
5813    def maskingpolicycolumnconstraint_sql(
5814        self, expression: exp.MaskingPolicyColumnConstraint
5815    ) -> str:
5816        this = self.sql(expression, "this")
5817        expressions = self.expressions(expression, flat=True)
5818        expressions = f" USING ({expressions})" if expressions else ""
5819        return f"MASKING POLICY {this}{expressions}"
5820
5821    def gapfill_sql(self, expression: exp.GapFill) -> str:
5822        this = self.sql(expression, "this")
5823        this = f"TABLE {this}"
5824        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5825
5826    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5827        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5828
5829    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5830        this = self.sql(expression, "this")
5831        expr = expression.expression
5832
5833        if isinstance(expr, exp.Func):
5834            # T-SQL's CLR functions are case sensitive
5835            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5836        else:
5837            expr = self.sql(expression, "expression")
5838
5839        return self.scope_resolution(expr, this)
5840
5841    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5842        if self.PARSE_JSON_NAME is None:
5843            return self.sql(expression.this)
5844
5845        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5846
5847    def rand_sql(self, expression: exp.Rand) -> str:
5848        lower = self.sql(expression, "lower")
5849        upper = self.sql(expression, "upper")
5850
5851        if lower and upper:
5852            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5853        return self.func("RAND", expression.this)
5854
5855    def changes_sql(self, expression: exp.Changes) -> str:
5856        information = self.sql(expression, "information")
5857        information = f"INFORMATION => {information}"
5858        at_before = self.sql(expression, "at_before")
5859        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5860        end = self.sql(expression, "end")
5861        end = f"{self.seg('')}{end}" if end else ""
5862
5863        return f"CHANGES ({information}){at_before}{end}"
5864
5865    def pad_sql(self, expression: exp.Pad) -> str:
5866        prefix = "L" if expression.args.get("is_left") else "R"
5867
5868        fill_pattern = self.sql(expression, "fill_pattern") or None
5869        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5870            fill_pattern = "' '"
5871
5872        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5873
5874    def summarize_sql(self, expression: exp.Summarize) -> str:
5875        table = " TABLE" if expression.args.get("table") else ""
5876        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5877
5878    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5879        generate_series = exp.GenerateSeries(**expression.args)
5880
5881        parent = expression.parent
5882        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5883            parent = parent.parent
5884
5885        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5886            return self.sql(exp.Unnest(expressions=[generate_series]))
5887
5888        if isinstance(parent, exp.Select):
5889            self.unsupported("GenerateSeries projection unnesting is not supported.")
5890
5891        return self.sql(generate_series)
5892
5893    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5894        if self.SUPPORTS_CONVERT_TIMEZONE:
5895            return self.function_fallback_sql(expression)
5896
5897        source_tz = expression.args.get("source_tz")
5898        target_tz = expression.args.get("target_tz")
5899        timestamp = expression.args.get("timestamp")
5900
5901        if source_tz and timestamp:
5902            timestamp = exp.AtTimeZone(
5903                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5904            )
5905
5906        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5907
5908        return self.sql(expr)
5909
5910    def json_sql(self, expression: exp.JSON) -> str:
5911        this = self.sql(expression, "this")
5912        this = f" {this}" if this else ""
5913
5914        _with = expression.args.get("with_")
5915
5916        if _with is None:
5917            with_sql = ""
5918        elif not _with:
5919            with_sql = " WITHOUT"
5920        else:
5921            with_sql = " WITH"
5922
5923        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5924
5925        return f"JSON{this}{with_sql}{unique_sql}"
5926
5927    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5928        path = self.sql(expression, "path")
5929        returning = self.sql(expression, "returning")
5930        returning = f" RETURNING {returning}" if returning else ""
5931
5932        on_condition = self.sql(expression, "on_condition")
5933        on_condition = f" {on_condition}" if on_condition else ""
5934
5935        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5936
5937    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5938        regexp = " REGEXP" if expression.args.get("regexp") else ""
5939        return f"SKIP{regexp} {self.sql(expression.expression)}"
5940
5941    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5942        else_ = "ELSE " if expression.args.get("else_") else ""
5943        condition = self.sql(expression, "expression")
5944        condition = f"WHEN {condition} THEN " if condition else else_
5945        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5946        return f"{condition}{insert}"
5947
5948    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5949        kind = self.sql(expression, "kind")
5950        source = self.sql(expression, "source")
5951
5952        if kind:
5953            expressions = self.seg(self.expressions(expression, sep=" "))
5954            sql = f"INSERT {kind}{expressions}{self.seg(source)}"
5955        else:
5956            # If kind isn't present, we have Hive's FROM-first form:
5957            # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...]
5958            inserts = self.sep().join(self.sql(e) for e in expression.expressions)
5959            sql = f"FROM {source}{self.seg(inserts)}"
5960
5961        return self.prepend_ctes(expression, sql)
5962
5963    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5964        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5965        empty = expression.args.get("empty")
5966        empty = (
5967            f"DEFAULT {empty} ON EMPTY"
5968            if isinstance(empty, exp.Expr)
5969            else self.sql(expression, "empty")
5970        )
5971
5972        error = expression.args.get("error")
5973        error = (
5974            f"DEFAULT {error} ON ERROR"
5975            if isinstance(error, exp.Expr)
5976            else self.sql(expression, "error")
5977        )
5978
5979        if error and empty:
5980            error = (
5981                f"{empty} {error}"
5982                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5983                else f"{error} {empty}"
5984            )
5985            empty = ""
5986
5987        null = self.sql(expression, "null")
5988
5989        return f"{empty}{error}{null}"
5990
5991    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5992        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5993        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5994
5995    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5996        this = self.sql(expression, "this")
5997        path = self.sql(expression, "path")
5998
5999        passing = self.expressions(expression, "passing")
6000        passing = f" PASSING {passing}" if passing else ""
6001
6002        on_condition = self.sql(expression, "on_condition")
6003        on_condition = f" {on_condition}" if on_condition else ""
6004
6005        path = f"{path}{passing}{on_condition}"
6006
6007        return self.func("JSON_EXISTS", this, path)
6008
6009    def _add_arrayagg_null_filter(
6010        self,
6011        array_agg_sql: str,
6012        array_agg_expr: exp.ArrayAgg,
6013        column_expr: exp.Expr,
6014    ) -> str:
6015        """
6016        Add NULL filter to ARRAY_AGG if dialect requires it.
6017
6018        Args:
6019            array_agg_sql: The generated ARRAY_AGG SQL string
6020            array_agg_expr: The ArrayAgg expression node
6021            column_expr: The column/expression to filter (before ORDER BY wrapping)
6022
6023        Returns:
6024            SQL string with FILTER clause added if needed
6025        """
6026        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
6027        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
6028        if not (
6029            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
6030        ):
6031            return array_agg_sql
6032
6033        parent = array_agg_expr.parent
6034        if isinstance(parent, exp.Filter):
6035            parent_cond = parent.expression.this
6036            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
6037        elif column_expr.find(exp.Column):
6038            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
6039            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
6040            this_sql = (
6041                self.expressions(column_expr)
6042                if isinstance(column_expr, exp.Distinct)
6043                else self.sql(column_expr)
6044            )
6045            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
6046
6047        return array_agg_sql
6048
6049    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
6050        array_agg = self.function_fallback_sql(expression)
6051        column_expr = expression.this
6052        if isinstance(column_expr, exp.Order):
6053            column_expr = column_expr.this
6054
6055        return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
6056
6057    def slice_sql(self, expression: exp.Slice) -> str:
6058        step = self.sql(expression, "step")
6059        # An omitted end is written as `-` before a step, since `::` is the cast operator
6060        end = self.sql(expression.expression) or ("-" if step else "")
6061        begin = self.sql(expression.this)
6062
6063        sql = f"{end}:{step}" if step else end
6064        return f"{begin}:{sql}" if sql else f"{begin}:"
6065
6066    def apply_sql(self, expression: exp.Apply) -> str:
6067        this = self.sql(expression, "this")
6068        expr = self.sql(expression, "expression")
6069
6070        return f"{this} APPLY({expr})"
6071
6072    def _grant_or_revoke_sql(
6073        self,
6074        expression: exp.Grant | exp.Revoke,
6075        keyword: str,
6076        preposition: str,
6077        grant_option_prefix: str = "",
6078        grant_option_suffix: str = "",
6079    ) -> str:
6080        privileges_sql = self.expressions(expression, key="privileges", flat=True)
6081
6082        kind = self.sql(expression, "kind")
6083        kind = f" {kind}" if kind else ""
6084
6085        securable = self.sql(expression, "securable")
6086        securable = f" {securable}" if securable else ""
6087
6088        principals = self.expressions(expression, key="principals", flat=True)
6089
6090        if not expression.args.get("grant_option"):
6091            grant_option_prefix = grant_option_suffix = ""
6092
6093        # cascade for revoke only
6094        cascade = self.sql(expression, "cascade")
6095        cascade = f" {cascade}" if cascade else ""
6096
6097        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
6098
6099    def grant_sql(self, expression: exp.Grant) -> str:
6100        return self._grant_or_revoke_sql(
6101            expression,
6102            keyword="GRANT",
6103            preposition="TO",
6104            grant_option_suffix=" WITH GRANT OPTION",
6105        )
6106
6107    def revoke_sql(self, expression: exp.Revoke) -> str:
6108        return self._grant_or_revoke_sql(
6109            expression,
6110            keyword="REVOKE",
6111            preposition="FROM",
6112            grant_option_prefix="GRANT OPTION FOR ",
6113        )
6114
6115    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
6116        this = self.sql(expression, "this")
6117        columns = self.expressions(expression, flat=True)
6118        columns = f"({columns})" if columns else ""
6119
6120        return f"{this}{columns}"
6121
6122    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
6123        this = self.sql(expression, "this")
6124
6125        kind = self.sql(expression, "kind")
6126        kind = f"{kind} " if kind else ""
6127
6128        return f"{kind}{this}"
6129
6130    def columns_sql(self, expression: exp.Columns) -> str:
6131        func = self.function_fallback_sql(expression)
6132        if expression.args.get("unpack"):
6133            func = f"*{func}"
6134
6135        return func
6136
6137    def overlay_sql(self, expression: exp.Overlay) -> str:
6138        this = self.sql(expression, "this")
6139        expr = self.sql(expression, "expression")
6140        from_sql = self.sql(expression, "from_")
6141        for_sql = self.sql(expression, "for_")
6142        for_sql = f" FOR {for_sql}" if for_sql else ""
6143
6144        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
6145
6146    @unsupported_args("format")
6147    def todouble_sql(self, expression: exp.ToDouble) -> str:
6148        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
6149        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
6150
6151    def string_sql(self, expression: exp.String) -> str:
6152        this = expression.this
6153        zone = expression.args.get("zone")
6154
6155        if zone:
6156            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
6157            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
6158            # set for source_tz to transpile the time conversion before the STRING cast
6159            this = exp.ConvertTimezone(
6160                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
6161            )
6162
6163        return self.sql(exp.cast(this, exp.DType.VARCHAR))
6164
6165    def median_sql(self, expression: exp.Median) -> str:
6166        if not self.SUPPORTS_MEDIAN:
6167            return self.sql(
6168                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
6169            )
6170
6171        return self.function_fallback_sql(expression)
6172
6173    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
6174        filler = self.sql(expression, "this")
6175        filler = f" {filler}" if filler else ""
6176        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
6177        return f"TRUNCATE{filler} {with_count}"
6178
6179    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
6180        if self.SUPPORTS_UNIX_SECONDS:
6181            return self.function_fallback_sql(expression)
6182
6183        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
6184
6185        return self.sql(
6186            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
6187        )
6188
6189    def arraysort_sql(self, expression: exp.ArraySort) -> str:
6190        asc = expression.args.get("asc")
6191        nulls_first = expression.args.get("nulls_first")
6192        if (asc and asc != exp.true()) or (nulls_first and nulls_first != exp.false()):
6193            self.unsupported("ARRAY_SORT only sorts ascending with NULLs last")
6194        return self.func("ARRAY_SORT", expression.this, expression.expression)
6195
6196    def arraysize_sql(self, expression: exp.ArraySize) -> str:
6197        dim = expression.expression
6198
6199        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
6200        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
6201            if not (dim.is_int and dim.name == "1"):
6202                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
6203            dim = None
6204
6205        # If dimension is required but not specified, default initialize it
6206        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
6207            dim = exp.Literal.number(1)
6208
6209        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
6210
6211    def attach_sql(self, expression: exp.Attach) -> str:
6212        this = self.sql(expression, "this")
6213        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
6214        expressions = self.expressions(expression)
6215        expressions = f" ({expressions})" if expressions else ""
6216
6217        return f"ATTACH{exists_sql} {this}{expressions}"
6218
6219    def detach_sql(self, expression: exp.Detach) -> str:
6220        kind = self.sql(expression, "kind")
6221        kind = f" {kind}" if kind else ""
6222        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
6223        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
6224        exists = " IF EXISTS" if expression.args.get("exists") else ""
6225        if exists:
6226            kind = kind or " DATABASE"
6227
6228        this = self.sql(expression, "this")
6229        this = f" {this}" if this else ""
6230        cluster = self.sql(expression, "cluster")
6231        cluster = f" {cluster}" if cluster else ""
6232        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
6233        sync = " SYNC" if expression.args.get("sync") else ""
6234        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
6235
6236    def attachoption_sql(self, expression: exp.AttachOption) -> str:
6237        this = self.sql(expression, "this")
6238        value = self.sql(expression, "expression")
6239        value = f" {value}" if value else ""
6240        return f"{this}{value}"
6241
6242    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
6243        return (
6244            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
6245        )
6246
6247    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
6248        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
6249        encode = f"{encode} {self.sql(expression, 'this')}"
6250
6251        properties = expression.args.get("properties")
6252        if properties:
6253            encode = f"{encode} {self.properties(properties)}"
6254
6255        return encode
6256
6257    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
6258        this = self.sql(expression, "this")
6259        include = f"INCLUDE {this}"
6260
6261        column_def = self.sql(expression, "column_def")
6262        if column_def:
6263            include = f"{include} {column_def}"
6264
6265        alias = self.sql(expression, "alias")
6266        if alias:
6267            include = f"{include} AS {alias}"
6268
6269        return include
6270
6271    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
6272        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
6273        name = f"{prefix} {self.sql(expression, 'this')}"
6274        return self.func("XMLELEMENT", name, *expression.expressions)
6275
6276    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
6277        this = self.sql(expression, "this")
6278        expr = self.sql(expression, "expression")
6279        expr = f"({expr})" if expr else ""
6280        return f"{this}{expr}"
6281
6282    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
6283        partitions = self.expressions(expression, "partition_expressions")
6284        create = self.expressions(expression, "create_expressions")
6285        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
6286
6287    def partitionbyrangepropertydynamic_sql(
6288        self, expression: exp.PartitionByRangePropertyDynamic
6289    ) -> str:
6290        start = self.sql(expression, "start")
6291        end = self.sql(expression, "end")
6292
6293        every = expression.args["every"]
6294        if isinstance(every, exp.Interval) and every.this.is_string:
6295            every.this.replace(exp.Literal.number(every.name))
6296
6297        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6298
6299    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
6300        name = self.sql(expression, "this")
6301        values = self.expressions(expression, flat=True)
6302
6303        return f"NAME {name} VALUE {values}"
6304
6305    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
6306        kind = self.sql(expression, "kind")
6307        sample = self.sql(expression, "sample")
6308        return f"SAMPLE {sample} {kind}"
6309
6310    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
6311        kind = self.sql(expression, "kind")
6312        option = self.sql(expression, "option")
6313        option = f" {option}" if option else ""
6314        this = self.sql(expression, "this")
6315        this = f" {this}" if this else ""
6316        columns = self.expressions(expression)
6317        columns = f" {columns}" if columns else ""
6318        return f"{kind}{option} STATISTICS{this}{columns}"
6319
6320    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
6321        this = self.sql(expression, "this")
6322        columns = self.expressions(expression)
6323        inner_expression = self.sql(expression, "expression")
6324        inner_expression = f" {inner_expression}" if inner_expression else ""
6325        update_options = self.sql(expression, "update_options")
6326        update_options = f" {update_options} UPDATE" if update_options else ""
6327        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
6328
6329    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
6330        kind = self.sql(expression, "kind")
6331        kind = f" {kind}" if kind else ""
6332        return f"DELETE{kind} STATISTICS"
6333
6334    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
6335        inner_expression = self.sql(expression, "expression")
6336        return f"LIST CHAINED ROWS{inner_expression}"
6337
6338    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
6339        kind = self.sql(expression, "kind")
6340        this = self.sql(expression, "this")
6341        this = f" {this}" if this else ""
6342        inner_expression = self.sql(expression, "expression")
6343        return f"VALIDATE {kind}{this}{inner_expression}"
6344
6345    def analyze_sql(self, expression: exp.Analyze) -> str:
6346        options = self.expressions(expression, key="options", sep=" ")
6347        options = f" {options}" if options else ""
6348        kind = self.sql(expression, "kind")
6349        kind = f" {kind}" if kind else ""
6350        tables = self.expressions(expression, key="tables", flat=True)
6351        tables = f" {tables}" if tables else ""
6352        mode = self.sql(expression, "mode")
6353        mode = f" {mode}" if mode else ""
6354        properties = self.sql(expression, "properties")
6355        properties = f" {properties}" if properties else ""
6356        partition = self.sql(expression, "partition")
6357        partition = f" {partition}" if partition else ""
6358        inner_expression = self.sql(expression, "expression")
6359        inner_expression = f" {inner_expression}" if inner_expression else ""
6360        return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6361
6362    def xmltable_sql(self, expression: exp.XMLTable) -> str:
6363        this = self.sql(expression, "this")
6364        namespaces = self.expressions(expression, key="namespaces")
6365        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
6366        passing = self.expressions(expression, key="passing")
6367        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
6368        columns = self.expressions(expression, key="columns")
6369        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
6370        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
6371        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6372
6373    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
6374        this = self.sql(expression, "this")
6375        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
6376
6377    def export_sql(self, expression: exp.Export) -> str:
6378        this = self.sql(expression, "this")
6379        connection = self.sql(expression, "connection")
6380        connection = f"WITH CONNECTION {connection} " if connection else ""
6381        options = self.sql(expression, "options")
6382        return f"EXPORT DATA {connection}{options} AS {this}"
6383
6384    def declare_sql(self, expression: exp.Declare) -> str:
6385        replace = "OR REPLACE " if expression.args.get("replace") else ""
6386        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
6387
6388    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
6389        variables = self.expressions(expression, "this")
6390        default = self.sql(expression, "default")
6391        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
6392
6393        kind = self.sql(expression, "kind")
6394        if isinstance(expression.args.get("kind"), exp.Schema):
6395            kind = f"TABLE {kind}"
6396
6397        kind = f" {kind}" if kind else ""
6398
6399        return f"{variables}{kind}{default}"
6400
6401    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
6402        kind = self.sql(expression, "kind")
6403        this = self.sql(expression, "this")
6404        set = self.sql(expression, "expression")
6405        to = self.sql(expression, "to")
6406        to = f" TO {to}" if to else ""
6407        default = self.sql(expression, "default")
6408        default = f" DEFAULT {default}" if default else ""
6409        using = self.sql(expression, "using")
6410        using = f" USING {using}" if using else ""
6411
6412        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
6413
6414        return f"{kind_sql} {this} SET {set}{to}{default}{using}"
6415
6416    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
6417        params = self.expressions(expression, key="params", flat=True)
6418        return self.func(expression.name, *expression.expressions) + f"({params})"
6419
6420    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
6421        return self.func(expression.name, *expression.expressions)
6422
6423    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
6424        return self.anonymousaggfunc_sql(expression)
6425
6426    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
6427        return self.parameterizedagg_sql(expression)
6428
6429    def show_sql(self, expression: exp.Show) -> str:
6430        self.unsupported("Unsupported SHOW statement")
6431        return ""
6432
6433    def install_sql(self, expression: exp.Install) -> str:
6434        self.unsupported("Unsupported INSTALL statement")
6435        return ""
6436
6437    def liststage_sql(self, expression: exp.ListStage) -> str:
6438        pattern = self.sql(expression, "pattern")
6439        pattern = f" PATTERN = {pattern}" if pattern else ""
6440        return f"LIST {self.sql(expression, 'this')}{pattern}"
6441
6442    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
6443        # Snowflake GET/PUT statements:
6444        #   PUT <file> <internalStage> <properties>
6445        #   GET <internalStage> <file> <properties>
6446        props = expression.args.get("properties")
6447        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
6448        this = self.sql(expression, "this")
6449        target = self.sql(expression, "target")
6450
6451        if isinstance(expression, exp.Put):
6452            return f"PUT {this} {target}{props_sql}"
6453        else:
6454            return f"GET {target} {this}{props_sql}"
6455
6456    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
6457        this = self.sql(expression, "this")
6458        expr = self.sql(expression, "expression")
6459        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
6460        return f"TRANSLATE({this} USING {expr}{with_error})"
6461
6462    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
6463        if self.SUPPORTS_DECODE_CASE:
6464            return self.func("DECODE", *expression.expressions)
6465
6466        decode_expr, *expressions = expression.expressions
6467
6468        ifs = []
6469        for search, result in zip(expressions[::2], expressions[1::2]):
6470            if isinstance(search, exp.Literal):
6471                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
6472            elif isinstance(search, exp.Null):
6473                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
6474            else:
6475                if isinstance(search, exp.Binary):
6476                    search = exp.paren(search)
6477
6478                cond = exp.or_(
6479                    decode_expr.eq(search),
6480                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
6481                    copy=False,
6482                )
6483                ifs.append(exp.If(this=cond, true=result))
6484
6485        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
6486        return self.sql(case)
6487
6488    def semanticview_sql(self, expression: exp.SemanticView) -> str:
6489        this = self.sql(expression, "this")
6490        this = self.seg(this, sep="")
6491        dimensions = self.expressions(
6492            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
6493        )
6494        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
6495        metrics = self.expressions(
6496            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
6497        )
6498        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
6499        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
6500        facts = self.seg(f"FACTS {facts}") if facts else ""
6501        where = self.sql(expression, "where")
6502        where = self.seg(f"WHERE {where}") if where else ""
6503        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
6504        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6505
6506    def getextract_sql(self, expression: exp.GetExtract) -> str:
6507        this = expression.this
6508        expr = expression.expression
6509
6510        if not this.type or not expression.type:
6511            import sqlglot.optimizer.annotate_types
6512
6513            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
6514
6515        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
6516            return self.sql(exp.Bracket(this=this, expressions=[expr]))
6517
6518        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
6519
6520    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
6521        return self.sql(
6522            exp.DateAdd(
6523                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
6524                expression=expression.this,
6525                unit=exp.var("DAY"),
6526            )
6527        )
6528
6529    def space_sql(self: Generator, expression: exp.Space) -> str:
6530        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
6531
6532    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
6533        return f"BUILD {self.sql(expression, 'this')}"
6534
6535    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
6536        method = self.sql(expression, "method")
6537        kind = expression.args.get("kind")
6538        if not kind:
6539            return f"REFRESH {method}"
6540
6541        every = self.sql(expression, "every")
6542        unit = self.sql(expression, "unit")
6543        every = f" EVERY {every} {unit}" if every else ""
6544        starts = self.sql(expression, "starts")
6545        starts = f" STARTS {starts}" if starts else ""
6546
6547        return f"REFRESH {method} ON {kind}{every}{starts}"
6548
6549    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
6550        self.unsupported("The model!attribute syntax is not supported")
6551        return ""
6552
6553    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
6554        return self.func("DIRECTORY", expression.this)
6555
6556    def uuid_sql(self, expression: exp.Uuid) -> str:
6557        is_string = expression.args.get("is_string", False)
6558        uuid_func_sql = self.func("UUID")
6559
6560        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
6561            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
6562
6563        return uuid_func_sql
6564
6565    def initcap_sql(self, expression: exp.Initcap) -> str:
6566        delimiters = expression.expression
6567
6568        if delimiters:
6569            # do not generate delimiters arg if we are round-tripping from default delimiters
6570            if (
6571                delimiters.is_string
6572                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
6573            ):
6574                delimiters = None
6575            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
6576                self.unsupported("INITCAP does not support custom delimiters")
6577                delimiters = None
6578
6579        return self.func("INITCAP", expression.this, delimiters)
6580
6581    def localtime_sql(self, expression: exp.Localtime) -> str:
6582        this = expression.this
6583        return self.func("LOCALTIME", this) if this else "LOCALTIME"
6584
6585    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
6586        this = expression.this
6587        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
6588
6589    def weekstart_name(self, expression: exp.WeekStart) -> str:
6590        import sqlglot.dialects.dialect
6591
6592        # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit
6593        this = expression.this.name.upper()
6594
6595        dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this)
6596        dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET)
6597
6598        if dow_from_week_start_day != dow_from_week_offset:
6599            self.unsupported(
6600                f"WEEK({this}) is not supported; falling back to the default week start day"
6601            )
6602
6603        return "WEEK"
6604
6605    def weekstart_sql(self, expression: exp.WeekStart) -> str:
6606        name = self.weekstart_name(expression)
6607
6608        # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords
6609        if isinstance(expression.parent, exp.DateTrunc):
6610            return self.sql(exp.Literal.string(name))
6611
6612        return name
6613
6614    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
6615        this = self.expressions(expression)
6616        charset = self.sql(expression, "charset")
6617        using = f" USING {charset}" if charset else ""
6618        return self.func(name, this + using)
6619
6620    def weightstring_sql(self, expression: exp.WeightString) -> str:
6621        to = self.sql(expression, "to")
6622        to = f" AS {to}" if to else ""
6623        return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}")
6624
6625    def block_sql(self, expression: exp.Block) -> str:
6626        expressions = self.expressions(expression, sep="; ", flat=True)
6627        begin = "BEGIN " if expression.args.get("begin") else ""
6628        return f"{begin}{expressions}" if expressions else ""
6629
6630    def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str:
6631        self.unsupported("Unsupported Inline UDFs syntax")
6632        return ""
6633
6634    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
6635        self.unsupported("Unsupported Stored Procedure syntax")
6636        return ""
6637
6638    def ifblock_sql(self, expression: exp.IfBlock) -> str:
6639        self.unsupported("Unsupported If block syntax")
6640        return ""
6641
6642    def casestatement_sql(self, expression: exp.CaseStatement) -> str:
6643        self.unsupported("Unsupported Case statement syntax")
6644        return ""
6645
6646    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
6647        self.unsupported("Unsupported While block syntax")
6648        return ""
6649
6650    def loopblock_sql(self, expression: exp.LoopBlock) -> str:
6651        self.unsupported("Unsupported Loop block syntax")
6652        return ""
6653
6654    def repeatblock_sql(self, expression: exp.RepeatBlock) -> str:
6655        self.unsupported("Unsupported Repeat block syntax")
6656        return ""
6657
6658    def leave_sql(self, expression: exp.Leave) -> str:
6659        self.unsupported("Unsupported Leave syntax")
6660        return ""
6661
6662    def iterate_sql(self, expression: exp.Iterate) -> str:
6663        self.unsupported("Unsupported Iterate syntax")
6664        return ""
6665
6666    def execute_sql(self, expression: exp.Execute) -> str:
6667        self.unsupported("Unsupported Execute syntax")
6668        return ""
6669
6670    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
6671        self.unsupported("Unsupported Execute syntax")
6672        return ""
6673
6674    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
6675        props = self.expressions(expression, sep=" ")
6676        return f"MODIFY {props}"
6677
6678    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
6679        kind = expression.args.get("kind")
6680        return f"USING {kind} {self.sql(expression, 'this')}"
6681
6682    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
6683        this = self.sql(expression, "this")
6684        to = self.sql(expression, "to")
6685        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)
934    def __init__(
935        self,
936        pretty: bool | int | None = None,
937        identify: str | bool = False,
938        normalize: bool = False,
939        pad: int = 2,
940        indent: int = 2,
941        normalize_functions: str | bool | None = None,
942        unsupported_level: ErrorLevel = ErrorLevel.WARN,
943        max_unsupported: int = 3,
944        leading_comma: bool = False,
945        max_text_width: int = 80,
946        comments: bool = True,
947        dialect: DialectType = None,
948    ):
949        import sqlglot
950        import sqlglot.dialects.dialect
951
952        self.pretty = pretty if pretty is not None else sqlglot.pretty
953        self.identify = identify
954        self.normalize = normalize
955        self.pad = pad
956        self._indent = indent
957        self.unsupported_level = unsupported_level
958        self.max_unsupported = max_unsupported
959        self.leading_comma = leading_comma
960        self.max_text_width = max_text_width
961        self.comments = comments
962        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
963
964        # This is both a Dialect property and a Generator argument, so we prioritize the latter
965        self.normalize_functions = (
966            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
967        )
968
969        self.unsupported_messages: list[str] = []
970        self._escaped_quote_end: str = (
971            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
972        )
973        self._escaped_byte_quote_end: str = (
974            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
975            if self.dialect.BYTE_END
976            else ""
977        )
978        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
979        self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES
980
981        self._next_name = name_sequence("_t")
982
983        self._identifier_start = self.dialect.IDENTIFIER_START
984        self._identifier_end = self.dialect.IDENTIFIER_END
985
986        self._quote_json_path_key_using_brackets = True
987
988        cls = type(self)
989        dispatch = _DISPATCH_CACHE.get(cls)
990        if dispatch is None:
991            dispatch = _build_dispatch(cls)
992            _DISPATCH_CACHE[cls] = dispatch
993        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.CopyTagsProperty'>: <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
MERGE_ALL_BY_NAME = 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
SUPPORTS_NEAREST_JOIN = 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
ALTER_TABLE_ADD_COLUMNS_WRAPPED = 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.properties.CopyTagsProperty'>: <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.NCHAR: 'NCHAR'>, <DType.CHAR: 'CHAR'>, <DType.NVARCHAR: 'NVARCHAR'>, <DType.VARCHAR: 'VARCHAR'>}
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:
 995    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 996        """
 997        Generates the SQL string corresponding to the given syntax tree.
 998
 999        Args:
1000            expression: The syntax tree.
1001            copy: Whether to copy the expression. The generator performs mutations so
1002                it is safer to copy.
1003
1004        Returns:
1005            The SQL string corresponding to `expression`.
1006        """
1007        if copy:
1008            expression = expression.copy()
1009
1010        expression = self.preprocess(expression)
1011
1012        self.unsupported_messages = []
1013        sql = self.sql(expression).strip()
1014
1015        if self.pretty:
1016            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
1017
1018        if self.unsupported_level == ErrorLevel.IGNORE:
1019            return sql
1020
1021        if self.unsupported_level == ErrorLevel.WARN:
1022            for msg in self.unsupported_messages:
1023                logger.warning(msg)
1024        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
1025            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
1026
1027        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:
1029    def preprocess(self, expression: exp.Expr) -> exp.Expr:
1030        """Apply generic preprocessing transformations to a given expression."""
1031        expression = self._move_ctes_to_top_level(expression)
1032
1033        if self.ENSURE_BOOLS:
1034            import sqlglot.transforms
1035
1036            expression = sqlglot.transforms.ensure_bools(expression)
1037
1038        return expression

Apply generic preprocessing transformations to a given expression.

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