Edit on GitHub

sqlglot.generator

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

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

Apply generic preprocessing transformations to a given expression.

def unsupported(self, message: str) -> None:
958    def unsupported(self, message: str) -> None:
959        if self.unsupported_level == ErrorLevel.IMMEDIATE:
960            raise UnsupportedError(message)
961        self.unsupported_messages.append(message)
def sep(self, sep: str = ' ') -> str:
963    def sep(self, sep: str = " ") -> str:
964        return f"{sep.strip()}\n" if self.pretty else sep
def seg(self, sql: str, sep: str = ' ') -> str:
966    def seg(self, sql: str, sep: str = " ") -> str:
967        return f"{self.sep(sep)}{sql}"
def sanitize_comment(self, comment: str) -> str:
969    def sanitize_comment(self, comment: str) -> str:
970        comment = " " + comment if comment[0].strip() else comment
971        comment = comment + " " if comment[-1].strip() else comment
972
973        # Escape block comment markers to prevent premature closure or unintended nesting.
974        # This is necessary because single-line comments (--) are converted to block comments
975        # (/* */) on output, and any */ in the original text would close the comment early.
976        comment = comment.replace("*/", "* /").replace("/*", "/ *")
977
978        return comment
def maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
 980    def maybe_comment(
 981        self,
 982        sql: str,
 983        expression: exp.Expr | None = None,
 984        comments: list[str] | None = None,
 985        separated: bool = False,
 986    ) -> str:
 987        comments = (
 988            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 989            if self.comments
 990            else None
 991        )
 992
 993        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 994            return sql
 995
 996        comments_sql = " ".join(
 997            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
 998        )
 999
1000        if not comments_sql:
1001            return sql
1002
1003        comments_sql = self._replace_line_breaks(comments_sql)
1004
1005        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1006            return (
1007                f"{self.sep()}{comments_sql}{sql}"
1008                if not sql or sql[0].isspace()
1009                else f"{comments_sql}{self.sep()}{sql}"
1010            )
1011
1012        return f"{sql} {comments_sql}"
def wrap(self, expression: sqlglot.expressions.core.Expr | str) -> str:
1014    def wrap(self, expression: exp.Expr | str) -> str:
1015        this_sql = (
1016            self.sql(expression)
1017            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1018            else self.sql(expression, "this")
1019        )
1020        if not this_sql:
1021            return "()"
1022
1023        this_sql = self.indent(this_sql, level=1, pad=0)
1024        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def no_identify(self, func: Callable[..., str], *args, **kwargs) -> str:
1026    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1027        original = self.identify
1028        self.identify = False
1029        result = func(*args, **kwargs)
1030        self.identify = original
1031        return result
def normalize_func(self, name: str) -> str:
1033    def normalize_func(self, name: str) -> str:
1034        if self.normalize_functions == "upper" or self.normalize_functions is True:
1035            return name.upper()
1036        if self.normalize_functions == "lower":
1037            return name.lower()
1038        return name
def indent( self, sql: str, level: int = 0, pad: int | None = None, skip_first: bool = False, skip_last: bool = False) -> str:
1040    def indent(
1041        self,
1042        sql: str,
1043        level: int = 0,
1044        pad: int | None = None,
1045        skip_first: bool = False,
1046        skip_last: bool = False,
1047    ) -> str:
1048        if not self.pretty or not sql:
1049            return sql
1050
1051        pad = self.pad if pad is None else pad
1052        lines = sql.split("\n")
1053
1054        return "\n".join(
1055            (
1056                line
1057                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1058                else f"{' ' * (level * self._indent + pad)}{line}"
1059            )
1060            for i, line in enumerate(lines)
1061        )
def sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1063    def sql(
1064        self,
1065        expression: str | exp.Expr | None,
1066        key: str | None = None,
1067        comment: bool = True,
1068    ) -> str:
1069        if not expression:
1070            return ""
1071
1072        if isinstance(expression, str):
1073            return expression
1074
1075        if key:
1076            value = expression.args.get(key)
1077            if value:
1078                return self.sql(value)
1079            return ""
1080
1081        handler = self._dispatch.get(expression.__class__)
1082
1083        if handler:
1084            sql = handler(self, expression)
1085        elif isinstance(expression, exp.Func):
1086            sql = self.function_fallback_sql(expression)
1087        elif isinstance(expression, exp.Property):
1088            sql = self.property_sql(expression)
1089        else:
1090            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1091
1092        return self.maybe_comment(sql, expression) if self.comments and comment else sql
def uncache_sql(self, expression: sqlglot.expressions.core.Uncache) -> str:
1094    def uncache_sql(self, expression: exp.Uncache) -> str:
1095        table = self.sql(expression, "this")
1096        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1097        return f"UNCACHE TABLE{exists_sql} {table}"
def cache_sql(self, expression: sqlglot.expressions.core.Cache) -> str:
1099    def cache_sql(self, expression: exp.Cache) -> str:
1100        lazy = " LAZY" if expression.args.get("lazy") else ""
1101        table = self.sql(expression, "this")
1102        options = expression.args.get("options")
1103        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1104        sql = self.sql(expression, "expression")
1105        sql = f" AS{self.sep()}{sql}" if sql else ""
1106        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1107        return self.prepend_ctes(expression, sql)
def characterset_sql(self, expression: sqlglot.expressions.ddl.CharacterSet) -> str:
1109    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1110        default = "DEFAULT " if expression.args.get("default") else ""
1111        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
def column_parts(self, expression: sqlglot.expressions.core.Column) -> str:
1113    def column_parts(self, expression: exp.Column) -> str:
1114        return ".".join(
1115            self.sql(part)
1116            for part in (
1117                expression.args.get("catalog"),
1118                expression.args.get("db"),
1119                expression.args.get("table"),
1120                expression.args.get("this"),
1121            )
1122            if part
1123        )
def column_sql(self, expression: sqlglot.expressions.core.Column) -> str:
1125    def column_sql(self, expression: exp.Column) -> str:
1126        join_mark = " (+)" if expression.args.get("join_mark") else ""
1127
1128        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1129            join_mark = ""
1130            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1131
1132        return f"{self.column_parts(expression)}{join_mark}"
def pseudocolumn_sql(self, expression: sqlglot.expressions.core.Pseudocolumn) -> str:
1134    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1135        return self.column_sql(expression)
def columnposition_sql(self, expression: sqlglot.expressions.query.ColumnPosition) -> str:
1137    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1138        this = self.sql(expression, "this")
1139        this = f" {this}" if this else ""
1140        position = self.sql(expression, "position")
1141        return f"{position}{this}"
def columndef_sql( self, expression: sqlglot.expressions.query.ColumnDef, sep: str = ' ') -> str:
1143    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1144        column = self.sql(expression, "this")
1145        kind = self.sql(expression, "kind")
1146        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1147        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1148        kind = f"{sep}{kind}" if kind else ""
1149        constraints = f" {constraints}" if constraints else ""
1150        position = self.sql(expression, "position")
1151        position = f" {position}" if position else ""
1152
1153        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1154            kind = ""
1155
1156        return f"{exists}{column}{kind}{constraints}{position}"
def columnconstraint_sql( self, expression: sqlglot.expressions.constraints.ColumnConstraint) -> str:
1158    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1159        this = self.sql(expression, "this")
1160        kind_sql = self.sql(expression, "kind").strip()
1161        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
def computedcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.ComputedColumnConstraint) -> str:
1163    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1164        this = self.sql(expression, "this")
1165        if expression.args.get("not_null"):
1166            persisted = " PERSISTED NOT NULL"
1167        elif expression.args.get("persisted"):
1168            persisted = " PERSISTED"
1169        else:
1170            persisted = ""
1171
1172        return f"AS {this}{persisted}"
def autoincrementcolumnconstraint_sql( self, _: sqlglot.expressions.constraints.AutoIncrementColumnConstraint) -> str:
1174    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1175        return self.token_sql(TokenType.AUTO_INCREMENT)
def compresscolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CompressColumnConstraint) -> str:
1177    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1178        if isinstance(expression.this, list):
1179            this = self.wrap(self.expressions(expression, key="this", flat=True))
1180        else:
1181            this = self.sql(expression, "this")
1182
1183        return f"COMPRESS {this}"
def generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsIdentityColumnConstraint) -> str:
1185    def generatedasidentitycolumnconstraint_sql(
1186        self, expression: exp.GeneratedAsIdentityColumnConstraint
1187    ) -> str:
1188        this = ""
1189        if expression.this is not None:
1190            on_null = " ON NULL" if expression.args.get("on_null") else ""
1191            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1192
1193        start = expression.args.get("start")
1194        start = f"START WITH {start}" if start else ""
1195        increment = expression.args.get("increment")
1196        increment = f" INCREMENT BY {increment}" if increment else ""
1197        minvalue = expression.args.get("minvalue")
1198        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1199        maxvalue = expression.args.get("maxvalue")
1200        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1201        cycle = expression.args.get("cycle")
1202        cycle_sql = ""
1203
1204        if cycle is not None:
1205            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1206            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1207
1208        sequence_opts = ""
1209        if start or increment or cycle_sql:
1210            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1211            sequence_opts = f" ({sequence_opts.strip()})"
1212
1213        expr = self.sql(expression, "expression")
1214        expr = f"({expr})" if expr else "IDENTITY"
1215
1216        return f"GENERATED{this} AS {expr}{sequence_opts}"
def generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1218    def generatedasrowcolumnconstraint_sql(
1219        self, expression: exp.GeneratedAsRowColumnConstraint
1220    ) -> str:
1221        start = "START" if expression.args.get("start") else "END"
1222        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1223        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
def periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.constraints.PeriodForSystemTimeConstraint) -> str:
1225    def periodforsystemtimeconstraint_sql(
1226        self, expression: exp.PeriodForSystemTimeConstraint
1227    ) -> str:
1228        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
def notnullcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.NotNullColumnConstraint) -> str:
1230    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1231        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
def primarykeycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.PrimaryKeyColumnConstraint) -> str:
1233    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1234        desc = expression.args.get("desc")
1235        if desc is not None:
1236            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1237        options = self.expressions(expression, key="options", flat=True, sep=" ")
1238        options = f" {options}" if options else ""
1239        return f"PRIMARY KEY{options}"
def uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1241    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1242        this = self.sql(expression, "this")
1243        this = f" {this}" if this else ""
1244        index_type = expression.args.get("index_type")
1245        index_type = f" USING {index_type}" if index_type else ""
1246        on_conflict = self.sql(expression, "on_conflict")
1247        on_conflict = f" {on_conflict}" if on_conflict else ""
1248        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1249        options = self.expressions(expression, key="options", flat=True, sep=" ")
1250        options = f" {options}" if options else ""
1251        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1253    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1254        input_ = expression.args.get("input_")
1255        output = expression.args.get("output")
1256        variadic = expression.args.get("variadic")
1257
1258        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1259        if variadic:
1260            return "VARIADIC"
1261
1262        if input_ and output:
1263            return f"IN{self.INOUT_SEPARATOR}OUT"
1264        if input_:
1265            return "IN"
1266        if output:
1267            return "OUT"
1268
1269        return ""
def createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1271    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1272        return self.sql(expression, "this")
def create_sql(self, expression: sqlglot.expressions.ddl.Create) -> str:
1274    def create_sql(self, expression: exp.Create) -> str:
1275        kind = self.sql(expression, "kind")
1276        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1277
1278        properties = expression.args.get("properties")
1279
1280        if (
1281            kind == "TRIGGER"
1282            and properties
1283            and properties.expressions
1284            and isinstance(properties.expressions[0], exp.TriggerProperties)
1285            and properties.expressions[0].args.get("constraint")
1286        ):
1287            kind = f"CONSTRAINT {kind}"
1288
1289        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1290
1291        this = self.createable_sql(expression, properties_locs)
1292
1293        properties_sql = ""
1294        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1295            exp.Properties.Location.POST_WITH
1296        ):
1297            props_ast = exp.Properties(
1298                expressions=[
1299                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1300                    *properties_locs[exp.Properties.Location.POST_WITH],
1301                ]
1302            )
1303            props_ast.parent = expression
1304            properties_sql = self.sql(props_ast)
1305
1306            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1307                properties_sql = self.sep() + properties_sql
1308            elif not self.pretty:
1309                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1310                properties_sql = f" {properties_sql}"
1311
1312        begin = " BEGIN" if expression.args.get("begin") else ""
1313
1314        expression_sql = self.sql(expression, "expression")
1315        if expression_sql:
1316            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1317
1318            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1319                postalias_props_sql = ""
1320                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1321                    postalias_props_sql = self.properties(
1322                        exp.Properties(
1323                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1324                        ),
1325                        wrapped=False,
1326                    )
1327                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1328                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1329
1330        postindex_props_sql = ""
1331        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1332            postindex_props_sql = self.properties(
1333                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1334                wrapped=False,
1335                prefix=" ",
1336            )
1337
1338        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1339        indexes = f" {indexes}" if indexes else ""
1340        index_sql = indexes + postindex_props_sql
1341
1342        replace = " OR REPLACE" if expression.args.get("replace") else ""
1343        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1344        unique = " UNIQUE" if expression.args.get("unique") else ""
1345
1346        clustered = expression.args.get("clustered")
1347        if clustered is None:
1348            clustered_sql = ""
1349        elif clustered:
1350            clustered_sql = " CLUSTERED COLUMNSTORE"
1351        else:
1352            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1353
1354        postcreate_props_sql = ""
1355        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1356            postcreate_props_sql = self.properties(
1357                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1358                sep=" ",
1359                prefix=" ",
1360                wrapped=False,
1361            )
1362
1363        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1364
1365        postexpression_props_sql = ""
1366        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1367            postexpression_props_sql = self.properties(
1368                exp.Properties(
1369                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1370                ),
1371                sep=" ",
1372                prefix=" ",
1373                wrapped=False,
1374            )
1375
1376        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1377        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1378        no_schema_binding = (
1379            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1380        )
1381
1382        clone = self.sql(expression, "clone")
1383        clone = f" {clone}" if clone else ""
1384
1385        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1386            properties_expression = f"{expression_sql}{properties_sql}"
1387        else:
1388            properties_expression = f"{properties_sql}{expression_sql}"
1389
1390        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1391        return self.prepend_ctes(expression, expression_sql)
def sequenceproperties_sql(self, expression: sqlglot.expressions.ddl.SequenceProperties) -> str:
1393    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1394        start = self.sql(expression, "start")
1395        start = f"START WITH {start}" if start else ""
1396        increment = self.sql(expression, "increment")
1397        increment = f" INCREMENT BY {increment}" if increment else ""
1398        minvalue = self.sql(expression, "minvalue")
1399        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1400        maxvalue = self.sql(expression, "maxvalue")
1401        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1402        owned = self.sql(expression, "owned")
1403        owned = f" OWNED BY {owned}" if owned else ""
1404
1405        cache = expression.args.get("cache")
1406        if cache is None:
1407            cache_str = ""
1408        elif cache is True:
1409            cache_str = " CACHE"
1410        else:
1411            cache_str = f" CACHE {cache}"
1412
1413        options = self.expressions(expression, key="options", flat=True, sep=" ")
1414        options = f" {options}" if options else ""
1415
1416        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
def triggerproperties_sql(self, expression: sqlglot.expressions.ddl.TriggerProperties) -> str:
1418    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1419        timing = expression.args.get("timing", "")
1420        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1421        timing_events = f"{timing} {events}".strip() if timing or events else ""
1422
1423        parts = [timing_events, "ON", self.sql(expression, "table")]
1424
1425        if referenced_table := expression.args.get("referenced_table"):
1426            parts.extend(["FROM", self.sql(referenced_table)])
1427
1428        if deferrable := expression.args.get("deferrable"):
1429            parts.append(deferrable)
1430
1431        if initially := expression.args.get("initially"):
1432            parts.append(f"INITIALLY {initially}")
1433
1434        if referencing := expression.args.get("referencing"):
1435            parts.append(self.sql(referencing))
1436
1437        if for_each := expression.args.get("for_each"):
1438            parts.append(f"FOR EACH {for_each}")
1439
1440        if when := expression.args.get("when"):
1441            parts.append(f"WHEN ({self.sql(when)})")
1442
1443        parts.append(self.sql(expression, "execute"))
1444
1445        return self.sep().join(parts)
def triggerreferencing_sql(self, expression: sqlglot.expressions.ddl.TriggerReferencing) -> str:
1447    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1448        parts = []
1449
1450        if old_alias := expression.args.get("old"):
1451            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1452
1453        if new_alias := expression.args.get("new"):
1454            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1455
1456        return f"REFERENCING {' '.join(parts)}"
def triggerevent_sql(self, expression: sqlglot.expressions.ddl.TriggerEvent) -> str:
1458    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1459        columns = expression.args.get("columns")
1460        if columns:
1461            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1462
1463        return self.sql(expression, "this")
def clone_sql(self, expression: sqlglot.expressions.ddl.Clone) -> str:
1465    def clone_sql(self, expression: exp.Clone) -> str:
1466        this = self.sql(expression, "this")
1467        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1468        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1469        return f"{shallow}{keyword} {this}"
def describe_sql(self, expression: sqlglot.expressions.ddl.Describe) -> str:
1471    def describe_sql(self, expression: exp.Describe) -> str:
1472        style = expression.args.get("style")
1473        style = f" {style}" if style else ""
1474        partition = self.sql(expression, "partition")
1475        partition = f" {partition}" if partition else ""
1476        format = self.sql(expression, "format")
1477        format = f" {format}" if format else ""
1478        as_json = " AS JSON" if expression.args.get("as_json") else ""
1479
1480        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
def heredoc_sql(self, expression: sqlglot.expressions.ddl.Heredoc) -> str:
1482    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1483        tag = self.sql(expression, "tag")
1484        return f"${tag}${self.sql(expression, 'this')}${tag}$"
def prepend_ctes(self, expression: sqlglot.expressions.core.Expr, sql: str) -> str:
1486    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1487        with_ = self.sql(expression, "with_")
1488        if with_:
1489            sql = f"{with_}{self.sep()}{sql}"
1490        return sql
def with_sql(self, expression: sqlglot.expressions.query.With) -> str:
1492    def with_sql(self, expression: exp.With) -> str:
1493        sql = self.expressions(expression, flat=True)
1494        recursive = (
1495            "RECURSIVE "
1496            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1497            else ""
1498        )
1499        search = self.sql(expression, "search")
1500        search = f" {search}" if search else ""
1501
1502        return f"WITH {recursive}{sql}{search}"
def cte_sql(self, expression: sqlglot.expressions.query.CTE) -> str:
1504    def cte_sql(self, expression: exp.CTE) -> str:
1505        alias = expression.args.get("alias")
1506        if alias:
1507            alias.add_comments(expression.pop_comments())
1508
1509        alias_sql = self.sql(expression, "alias")
1510
1511        materialized = expression.args.get("materialized")
1512        if materialized is False:
1513            materialized = "NOT MATERIALIZED "
1514        elif materialized:
1515            materialized = "MATERIALIZED "
1516
1517        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1518        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1519
1520        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
def tablealias_sql(self, expression: sqlglot.expressions.query.TableAlias) -> str:
1522    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1523        alias = self.sql(expression, "this")
1524        columns = self.expressions(expression, key="columns", flat=True)
1525        columns = f"({columns})" if columns else ""
1526
1527        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1528            columns = ""
1529            self.unsupported("Named columns are not supported in table alias.")
1530
1531        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1532            alias = self._next_name()
1533
1534        return f"{alias}{columns}"
def bitstring_sql(self, expression: sqlglot.expressions.query.BitString) -> str:
1536    def bitstring_sql(self, expression: exp.BitString) -> str:
1537        this = self.sql(expression, "this")
1538        if self.dialect.BIT_START:
1539            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1540        return f"{int(this, 2)}"
def hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1542    def hexstring_sql(
1543        self, expression: exp.HexString, binary_function_repr: str | None = None
1544    ) -> str:
1545        this = self.sql(expression, "this")
1546        is_integer_type = expression.args.get("is_integer")
1547
1548        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1549            not self.dialect.HEX_START and not binary_function_repr
1550        ):
1551            # Integer representation will be returned if:
1552            # - The read dialect treats the hex value as integer literal but not the write
1553            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1554            return f"{int(this, 16)}"
1555
1556        if not is_integer_type:
1557            # Read dialect treats the hex value as BINARY/BLOB
1558            if binary_function_repr:
1559                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1560                return self.func(binary_function_repr, exp.Literal.string(this))
1561            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1562                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1563                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1564
1565        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
def bytestring_sql(self, expression: sqlglot.expressions.query.ByteString) -> str:
1567    def bytestring_sql(self, expression: exp.ByteString) -> str:
1568        this = self.sql(expression, "this")
1569        if self.dialect.BYTE_START:
1570            escaped_byte_string = self.escape_str(
1571                this,
1572                escape_backslash=False,
1573                delimiter=self.dialect.BYTE_END,
1574                escaped_delimiter=self._escaped_byte_quote_end,
1575                is_byte_string=True,
1576            )
1577            is_bytes = expression.args.get("is_bytes", False)
1578            delimited_byte_string = (
1579                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1580            )
1581            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1582                return self.sql(
1583                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1584                )
1585            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1586                return self.sql(
1587                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1588                )
1589
1590            return delimited_byte_string
1591        return this
def unicodestring_sql(self, expression: sqlglot.expressions.query.UnicodeString) -> str:
1593    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1594        this = self.sql(expression, "this")
1595        escape = expression.args.get("escape")
1596
1597        if self.dialect.UNICODE_START:
1598            escape_substitute = r"\\\1"
1599            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1600        else:
1601            escape_substitute = r"\\u\1"
1602            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1603
1604        if escape:
1605            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1606            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1607        else:
1608            escape_pattern = ESCAPED_UNICODE_RE
1609            escape_sql = ""
1610
1611        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1612            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1613
1614        return f"{left_quote}{this}{right_quote}{escape_sql}"
def rawstring_sql(self, expression: sqlglot.expressions.query.RawString) -> str:
1616    def rawstring_sql(self, expression: exp.RawString) -> str:
1617        string = expression.this
1618        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1619            string = string.replace("\\", "\\\\")
1620
1621        string = self.escape_str(string, escape_backslash=False)
1622        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
def datatypeparam_sql(self, expression: sqlglot.expressions.datatypes.DataTypeParam) -> str:
1624    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1625        this = self.sql(expression, "this")
1626        specifier = self.sql(expression, "expression")
1627        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1628        return f"{this}{specifier}"
def datatype_sql(self, expression: sqlglot.expressions.datatypes.DataType) -> str:
1630    def datatype_sql(self, expression: exp.DataType) -> str:
1631        nested = ""
1632        values = ""
1633
1634        expr_nested = expression.args.get("nested")
1635        interior = (
1636            self.expressions(
1637                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1638            )
1639            if expr_nested and self.pretty
1640            else self.expressions(expression, flat=True)
1641        )
1642
1643        type_value = expression.this
1644        if type_value in self.UNSUPPORTED_TYPES:
1645            self.unsupported(
1646                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1647            )
1648
1649        type_sql: t.Any = ""
1650        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1651            type_sql = self.sql(expression, "kind")
1652        elif type_value == exp.DType.CHARACTER_SET:
1653            return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}"
1654        else:
1655            type_sql = (
1656                self.TYPE_MAPPING.get(type_value, type_value.value)
1657                if isinstance(type_value, exp.DType)
1658                else type_value
1659            )
1660
1661        if interior:
1662            if expr_nested:
1663                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1664                if expression.args.get("values") is not None:
1665                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1666                    values = self.expressions(expression, key="values", flat=True)
1667                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1668            elif type_value == exp.DType.INTERVAL:
1669                nested = f" {interior}"
1670            else:
1671                nested = f"({interior})"
1672
1673        type_sql = f"{type_sql}{nested}{values}"
1674        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1675            exp.DType.TIMETZ,
1676            exp.DType.TIMESTAMPTZ,
1677        ):
1678            type_sql = f"{type_sql} WITH TIME ZONE"
1679
1680        return type_sql
def directory_sql(self, expression: sqlglot.expressions.dml.Directory) -> str:
1682    def directory_sql(self, expression: exp.Directory) -> str:
1683        local = "LOCAL " if expression.args.get("local") else ""
1684        row_format = self.sql(expression, "row_format")
1685        row_format = f" {row_format}" if row_format else ""
1686        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
def delete_sql(self, expression: sqlglot.expressions.dml.Delete) -> str:
1688    def delete_sql(self, expression: exp.Delete) -> str:
1689        hint = self.sql(expression, "hint")
1690        this = self.sql(expression, "this")
1691        this = f" FROM {this}" if this else ""
1692        using = self.expressions(expression, key="using")
1693        using = f" USING {using}" if using else ""
1694        cluster = self.sql(expression, "cluster")
1695        cluster = f" {cluster}" if cluster else ""
1696        where = self.sql(expression, "where")
1697        returning = self.sql(expression, "returning")
1698        order = self.sql(expression, "order")
1699        limit = self.sql(expression, "limit")
1700        tables = self.expressions(expression, key="tables")
1701        tables = f" {tables}" if tables else ""
1702        if self.RETURNING_END:
1703            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1704        else:
1705            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1706        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
def drop_sql(self, expression: sqlglot.expressions.ddl.Drop) -> str:
1708    def drop_sql(self, expression: exp.Drop) -> str:
1709        this = self.sql(expression, "this")
1710        expressions = self.expressions(expression, flat=True)
1711        expressions = f" ({expressions})" if expressions else ""
1712        kind = expression.args["kind"]
1713        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1714        iceberg = (
1715            " ICEBERG"
1716            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1717            else ""
1718        )
1719        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1720        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1721        on_cluster = self.sql(expression, "cluster")
1722        on_cluster = f" {on_cluster}" if on_cluster else ""
1723        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1724        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1725        cascade = " CASCADE" if expression.args.get("cascade") else ""
1726        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1727        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1728        purge = " PURGE" if expression.args.get("purge") else ""
1729        sync = " SYNC" if expression.args.get("sync") else ""
1730        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}"
def set_operation(self, expression: sqlglot.expressions.query.SetOperation) -> str:
1732    def set_operation(self, expression: exp.SetOperation) -> str:
1733        op_type = type(expression)
1734        op_name = op_type.key.upper()
1735
1736        distinct = expression.args.get("distinct")
1737        if (
1738            distinct is False
1739            and op_type in (exp.Except, exp.Intersect)
1740            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1741        ):
1742            self.unsupported(f"{op_name} ALL is not supported")
1743
1744        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1745
1746        if distinct is None:
1747            distinct = default_distinct
1748            if distinct is None:
1749                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1750
1751        if distinct is default_distinct:
1752            distinct_or_all = ""
1753        else:
1754            distinct_or_all = " DISTINCT" if distinct else " ALL"
1755
1756        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1757        side_kind = f"{side_kind} " if side_kind else ""
1758
1759        by_name = " BY NAME" if expression.args.get("by_name") else ""
1760        on = self.expressions(expression, key="on", flat=True)
1761        on = f" ON ({on})" if on else ""
1762
1763        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
def set_operations(self, expression: sqlglot.expressions.query.SetOperation) -> str:
1765    def set_operations(self, expression: exp.SetOperation) -> str:
1766        if not self.SET_OP_MODIFIERS:
1767            limit = expression.args.get("limit")
1768            order = expression.args.get("order")
1769
1770            if limit or order:
1771                select = self._move_ctes_to_top_level(
1772                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1773                )
1774
1775                if limit:
1776                    select = select.limit(limit.pop(), copy=False)
1777                if order:
1778                    select = select.order_by(order.pop(), copy=False)
1779                return self.sql(select)
1780
1781        sqls: list[str] = []
1782        stack: list[str | exp.Expr] = [expression]
1783
1784        while stack:
1785            node = stack.pop()
1786
1787            if isinstance(node, exp.SetOperation):
1788                stack.append(node.expression)
1789                stack.append(
1790                    self.maybe_comment(
1791                        self.set_operation(node), comments=node.comments, separated=True
1792                    )
1793                )
1794                stack.append(node.this)
1795            else:
1796                sqls.append(self.sql(node))
1797
1798        this = self.sep().join(sqls)
1799        this = self.query_modifiers(expression, this)
1800        return self.prepend_ctes(expression, this)
def fetch_sql(self, expression: sqlglot.expressions.query.Fetch) -> str:
1802    def fetch_sql(self, expression: exp.Fetch) -> str:
1803        direction = expression.args.get("direction")
1804        direction = f" {direction}" if direction else ""
1805        count = self.sql(expression, "count")
1806        count = f" {count}" if count else ""
1807        limit_options = self.sql(expression, "limit_options")
1808        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1809        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
def limitoptions_sql(self, expression: sqlglot.expressions.query.LimitOptions) -> str:
1811    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1812        percent = " PERCENT" if expression.args.get("percent") else ""
1813        rows = " ROWS" if expression.args.get("rows") else ""
1814        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1815        if not with_ties and rows:
1816            with_ties = " ONLY"
1817        return f"{percent}{rows}{with_ties}"
def filter_sql(self, expression: sqlglot.expressions.core.Filter) -> str:
1819    def filter_sql(self, expression: exp.Filter) -> str:
1820        if self.AGGREGATE_FILTER_SUPPORTED:
1821            this = self.sql(expression, "this")
1822            where = self.sql(expression, "expression").strip()
1823            return f"{this} FILTER({where})"
1824
1825        agg = expression.this
1826        agg_arg = agg.this
1827        cond = expression.expression.this
1828        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1829        return self.sql(agg)
def hint_sql(self, expression: sqlglot.expressions.core.Hint) -> str:
1831    def hint_sql(self, expression: exp.Hint) -> str:
1832        if not self.QUERY_HINTS:
1833            self.unsupported("Hints are not supported")
1834            return ""
1835
1836        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
def indexparameters_sql(self, expression: sqlglot.expressions.constraints.IndexParameters) -> str:
1838    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1839        using = self.sql(expression, "using")
1840        using = f" USING {using}" if using else ""
1841        columns = self.expressions(expression, key="columns", flat=True)
1842        columns = f"({columns})" if columns else ""
1843        partition_by = self.expressions(expression, key="partition_by", flat=True)
1844        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1845        where = self.sql(expression, "where")
1846        include = self.expressions(expression, key="include", flat=True)
1847        if include:
1848            include = f" INCLUDE ({include})"
1849        with_storage = self.expressions(expression, key="with_storage", flat=True)
1850        with_storage = f" WITH ({with_storage})" if with_storage else ""
1851        tablespace = self.sql(expression, "tablespace")
1852        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1853        on = self.sql(expression, "on")
1854        on = f" ON {on}" if on else ""
1855
1856        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
def index_sql(self, expression: sqlglot.expressions.query.Index) -> str:
1858    def index_sql(self, expression: exp.Index) -> str:
1859        unique = "UNIQUE " if expression.args.get("unique") else ""
1860        primary = "PRIMARY " if expression.args.get("primary") else ""
1861        amp = "AMP " if expression.args.get("amp") else ""
1862        name = self.sql(expression, "this")
1863        name = f"{name} " if name else ""
1864        table = self.sql(expression, "table")
1865        table = f"{self.INDEX_ON} {table}" if table else ""
1866
1867        index = "INDEX " if not table else ""
1868
1869        params = self.sql(expression, "params")
1870        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
def identifier_sql(self, expression: sqlglot.expressions.core.Identifier) -> str:
1872    def identifier_sql(self, expression: exp.Identifier) -> str:
1873        text = expression.name
1874        lower = text.lower()
1875        quoted = expression.quoted
1876        text = lower if self.normalize and not quoted else text
1877        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1878        if (
1879            quoted
1880            or self.dialect.can_quote(expression, self.identify)
1881            or lower in self.RESERVED_KEYWORDS
1882            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1883        ):
1884            text = f"{self._identifier_start}{text}{self._identifier_end}"
1885        return text
def hex_sql(self, expression: sqlglot.expressions.string.Hex) -> str:
1887    def hex_sql(self, expression: exp.Hex) -> str:
1888        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1889        if self.dialect.HEX_LOWERCASE:
1890            text = self.func("LOWER", text)
1891
1892        return text
def lowerhex_sql(self, expression: sqlglot.expressions.string.LowerHex) -> str:
1894    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1895        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1896        if not self.dialect.HEX_LOWERCASE:
1897            text = self.func("LOWER", text)
1898        return text
def inputoutputformat_sql(self, expression: sqlglot.expressions.query.InputOutputFormat) -> str:
1900    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1901        input_format = self.sql(expression, "input_format")
1902        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1903        output_format = self.sql(expression, "output_format")
1904        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1905        return self.sep().join((input_format, output_format))
def national_sql( self, expression: sqlglot.expressions.query.National, prefix: str = 'N') -> str:
1907    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1908        string = self.sql(exp.Literal.string(expression.name))
1909        return f"{prefix}{string}"
def partition_sql(self, expression: sqlglot.expressions.query.Partition) -> str:
1911    def partition_sql(self, expression: exp.Partition) -> str:
1912        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1913        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
def properties_sql(self, expression: sqlglot.expressions.properties.Properties) -> str:
1915    def properties_sql(self, expression: exp.Properties) -> str:
1916        root_properties = []
1917        with_properties = []
1918
1919        for p in expression.expressions:
1920            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1921            if p_loc == exp.Properties.Location.POST_WITH:
1922                with_properties.append(p)
1923            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1924                root_properties.append(p)
1925
1926        root_props_ast = exp.Properties(expressions=root_properties)
1927        root_props_ast.parent = expression.parent
1928
1929        with_props_ast = exp.Properties(expressions=with_properties)
1930        with_props_ast.parent = expression.parent
1931
1932        root_props = self.root_properties(root_props_ast)
1933        with_props = self.with_properties(with_props_ast)
1934
1935        if root_props and with_props and not self.pretty:
1936            with_props = " " + with_props
1937
1938        return root_props + with_props
def root_properties(self, properties: sqlglot.expressions.properties.Properties) -> str:
1940    def root_properties(self, properties: exp.Properties) -> str:
1941        if properties.expressions:
1942            return self.expressions(properties, indent=False, sep=" ")
1943        return ""
def properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1945    def properties(
1946        self,
1947        properties: exp.Properties,
1948        prefix: str = "",
1949        sep: str = ", ",
1950        suffix: str = "",
1951        wrapped: bool = True,
1952    ) -> str:
1953        if properties.expressions:
1954            expressions = self.expressions(properties, sep=sep, indent=False)
1955            if expressions:
1956                expressions = self.wrap(expressions) if wrapped else expressions
1957                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1958        return ""
def with_properties(self, properties: sqlglot.expressions.properties.Properties) -> str:
1960    def with_properties(self, properties: exp.Properties) -> str:
1961        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
def locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
1963    def locate_properties(self, properties: exp.Properties) -> defaultdict:
1964        properties_locs = defaultdict(list)
1965        for p in properties.expressions:
1966            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1967            if p_loc != exp.Properties.Location.UNSUPPORTED:
1968                properties_locs[p_loc].append(p)
1969            else:
1970                self.unsupported(f"Unsupported property {p.key}")
1971
1972        return properties_locs
def property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
1974    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1975        if isinstance(expression.this, exp.Dot):
1976            return self.sql(expression, "this")
1977        return f"'{expression.name}'" if string_key else expression.name
def property_sql(self, expression: sqlglot.expressions.properties.Property) -> str:
1979    def property_sql(self, expression: exp.Property) -> str:
1980        property_cls = expression.__class__
1981        if property_cls == exp.Property:
1982            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1983
1984        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1985        if not property_name:
1986            self.unsupported(f"Unsupported property {expression.key}")
1987
1988        return f"{property_name}={self.sql(expression, 'this')}"
def uuidproperty_sql(self, expression: sqlglot.expressions.properties.UuidProperty) -> str:
1990    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
1991        return f"UUID {self.sql(expression, 'this')}"
def likeproperty_sql(self, expression: sqlglot.expressions.properties.LikeProperty) -> str:
1993    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1994        if self.SUPPORTS_CREATE_TABLE_LIKE:
1995            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1996            options = f" {options}" if options else ""
1997
1998            like = f"LIKE {self.sql(expression, 'this')}{options}"
1999            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2000                like = f"({like})"
2001
2002            return like
2003
2004        if expression.expressions:
2005            self.unsupported("Transpilation of LIKE property options is unsupported")
2006
2007        select = exp.select("*").from_(expression.this).limit(0)
2008        return f"AS {self.sql(select)}"
def fallbackproperty_sql(self, expression: sqlglot.expressions.properties.FallbackProperty) -> str:
2010    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2011        no = "NO " if expression.args.get("no") else ""
2012        protection = " PROTECTION" if expression.args.get("protection") else ""
2013        return f"{no}FALLBACK{protection}"
def journalproperty_sql(self, expression: sqlglot.expressions.properties.JournalProperty) -> str:
2015    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2016        no = "NO " if expression.args.get("no") else ""
2017        local = expression.args.get("local")
2018        local = f"{local} " if local else ""
2019        dual = "DUAL " if expression.args.get("dual") else ""
2020        before = "BEFORE " if expression.args.get("before") else ""
2021        after = "AFTER " if expression.args.get("after") else ""
2022        return f"{no}{local}{dual}{before}{after}JOURNAL"
def freespaceproperty_sql( self, expression: sqlglot.expressions.properties.FreespaceProperty) -> str:
2024    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2025        freespace = self.sql(expression, "this")
2026        percent = " PERCENT" if expression.args.get("percent") else ""
2027        return f"FREESPACE={freespace}{percent}"
def checksumproperty_sql(self, expression: sqlglot.expressions.properties.ChecksumProperty) -> str:
2029    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2030        if expression.args.get("default"):
2031            property = "DEFAULT"
2032        elif expression.args.get("on"):
2033            property = "ON"
2034        else:
2035            property = "OFF"
2036        return f"CHECKSUM={property}"
def mergeblockratioproperty_sql( self, expression: sqlglot.expressions.properties.MergeBlockRatioProperty) -> str:
2038    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2039        if expression.args.get("no"):
2040            return "NO MERGEBLOCKRATIO"
2041        if expression.args.get("default"):
2042            return "DEFAULT MERGEBLOCKRATIO"
2043
2044        percent = " PERCENT" if expression.args.get("percent") else ""
2045        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def moduleproperty_sql(self, expression: sqlglot.expressions.properties.ModuleProperty) -> str:
2047    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2048        expressions = self.expressions(expression, flat=True)
2049        expressions = f"({expressions})" if expressions else ""
2050        return f"USING {self.sql(expression, 'this')}{expressions}"
def datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2052    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2053        default = expression.args.get("default")
2054        minimum = expression.args.get("minimum")
2055        maximum = expression.args.get("maximum")
2056        if default or minimum or maximum:
2057            if default:
2058                prop = "DEFAULT"
2059            elif minimum:
2060                prop = "MINIMUM"
2061            else:
2062                prop = "MAXIMUM"
2063            return f"{prop} DATABLOCKSIZE"
2064        units = expression.args.get("units")
2065        units = f" {units}" if units else ""
2066        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2068    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2069        autotemp = expression.args.get("autotemp")
2070        always = expression.args.get("always")
2071        default = expression.args.get("default")
2072        manual = expression.args.get("manual")
2073        never = expression.args.get("never")
2074
2075        if autotemp is not None:
2076            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2077        elif always:
2078            prop = "ALWAYS"
2079        elif default:
2080            prop = "DEFAULT"
2081        elif manual:
2082            prop = "MANUAL"
2083        elif never:
2084            prop = "NEVER"
2085        return f"BLOCKCOMPRESSION={prop}"
def isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2087    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2088        no = expression.args.get("no")
2089        no = " NO" if no else ""
2090        concurrent = expression.args.get("concurrent")
2091        concurrent = " CONCURRENT" if concurrent else ""
2092        target = self.sql(expression, "target")
2093        target = f" {target}" if target else ""
2094        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2096    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2097        if isinstance(expression.this, list):
2098            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2099        if expression.this:
2100            modulus = self.sql(expression, "this")
2101            remainder = self.sql(expression, "expression")
2102            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2103
2104        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2105        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2106        return f"FROM ({from_expressions}) TO ({to_expressions})"
def partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2108    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2109        this = self.sql(expression, "this")
2110
2111        for_values_or_default = expression.expression
2112        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2113            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2114        else:
2115            for_values_or_default = " DEFAULT"
2116
2117        return f"PARTITION OF {this}{for_values_or_default}"
def lockingproperty_sql(self, expression: sqlglot.expressions.properties.LockingProperty) -> str:
2119    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2120        kind = expression.args.get("kind")
2121        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2122        for_or_in = expression.args.get("for_or_in")
2123        for_or_in = f" {for_or_in}" if for_or_in else ""
2124        lock_type = expression.args.get("lock_type")
2125        override = " OVERRIDE" if expression.args.get("override") else ""
2126        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
def withdataproperty_sql(self, expression: sqlglot.expressions.properties.WithDataProperty) -> str:
2128    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2129        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2130        statistics = expression.args.get("statistics")
2131        statistics_sql = ""
2132        if statistics is not None:
2133            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2134        return f"{data_sql}{statistics_sql}"
def withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2136    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2137        this = self.sql(expression, "this")
2138        this = f"HISTORY_TABLE={this}" if this else ""
2139        data_consistency: str | None = self.sql(expression, "data_consistency")
2140        data_consistency = (
2141            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2142        )
2143        retention_period: str | None = self.sql(expression, "retention_period")
2144        retention_period = (
2145            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2146        )
2147
2148        if this:
2149            on_sql = self.func("ON", this, data_consistency, retention_period)
2150        else:
2151            on_sql = "ON" if expression.args.get("on") else "OFF"
2152
2153        sql = f"SYSTEM_VERSIONING={on_sql}"
2154
2155        return f"WITH({sql})" if expression.args.get("with_") else sql
def insert_sql(self, expression: sqlglot.expressions.dml.Insert) -> str:
2157    def insert_sql(self, expression: exp.Insert) -> str:
2158        hint = self.sql(expression, "hint")
2159        overwrite = expression.args.get("overwrite")
2160
2161        if isinstance(expression.this, exp.Directory):
2162            this = " OVERWRITE" if overwrite else " INTO"
2163        else:
2164            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2165
2166        stored = self.sql(expression, "stored")
2167        stored = f" {stored}" if stored else ""
2168        alternative = expression.args.get("alternative")
2169        alternative = f" OR {alternative}" if alternative else ""
2170        ignore = " IGNORE" if expression.args.get("ignore") else ""
2171        is_function = expression.args.get("is_function")
2172        if is_function:
2173            this = f"{this} FUNCTION"
2174        this = f"{this} {self.sql(expression, 'this')}"
2175
2176        exists = " IF EXISTS" if expression.args.get("exists") else ""
2177        where = self.sql(expression, "where")
2178        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2179        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2180        on_conflict = self.sql(expression, "conflict")
2181        on_conflict = f" {on_conflict}" if on_conflict else ""
2182        by_name = " BY NAME" if expression.args.get("by_name") else ""
2183        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2184        returning = self.sql(expression, "returning")
2185
2186        if self.RETURNING_END:
2187            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2188        else:
2189            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2190
2191        partition_by = self.sql(expression, "partition")
2192        partition_by = f" {partition_by}" if partition_by else ""
2193        settings = self.sql(expression, "settings")
2194        settings = f" {settings}" if settings else ""
2195
2196        source = self.sql(expression, "source")
2197        source = f"TABLE {source}" if source else ""
2198
2199        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2200        return self.prepend_ctes(expression, sql)
def introducer_sql(self, expression: sqlglot.expressions.query.Introducer) -> str:
2202    def introducer_sql(self, expression: exp.Introducer) -> str:
2203        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def kill_sql(self, expression: sqlglot.expressions.ddl.Kill) -> str:
2205    def kill_sql(self, expression: exp.Kill) -> str:
2206        kind = self.sql(expression, "kind")
2207        kind = f" {kind}" if kind else ""
2208        this = self.sql(expression, "this")
2209        this = f" {this}" if this else ""
2210        return f"KILL{kind}{this}"
def pseudotype_sql(self, expression: sqlglot.expressions.datatypes.PseudoType) -> str:
2212    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2213        return expression.name
def objectidentifier_sql(self, expression: sqlglot.expressions.datatypes.ObjectIdentifier) -> str:
2215    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2216        return expression.name
def onconflict_sql(self, expression: sqlglot.expressions.dml.OnConflict) -> str:
2218    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2219        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2220
2221        constraint = self.sql(expression, "constraint")
2222        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2223
2224        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2225        if conflict_keys:
2226            conflict_keys = f"({conflict_keys})"
2227
2228        index_predicate = self.sql(expression, "index_predicate")
2229        conflict_keys = f"{conflict_keys}{index_predicate} "
2230
2231        action = self.sql(expression, "action")
2232
2233        expressions = self.expressions(expression, flat=True)
2234        if expressions:
2235            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2236            expressions = f" {set_keyword}{expressions}"
2237
2238        where = self.sql(expression, "where")
2239        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def returning_sql(self, expression: sqlglot.expressions.dml.Returning) -> str:
2241    def returning_sql(self, expression: exp.Returning) -> str:
2242        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
def rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2244    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2245        fields = self.sql(expression, "fields")
2246        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2247        escaped = self.sql(expression, "escaped")
2248        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2249        items = self.sql(expression, "collection_items")
2250        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2251        keys = self.sql(expression, "map_keys")
2252        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2253        lines = self.sql(expression, "lines")
2254        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2255        null = self.sql(expression, "null")
2256        null = f" NULL DEFINED AS {null}" if null else ""
2257        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
def withtablehint_sql(self, expression: sqlglot.expressions.query.WithTableHint) -> str:
2259    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2260        return f"WITH ({self.expressions(expression, flat=True)})"
def indextablehint_sql(self, expression: sqlglot.expressions.query.IndexTableHint) -> str:
2262    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2263        this = f"{self.sql(expression, 'this')} INDEX"
2264        target = self.sql(expression, "target")
2265        target = f" FOR {target}" if target else ""
2266        return f"{this}{target} ({self.expressions(expression, flat=True)})"
def historicaldata_sql(self, expression: sqlglot.expressions.query.HistoricalData) -> str:
2268    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2269        this = self.sql(expression, "this")
2270        kind = self.sql(expression, "kind")
2271        expr = self.sql(expression, "expression")
2272        return f"{this} ({kind} => {expr})"
def table_parts(self, expression: sqlglot.expressions.query.Table) -> str:
2274    def table_parts(self, expression: exp.Table) -> str:
2275        return ".".join(
2276            self.sql(part)
2277            for part in (
2278                expression.args.get("catalog"),
2279                expression.args.get("db"),
2280                expression.args.get("this"),
2281            )
2282            if part is not None
2283        )
def table_sql( self, expression: sqlglot.expressions.query.Table, sep: str = ' AS ') -> str:
2285    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2286        table = self.table_parts(expression)
2287        only = "ONLY " if expression.args.get("only") else ""
2288        partition = self.sql(expression, "partition")
2289        partition = f" {partition}" if partition else ""
2290        version = self.sql(expression, "version")
2291        version = f" {version}" if version else ""
2292        alias = self.sql(expression, "alias")
2293        alias = f"{sep}{alias}" if alias else ""
2294
2295        sample = self.sql(expression, "sample")
2296        post_alias = ""
2297        pre_alias = ""
2298
2299        if self.dialect.ALIAS_POST_TABLESAMPLE:
2300            pre_alias = sample
2301        else:
2302            post_alias = sample
2303
2304        if self.dialect.ALIAS_POST_VERSION:
2305            pre_alias = f"{pre_alias}{version}"
2306        else:
2307            post_alias = f"{post_alias}{version}"
2308
2309        hints = self.expressions(expression, key="hints", sep=" ")
2310        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2311        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2312        joins = self.indent(
2313            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2314        )
2315        laterals = self.expressions(expression, key="laterals", sep="")
2316
2317        file_format = self.sql(expression, "format")
2318        if file_format:
2319            pattern = self.sql(expression, "pattern")
2320            pattern = f", PATTERN => {pattern}" if pattern else ""
2321            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2322
2323        ordinality = expression.args.get("ordinality") or ""
2324        if ordinality:
2325            ordinality = f" WITH ORDINALITY{alias}"
2326            alias = ""
2327
2328        when = self.sql(expression, "when")
2329        if when:
2330            table = f"{table} {when}"
2331
2332        changes = self.sql(expression, "changes")
2333        changes = f" {changes}" if changes else ""
2334
2335        rows_from = self.expressions(expression, key="rows_from")
2336        if rows_from:
2337            table = f"ROWS FROM {self.wrap(rows_from)}"
2338
2339        indexed = expression.args.get("indexed")
2340        if indexed is not None:
2341            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2342        else:
2343            indexed = ""
2344
2345        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:
2347    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2348        table = self.func("TABLE", expression.this)
2349        alias = self.sql(expression, "alias")
2350        alias = f" AS {alias}" if alias else ""
2351        sample = self.sql(expression, "sample")
2352        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2353        joins = self.indent(
2354            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2355        )
2356        return f"{table}{alias}{pivots}{sample}{joins}"
def tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2358    def tablesample_sql(
2359        self,
2360        expression: exp.TableSample,
2361        tablesample_keyword: str | None = None,
2362    ) -> str:
2363        method = self.sql(expression, "method")
2364        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2365        numerator = self.sql(expression, "bucket_numerator")
2366        denominator = self.sql(expression, "bucket_denominator")
2367        field = self.sql(expression, "bucket_field")
2368        field = f" ON {field}" if field else ""
2369        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2370        seed = self.sql(expression, "seed")
2371        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2372
2373        size = self.sql(expression, "size")
2374        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2375            size = f"{size} ROWS"
2376
2377        percent = self.sql(expression, "percent")
2378        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2379            percent = f"{percent} PERCENT"
2380
2381        expr = f"{bucket}{percent}{size}"
2382        if self.TABLESAMPLE_REQUIRES_PARENS:
2383            expr = f"({expr})"
2384
2385        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
def pivot_sql(self, expression: sqlglot.expressions.query.Pivot) -> str:
2387    def pivot_sql(self, expression: exp.Pivot) -> str:
2388        expressions = self.expressions(expression, flat=True)
2389        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2390
2391        group = self.sql(expression, "group")
2392
2393        if expression.this:
2394            this = self.sql(expression, "this")
2395            if not expressions:
2396                sql = f"UNPIVOT {this}"
2397            else:
2398                on = f"{self.seg('ON')} {expressions}"
2399                into = self.sql(expression, "into")
2400                into = f"{self.seg('INTO')} {into}" if into else ""
2401                using = self.expressions(expression, key="using", flat=True)
2402                using = f"{self.seg('USING')} {using}" if using else ""
2403                sql = f"{direction} {this}{on}{into}{using}{group}"
2404            return self.prepend_ctes(expression, sql)
2405
2406        alias = self.sql(expression, "alias")
2407        alias = f" AS {alias}" if alias else ""
2408
2409        fields = self.expressions(
2410            expression,
2411            "fields",
2412            sep=" ",
2413            dynamic=True,
2414            new_line=True,
2415            skip_first=True,
2416            skip_last=True,
2417        )
2418
2419        include_nulls = expression.args.get("include_nulls")
2420        if include_nulls is not None:
2421            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2422        else:
2423            nulls = ""
2424
2425        default_on_null = self.sql(expression, "default_on_null")
2426        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2427        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2428        return self.prepend_ctes(expression, sql)
def version_sql(self, expression: sqlglot.expressions.query.Version) -> str:
2430    def version_sql(self, expression: exp.Version) -> str:
2431        this = f"FOR {expression.name}"
2432        kind = expression.text("kind")
2433        expr = self.sql(expression, "expression")
2434        return f"{this} {kind} {expr}"
def tuple_sql(self, expression: sqlglot.expressions.query.Tuple) -> str:
2436    def tuple_sql(self, expression: exp.Tuple) -> str:
2437        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:
2471    def update_sql(self, expression: exp.Update) -> str:
2472        hint = self.sql(expression, "hint")
2473        this = self.sql(expression, "this")
2474        join_sql, from_sql = self._update_from_joins_sql(expression)
2475        set_sql = self.expressions(expression, flat=True)
2476        where_sql = self.sql(expression, "where")
2477        returning = self.sql(expression, "returning")
2478        order = self.sql(expression, "order")
2479        limit = self.sql(expression, "limit")
2480        if self.RETURNING_END:
2481            expression_sql = f"{from_sql}{where_sql}{returning}"
2482        else:
2483            expression_sql = f"{returning}{from_sql}{where_sql}"
2484        options = self.expressions(expression, key="options")
2485        options = f" OPTION({options})" if options else ""
2486        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2487        return self.prepend_ctes(expression, sql)
def values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2489    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2490        values_as_table = values_as_table and self.VALUES_AS_TABLE
2491
2492        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2493        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2494            args = self.expressions(expression)
2495            alias = self.sql(expression, "alias")
2496            values = f"VALUES{self.seg('')}{args}"
2497            values = (
2498                f"({values})"
2499                if self.WRAP_DERIVED_VALUES
2500                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2501                else values
2502            )
2503            values = self.query_modifiers(expression, values)
2504            return f"{values} AS {alias}" if alias else values
2505
2506        # Converts `VALUES...` expression into a series of select unions.
2507        alias_node = expression.args.get("alias")
2508        column_names = alias_node and alias_node.columns
2509
2510        selects: list[exp.Query] = []
2511
2512        for i, tup in enumerate(expression.expressions):
2513            row = tup.expressions
2514
2515            if i == 0 and column_names:
2516                row = [
2517                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2518                ]
2519
2520            selects.append(exp.Select(expressions=row))
2521
2522        if self.pretty:
2523            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2524            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2525            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2526            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2527            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2528
2529        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2530        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2531        return f"({unions}){alias}"
def var_sql(self, expression: sqlglot.expressions.core.Var) -> str:
2533    def var_sql(self, expression: exp.Var) -> str:
2534        return self.sql(expression, "this")
@unsupported_args('expressions')
def into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2536    @unsupported_args("expressions")
2537    def into_sql(self, expression: exp.Into) -> str:
2538        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2539        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2540        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
def from_sql(self, expression: sqlglot.expressions.query.From) -> str:
2542    def from_sql(self, expression: exp.From) -> str:
2543        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
def groupingsets_sql(self, expression: sqlglot.expressions.query.GroupingSets) -> str:
2545    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2546        grouping_sets = self.expressions(expression, indent=False)
2547        return f"GROUPING SETS {self.wrap(grouping_sets)}"
def rollup_sql(self, expression: sqlglot.expressions.query.Rollup) -> str:
2549    def rollup_sql(self, expression: exp.Rollup) -> str:
2550        expressions = self.expressions(expression, indent=False)
2551        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
def rollupindex_sql(self, expression: sqlglot.expressions.properties.RollupIndex) -> str:
2553    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2554        this = self.sql(expression, "this")
2555
2556        columns = self.expressions(expression, flat=True)
2557
2558        from_sql = self.sql(expression, "from_index")
2559        from_sql = f" FROM {from_sql}" if from_sql else ""
2560
2561        properties = expression.args.get("properties")
2562        properties_sql = (
2563            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2564        )
2565
2566        return f"{this}({columns}){from_sql}{properties_sql}"
def rollupproperty_sql(self, expression: sqlglot.expressions.properties.RollupProperty) -> str:
2568    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2569        return f"ROLLUP ({self.expressions(expression, flat=True)})"
def cube_sql(self, expression: sqlglot.expressions.query.Cube) -> str:
2571    def cube_sql(self, expression: exp.Cube) -> str:
2572        expressions = self.expressions(expression, indent=False)
2573        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
def group_sql(self, expression: sqlglot.expressions.query.Group) -> str:
2575    def group_sql(self, expression: exp.Group) -> str:
2576        group_by_all = expression.args.get("all")
2577        if group_by_all is True:
2578            modifier = " ALL"
2579        elif group_by_all is False:
2580            modifier = " DISTINCT"
2581        else:
2582            modifier = ""
2583
2584        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2585
2586        grouping_sets = self.expressions(expression, key="grouping_sets")
2587        cube = self.expressions(expression, key="cube")
2588        rollup = self.expressions(expression, key="rollup")
2589
2590        groupings = csv(
2591            self.seg(grouping_sets) if grouping_sets else "",
2592            self.seg(cube) if cube else "",
2593            self.seg(rollup) if rollup else "",
2594            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2595            sep=self.GROUPINGS_SEP,
2596        )
2597
2598        if (
2599            expression.expressions
2600            and groupings
2601            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2602        ):
2603            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2604
2605        return f"{group_by}{groupings}"
def having_sql(self, expression: sqlglot.expressions.query.Having) -> str:
2607    def having_sql(self, expression: exp.Having) -> str:
2608        this = self.indent(self.sql(expression, "this"))
2609        return f"{self.seg('HAVING')}{self.sep()}{this}"
def connect_sql(self, expression: sqlglot.expressions.query.Connect) -> str:
2611    def connect_sql(self, expression: exp.Connect) -> str:
2612        start = self.sql(expression, "start")
2613        start = self.seg(f"START WITH {start}") if start else ""
2614        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2615        connect = self.sql(expression, "connect")
2616        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2617        return start + connect
def prior_sql(self, expression: sqlglot.expressions.query.Prior) -> str:
2619    def prior_sql(self, expression: exp.Prior) -> str:
2620        return f"PRIOR {self.sql(expression, 'this')}"
def join_sql(self, expression: sqlglot.expressions.query.Join) -> str:
2622    def join_sql(self, expression: exp.Join) -> str:
2623        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2624            side = None
2625        else:
2626            side = expression.side
2627
2628        op_sql = " ".join(
2629            op
2630            for op in (
2631                expression.method,
2632                "GLOBAL" if expression.args.get("global_") else None,
2633                side,
2634                expression.kind,
2635                expression.hint if self.JOIN_HINTS else None,
2636                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2637            )
2638            if op
2639        )
2640        match_cond = self.sql(expression, "match_condition")
2641        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2642        on_sql = self.sql(expression, "on")
2643        using = expression.args.get("using")
2644
2645        if not on_sql and using:
2646            on_sql = csv(*(self.sql(column) for column in using))
2647
2648        this = expression.this
2649        this_sql = self.sql(this)
2650
2651        exprs = self.expressions(expression)
2652        if exprs:
2653            this_sql = f"{this_sql},{self.seg(exprs)}"
2654
2655        if on_sql:
2656            on_sql = self.indent(on_sql, skip_first=True)
2657            space = self.seg(" " * self.pad) if self.pretty else " "
2658            if using:
2659                on_sql = f"{space}USING ({on_sql})"
2660            else:
2661                on_sql = f"{space}ON {on_sql}"
2662        elif not op_sql:
2663            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2664                return f" {this_sql}"
2665
2666            return f", {this_sql}"
2667
2668        if op_sql != "STRAIGHT_JOIN":
2669            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2670
2671        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2672        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
def lambda_sql( self, expression: sqlglot.expressions.query.Lambda, arrow_sep: str = '->', wrap: bool = True) -> str:
2674    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2675        args = self.expressions(expression, flat=True)
2676        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2677        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
def lateral_op(self, expression: sqlglot.expressions.query.Lateral) -> str:
2679    def lateral_op(self, expression: exp.Lateral) -> str:
2680        cross_apply = expression.args.get("cross_apply")
2681
2682        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2683        if cross_apply is True:
2684            op = "INNER JOIN "
2685        elif cross_apply is False:
2686            op = "LEFT JOIN "
2687        else:
2688            op = ""
2689
2690        return f"{op}LATERAL"
def lateral_sql(self, expression: sqlglot.expressions.query.Lateral) -> str:
2692    def lateral_sql(self, expression: exp.Lateral) -> str:
2693        this = self.sql(expression, "this")
2694
2695        if expression.args.get("view"):
2696            alias = expression.args["alias"]
2697            columns = self.expressions(alias, key="columns", flat=True)
2698            table = f" {alias.name}" if alias.name else ""
2699            columns = f" AS {columns}" if columns else ""
2700            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2701            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2702
2703        alias = self.sql(expression, "alias")
2704        alias = f" AS {alias}" if alias else ""
2705
2706        ordinality = expression.args.get("ordinality") or ""
2707        if ordinality:
2708            ordinality = f" WITH ORDINALITY{alias}"
2709            alias = ""
2710
2711        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
def limit_sql( self, expression: sqlglot.expressions.query.Limit, top: bool = False) -> str:
2713    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2714        this = self.sql(expression, "this")
2715
2716        args = [
2717            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2718            for e in (expression.args.get(k) for k in ("offset", "expression"))
2719            if e
2720        ]
2721
2722        args_sql = ", ".join(self.sql(e) for e in args)
2723        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2724        expressions = self.expressions(expression, flat=True)
2725        limit_options = self.sql(expression, "limit_options")
2726        expressions = f" BY {expressions}" if expressions else ""
2727
2728        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:
2730    def offset_sql(self, expression: exp.Offset) -> str:
2731        this = self.sql(expression, "this")
2732        value = expression.expression
2733        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2734        expressions = self.expressions(expression, flat=True)
2735        expressions = f" BY {expressions}" if expressions else ""
2736        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
def setitem_sql(self, expression: sqlglot.expressions.ddl.SetItem) -> str:
2738    def setitem_sql(self, expression: exp.SetItem) -> str:
2739        kind = self.sql(expression, "kind")
2740        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2741            kind = ""
2742        else:
2743            kind = f"{kind} " if kind else ""
2744        this = self.sql(expression, "this")
2745        expressions = self.expressions(expression)
2746        collate = self.sql(expression, "collate")
2747        collate = f" COLLATE {collate}" if collate else ""
2748        global_ = "GLOBAL " if expression.args.get("global_") else ""
2749        return f"{global_}{kind}{this}{expressions}{collate}"
def set_sql(self, expression: sqlglot.expressions.ddl.Set) -> str:
2751    def set_sql(self, expression: exp.Set) -> str:
2752        expressions = f" {self.expressions(expression, flat=True)}"
2753        tag = " TAG" if expression.args.get("tag") else ""
2754        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
def queryband_sql(self, expression: sqlglot.expressions.query.QueryBand) -> str:
2756    def queryband_sql(self, expression: exp.QueryBand) -> str:
2757        this = self.sql(expression, "this")
2758        update = " UPDATE" if expression.args.get("update") else ""
2759        scope = self.sql(expression, "scope")
2760        scope = f" FOR {scope}" if scope else ""
2761
2762        return f"QUERY_BAND = {this}{update}{scope}"
def pragma_sql(self, expression: sqlglot.expressions.ddl.Pragma) -> str:
2764    def pragma_sql(self, expression: exp.Pragma) -> str:
2765        return f"PRAGMA {self.sql(expression, 'this')}"
def lock_sql(self, expression: sqlglot.expressions.query.Lock) -> str:
2767    def lock_sql(self, expression: exp.Lock) -> str:
2768        if not self.LOCKING_READS_SUPPORTED:
2769            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2770            return ""
2771
2772        update = expression.args["update"]
2773        key = expression.args.get("key")
2774        if update:
2775            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2776        else:
2777            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2778        expressions = self.expressions(expression, flat=True)
2779        expressions = f" OF {expressions}" if expressions else ""
2780        wait = expression.args.get("wait")
2781
2782        if wait is not None:
2783            if isinstance(wait, exp.Literal):
2784                wait = f" WAIT {self.sql(wait)}"
2785            else:
2786                wait = " NOWAIT" if wait else " SKIP LOCKED"
2787
2788        return f"{lock_type}{expressions}{wait or ''}"
def literal_sql(self, expression: sqlglot.expressions.core.Literal) -> str:
2790    def literal_sql(self, expression: exp.Literal) -> str:
2791        text = expression.this or ""
2792        if expression.is_string:
2793            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2794        return text
def escape_str( self, text: str, escape_backslash: bool = True, delimiter: str | None = None, escaped_delimiter: str | None = None, is_byte_string: bool = False) -> str:
2796    def escape_str(
2797        self,
2798        text: str,
2799        escape_backslash: bool = True,
2800        delimiter: str | None = None,
2801        escaped_delimiter: str | None = None,
2802        is_byte_string: bool = False,
2803    ) -> str:
2804        if is_byte_string:
2805            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
2806        else:
2807            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
2808
2809        if supports_escape_sequences:
2810            text = "".join(
2811                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
2812                for ch in text
2813            )
2814
2815        delimiter = delimiter or self.dialect.QUOTE_END
2816        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2817
2818        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
def loaddata_sql(self, expression: sqlglot.expressions.dml.LoadData) -> str:
2820    def loaddata_sql(self, expression: exp.LoadData) -> str:
2821        is_overwrite = expression.args.get("overwrite")
2822        overwrite = " OVERWRITE" if is_overwrite else ""
2823        this = self.sql(expression, "this")
2824
2825        files = expression.args.get("files")
2826        if files:
2827            files_sql = self.expressions(files, flat=True)
2828            files_sql = f"FILES{self.wrap(files_sql)}"
2829            this = f" {this}" if is_overwrite else f" INTO TABLE {this}"
2830            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
2831
2832        local = " LOCAL" if expression.args.get("local") else ""
2833        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2834        this = f" INTO TABLE {this}"
2835        partition = self.sql(expression, "partition")
2836        partition = f" {partition}" if partition else ""
2837        input_format = self.sql(expression, "input_format")
2838        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2839        serde = self.sql(expression, "serde")
2840        serde = f" SERDE {serde}" if serde else ""
2841        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
def null_sql(self, *_) -> str:
2843    def null_sql(self, *_) -> str:
2844        return "NULL"
def boolean_sql(self, expression: sqlglot.expressions.core.Boolean) -> str:
2846    def boolean_sql(self, expression: exp.Boolean) -> str:
2847        return "TRUE" if expression.this else "FALSE"
def booland_sql(self, expression: sqlglot.expressions.math.Booland) -> str:
2849    def booland_sql(self, expression: exp.Booland) -> str:
2850        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
def boolor_sql(self, expression: sqlglot.expressions.math.Boolor) -> str:
2852    def boolor_sql(self, expression: exp.Boolor) -> str:
2853        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
def order_sql( self, expression: sqlglot.expressions.query.Order, flat: bool = False) -> str:
2855    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2856        this = self.sql(expression, "this")
2857        this = f"{this} " if this else this
2858        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2859        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:
2861    def withfill_sql(self, expression: exp.WithFill) -> str:
2862        from_sql = self.sql(expression, "from_")
2863        from_sql = f" FROM {from_sql}" if from_sql else ""
2864        to_sql = self.sql(expression, "to")
2865        to_sql = f" TO {to_sql}" if to_sql else ""
2866        step_sql = self.sql(expression, "step")
2867        step_sql = f" STEP {step_sql}" if step_sql else ""
2868        interpolated_values = [
2869            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2870            if isinstance(e, exp.Alias)
2871            else self.sql(e, "this")
2872            for e in expression.args.get("interpolate") or []
2873        ]
2874        interpolate = (
2875            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2876        )
2877        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
def cluster_sql(self, expression: sqlglot.expressions.query.Cluster) -> str:
2879    def cluster_sql(self, expression: exp.Cluster) -> str:
2880        return self.op_expressions("CLUSTER BY", expression)
def distribute_sql(self, expression: sqlglot.expressions.query.Distribute) -> str:
2882    def distribute_sql(self, expression: exp.Distribute) -> str:
2883        return self.op_expressions("DISTRIBUTE BY", expression)
def sort_sql(self, expression: sqlglot.expressions.query.Sort) -> str:
2885    def sort_sql(self, expression: exp.Sort) -> str:
2886        return self.op_expressions("SORT BY", expression)
def ordered_sql(self, expression: sqlglot.expressions.core.Ordered) -> str:
2888    def ordered_sql(self, expression: exp.Ordered) -> str:
2889        desc = expression.args.get("desc")
2890        asc = not desc
2891
2892        nulls_first = expression.args.get("nulls_first")
2893        nulls_last = not nulls_first
2894        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2895        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2896        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2897
2898        this = self.sql(expression, "this")
2899
2900        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2901        nulls_sort_change = ""
2902        if nulls_first and (
2903            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2904        ):
2905            nulls_sort_change = " NULLS FIRST"
2906        elif (
2907            nulls_last
2908            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2909            and not nulls_are_last
2910        ):
2911            nulls_sort_change = " NULLS LAST"
2912
2913        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2914        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2915            window = expression.find_ancestor(exp.Window, exp.Select)
2916
2917            if isinstance(window, exp.Window):
2918                window_this = window.this
2919                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
2920                    window_this = window_this.this
2921                spec = window.args.get("spec")
2922            else:
2923                window_this = None
2924                spec = None
2925
2926            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
2927            # without a spec or with a ROWS spec, but not with RANGE
2928            if not (
2929                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
2930                and (not spec or spec.text("kind").upper() == "ROWS")
2931            ):
2932                if window_this and spec:
2933                    self.unsupported(
2934                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
2935                    )
2936                    nulls_sort_change = ""
2937                elif self.NULL_ORDERING_SUPPORTED is False and (
2938                    (asc and nulls_sort_change == " NULLS LAST")
2939                    or (desc and nulls_sort_change == " NULLS FIRST")
2940                ):
2941                    # BigQuery does not allow these ordering/nulls combinations when used under
2942                    # an aggregation func or under a window containing one
2943                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2944
2945                    if isinstance(ancestor, exp.Window):
2946                        ancestor = ancestor.this
2947                    if isinstance(ancestor, exp.AggFunc):
2948                        self.unsupported(
2949                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
2950                        )
2951                        nulls_sort_change = ""
2952                elif self.NULL_ORDERING_SUPPORTED is None:
2953                    if expression.this.is_int:
2954                        self.unsupported(
2955                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2956                        )
2957                    elif not isinstance(expression.this, exp.Rand):
2958                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2959                        this = (
2960                            f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2961                        )
2962                    nulls_sort_change = ""
2963
2964        with_fill = self.sql(expression, "with_fill")
2965        with_fill = f" {with_fill}" if with_fill else ""
2966
2967        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
2969    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2970        window_frame = self.sql(expression, "window_frame")
2971        window_frame = f"{window_frame} " if window_frame else ""
2972
2973        this = self.sql(expression, "this")
2974
2975        return f"{window_frame}{this}"
def matchrecognize_sql(self, expression: sqlglot.expressions.query.MatchRecognize) -> str:
2977    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2978        partition = self.partition_by_sql(expression)
2979        order = self.sql(expression, "order")
2980        measures = self.expressions(expression, key="measures")
2981        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2982        rows = self.sql(expression, "rows")
2983        rows = self.seg(rows) if rows else ""
2984        after = self.sql(expression, "after")
2985        after = self.seg(after) if after else ""
2986        pattern = self.sql(expression, "pattern")
2987        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2988        definition_sqls = [
2989            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2990            for definition in expression.args.get("define", [])
2991        ]
2992        definitions = self.expressions(sqls=definition_sqls)
2993        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2994        body = "".join(
2995            (
2996                partition,
2997                order,
2998                measures,
2999                rows,
3000                after,
3001                pattern,
3002                define,
3003            )
3004        )
3005        alias = self.sql(expression, "alias")
3006        alias = f" {alias}" if alias else ""
3007        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
def query_modifiers(self, expression: sqlglot.expressions.core.Expr, *sqls: str) -> str:
3009    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3010        limit = expression.args.get("limit")
3011
3012        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3013            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
3014        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
3015            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3016
3017        return csv(
3018            *sqls,
3019            *[self.sql(join) for join in expression.args.get("joins") or []],
3020            self.sql(expression, "match"),
3021            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3022            self.sql(expression, "prewhere"),
3023            self.sql(expression, "where"),
3024            self.sql(expression, "connect"),
3025            self.sql(expression, "group"),
3026            self.sql(expression, "having"),
3027            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3028            self.sql(expression, "order"),
3029            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3030            *self.after_limit_modifiers(expression),
3031            self.options_modifier(expression),
3032            self.for_modifiers(expression),
3033            sep="",
3034        )
def options_modifier(self, expression: sqlglot.expressions.core.Expr) -> str:
3036    def options_modifier(self, expression: exp.Expr) -> str:
3037        options = self.expressions(expression, key="options")
3038        return f" {options}" if options else ""
def for_modifiers(self, expression: sqlglot.expressions.core.Expr) -> str:
3040    def for_modifiers(self, expression: exp.Expr) -> str:
3041        for_modifiers = self.expressions(expression, key="for_")
3042        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
def queryoption_sql(self, expression: sqlglot.expressions.query.QueryOption) -> str:
3044    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3045        self.unsupported("Unsupported query option.")
3046        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]:
3048    def offset_limit_modifiers(
3049        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3050    ) -> list[str]:
3051        return [
3052            self.sql(expression, "offset") if fetch else self.sql(limit),
3053            self.sql(limit) if fetch else self.sql(expression, "offset"),
3054        ]
def after_limit_modifiers(self, expression: sqlglot.expressions.core.Expr) -> list[str]:
3056    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3057        locks = self.expressions(expression, key="locks", sep=" ")
3058        locks = f" {locks}" if locks else ""
3059        return [locks, self.sql(expression, "sample")]
def select_sql(self, expression: sqlglot.expressions.query.Select) -> str:
3061    def select_sql(self, expression: exp.Select) -> str:
3062        into = expression.args.get("into")
3063        if not self.SUPPORTS_SELECT_INTO and into:
3064            into.pop()
3065
3066        hint = self.sql(expression, "hint")
3067        distinct = self.sql(expression, "distinct")
3068        distinct = f" {distinct}" if distinct else ""
3069        kind = self.sql(expression, "kind")
3070
3071        limit = expression.args.get("limit")
3072        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3073            top = self.limit_sql(limit, top=True)
3074            limit.pop()
3075        else:
3076            top = ""
3077
3078        expressions = self.expressions(expression)
3079
3080        if kind:
3081            if kind in self.SELECT_KINDS:
3082                kind = f" AS {kind}"
3083            else:
3084                if kind == "STRUCT":
3085                    expressions = self.expressions(
3086                        sqls=[
3087                            self.sql(
3088                                exp.Struct(
3089                                    expressions=[
3090                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3091                                        if isinstance(e, exp.Alias)
3092                                        else e
3093                                        for e in expression.expressions
3094                                    ]
3095                                )
3096                            )
3097                        ]
3098                    )
3099                kind = ""
3100
3101        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3102        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3103
3104        exclude = expression.args.get("exclude")
3105
3106        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3107            exclude_sql = self.expressions(sqls=exclude, flat=True)
3108            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3109
3110        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3111        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3112        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3113        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3114        sql = self.query_modifiers(
3115            expression,
3116            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3117            self.sql(expression, "into", comment=False),
3118            self.sql(expression, "from_", comment=False),
3119        )
3120
3121        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3122        if expression.args.get("with_"):
3123            sql = self.maybe_comment(sql, expression)
3124            expression.pop_comments()
3125
3126        sql = self.prepend_ctes(expression, sql)
3127
3128        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3129            expression.set("exclude", None)
3130            subquery = expression.subquery(copy=False)
3131            star = exp.Star(except_=exclude)
3132            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3133
3134        if not self.SUPPORTS_SELECT_INTO and into:
3135            if into.args.get("temporary"):
3136                table_kind = " TEMPORARY"
3137            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3138                table_kind = " UNLOGGED"
3139            else:
3140                table_kind = ""
3141            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3142
3143        return sql
def schema_sql(self, expression: sqlglot.expressions.query.Schema) -> str:
3145    def schema_sql(self, expression: exp.Schema) -> str:
3146        this = self.sql(expression, "this")
3147        sql = self.schema_columns_sql(expression)
3148        return f"{this} {sql}" if this and sql else this or sql
def schema_columns_sql(self, expression: sqlglot.expressions.core.Expr) -> str:
3150    def schema_columns_sql(self, expression: exp.Expr) -> str:
3151        if expression.expressions:
3152            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3153        return ""
def star_sql(self, expression: sqlglot.expressions.core.Star) -> str:
3155    def star_sql(self, expression: exp.Star) -> str:
3156        except_ = self.expressions(expression, key="except_", flat=True)
3157        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3158        replace = self.expressions(expression, key="replace", flat=True)
3159        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3160        rename = self.expressions(expression, key="rename", flat=True)
3161        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3162        return f"*{except_}{replace}{rename}"
def parameter_sql(self, expression: sqlglot.expressions.core.Parameter) -> str:
3164    def parameter_sql(self, expression: exp.Parameter) -> str:
3165        this = self.sql(expression, "this")
3166        return f"{self.PARAMETER_TOKEN}{this}"
def sessionparameter_sql(self, expression: sqlglot.expressions.core.SessionParameter) -> str:
3168    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3169        this = self.sql(expression, "this")
3170        kind = expression.text("kind")
3171        if kind:
3172            kind = f"{kind}."
3173        return f"@@{kind}{this}"
def placeholder_sql(self, expression: sqlglot.expressions.core.Placeholder) -> str:
3175    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3176        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:
3178    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3179        alias = self.sql(expression, "alias")
3180        alias = f"{sep}{alias}" if alias else ""
3181        sample = self.sql(expression, "sample")
3182        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3183            alias = f"{sample}{alias}"
3184
3185            # Set to None so it's not generated again by self.query_modifiers()
3186            expression.set("sample", None)
3187
3188        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3189        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3190        return self.prepend_ctes(expression, sql)
def qualify_sql(self, expression: sqlglot.expressions.query.Qualify) -> str:
3192    def qualify_sql(self, expression: exp.Qualify) -> str:
3193        this = self.indent(self.sql(expression, "this"))
3194        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
def unnest_sql(self, expression: sqlglot.expressions.array.Unnest) -> str:
3196    def unnest_sql(self, expression: exp.Unnest) -> str:
3197        args = self.expressions(expression, flat=True)
3198
3199        alias = expression.args.get("alias")
3200        offset = expression.args.get("offset")
3201
3202        if self.UNNEST_WITH_ORDINALITY:
3203            if alias and isinstance(offset, exp.Expr):
3204                alias.append("columns", offset)
3205
3206        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3207            columns = alias.columns
3208            alias = self.sql(columns[0]) if columns else ""
3209        else:
3210            alias = self.sql(alias)
3211
3212        alias = f" AS {alias}" if alias else alias
3213        if self.UNNEST_WITH_ORDINALITY:
3214            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3215        else:
3216            if isinstance(offset, exp.Expr):
3217                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3218            elif offset:
3219                suffix = f"{alias} WITH OFFSET"
3220            else:
3221                suffix = alias
3222
3223        return f"UNNEST({args}){suffix}"
def prewhere_sql(self, expression: sqlglot.expressions.query.PreWhere) -> str:
3225    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3226        return ""
def where_sql(self, expression: sqlglot.expressions.query.Where) -> str:
3228    def where_sql(self, expression: exp.Where) -> str:
3229        this = self.indent(self.sql(expression, "this"))
3230        return f"{self.seg('WHERE')}{self.sep()}{this}"
def window_sql(self, expression: sqlglot.expressions.query.Window) -> str:
3232    def window_sql(self, expression: exp.Window) -> str:
3233        this = self.sql(expression, "this")
3234        partition = self.partition_by_sql(expression)
3235        order = expression.args.get("order")
3236        order = self.order_sql(order, flat=True) if order else ""
3237        spec = self.sql(expression, "spec")
3238        alias = self.sql(expression, "alias")
3239        over = self.sql(expression, "over") or "OVER"
3240
3241        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3242
3243        first = expression.args.get("first")
3244        if first is None:
3245            first = ""
3246        else:
3247            first = "FIRST" if first else "LAST"
3248
3249        if not partition and not order and not spec and alias:
3250            return f"{this} {alias}"
3251
3252        args = self.format_args(
3253            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3254        )
3255        return f"{this} ({args})"
def partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3257    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3258        partition = self.expressions(expression, key="partition_by", flat=True)
3259        return f"PARTITION BY {partition}" if partition else ""
def windowspec_sql(self, expression: sqlglot.expressions.query.WindowSpec) -> str:
3261    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3262        kind = self.sql(expression, "kind")
3263        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3264        end = (
3265            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3266            or "CURRENT ROW"
3267        )
3268
3269        window_spec = f"{kind} BETWEEN {start} AND {end}"
3270
3271        exclude = self.sql(expression, "exclude")
3272        if exclude:
3273            if self.SUPPORTS_WINDOW_EXCLUDE:
3274                window_spec += f" EXCLUDE {exclude}"
3275            else:
3276                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3277
3278        return window_spec
def withingroup_sql(self, expression: sqlglot.expressions.core.WithinGroup) -> str:
3280    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3281        this = self.sql(expression, "this")
3282        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3283        return f"{this} WITHIN GROUP ({expression_sql})"
def between_sql(self, expression: sqlglot.expressions.core.Between) -> str:
3285    def between_sql(self, expression: exp.Between) -> str:
3286        this = self.sql(expression, "this")
3287        low = self.sql(expression, "low")
3288        high = self.sql(expression, "high")
3289        symmetric = expression.args.get("symmetric")
3290
3291        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3292            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3293
3294        flag = (
3295            " SYMMETRIC"
3296            if symmetric
3297            else " ASYMMETRIC"
3298            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3299            else ""  # silently drop ASYMMETRIC – semantics identical
3300        )
3301        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]:
3303    def bracket_offset_expressions(
3304        self, expression: exp.Bracket, index_offset: int | None = None
3305    ) -> list[exp.Expr]:
3306        if expression.args.get("json_access"):
3307            return expression.expressions
3308
3309        return apply_index_offset(
3310            expression.this,
3311            expression.expressions,
3312            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3313            dialect=self.dialect,
3314        )
def bracket_sql(self, expression: sqlglot.expressions.core.Bracket) -> str:
3316    def bracket_sql(self, expression: exp.Bracket) -> str:
3317        expressions = self.bracket_offset_expressions(expression)
3318        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3319        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
def all_sql(self, expression: sqlglot.expressions.core.All) -> str:
3321    def all_sql(self, expression: exp.All) -> str:
3322        this = self.sql(expression, "this")
3323        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3324            this = self.wrap(this)
3325        return f"ALL {this}"
def any_sql(self, expression: sqlglot.expressions.core.Any) -> str:
3327    def any_sql(self, expression: exp.Any) -> str:
3328        this = self.sql(expression, "this")
3329        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3330            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3331                this = self.wrap(this)
3332            return f"ANY{this}"
3333        return f"ANY {this}"
def exists_sql(self, expression: sqlglot.expressions.functions.Exists) -> str:
3335    def exists_sql(self, expression: exp.Exists) -> str:
3336        return f"EXISTS{self.wrap(expression)}"
def case_sql(self, expression: sqlglot.expressions.functions.Case) -> str:
3338    def case_sql(self, expression: exp.Case) -> str:
3339        this = self.sql(expression, "this")
3340        statements = [f"CASE {this}" if this else "CASE"]
3341
3342        for e in expression.args["ifs"]:
3343            statements.append(f"WHEN {self.sql(e, 'this')}")
3344            statements.append(f"THEN {self.sql(e, 'true')}")
3345
3346        default = self.sql(expression, "default")
3347
3348        if default:
3349            statements.append(f"ELSE {default}")
3350
3351        statements.append("END")
3352
3353        if self.pretty and self.too_wide(statements):
3354            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3355
3356        return " ".join(statements)
def constraint_sql(self, expression: sqlglot.expressions.constraints.Constraint) -> str:
3358    def constraint_sql(self, expression: exp.Constraint) -> str:
3359        this = self.sql(expression, "this")
3360        expressions = self.expressions(expression, flat=True)
3361        return f"CONSTRAINT {this} {expressions}"
def nextvaluefor_sql(self, expression: sqlglot.expressions.ddl.NextValueFor) -> str:
3363    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3364        order = expression.args.get("order")
3365        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3366        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
def extract_sql(self, expression: sqlglot.expressions.temporal.Extract) -> str:
3368    def extract_sql(self, expression: exp.Extract) -> str:
3369        import sqlglot.dialects.dialect
3370
3371        this = (
3372            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3373            if self.NORMALIZE_EXTRACT_DATE_PARTS
3374            else expression.this
3375        )
3376        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3377        expression_sql = self.sql(expression, "expression")
3378
3379        return f"EXTRACT({this_sql} FROM {expression_sql})"
def trim_sql(self, expression: sqlglot.expressions.string.Trim) -> str:
3381    def trim_sql(self, expression: exp.Trim) -> str:
3382        trim_type = self.sql(expression, "position")
3383
3384        if trim_type == "LEADING":
3385            func_name = "LTRIM"
3386        elif trim_type == "TRAILING":
3387            func_name = "RTRIM"
3388        else:
3389            func_name = "TRIM"
3390
3391        return self.func(func_name, expression.this, expression.expression)
def convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3393    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3394        args = expression.expressions
3395        if isinstance(expression, exp.ConcatWs):
3396            args = args[1:]  # Skip the delimiter
3397
3398        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3399            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3400
3401        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
3402
3403            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3404                if not e.type:
3405                    import sqlglot.optimizer.annotate_types
3406
3407                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3408
3409                if e.is_string or e.is_type(exp.DType.ARRAY):
3410                    return e
3411
3412                return exp.func("coalesce", e, exp.Literal.string(""))
3413
3414            args = [_wrap_with_coalesce(e) for e in args]
3415
3416        return args
def concat_sql(self, expression: sqlglot.expressions.string.Concat) -> str:
3418    def concat_sql(self, expression: exp.Concat) -> str:
3419        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3420            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3421            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3422            # instead of coalescing them to empty string.
3423            import sqlglot.dialects.dialect
3424
3425            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3426
3427        expressions = self.convert_concat_args(expression)
3428
3429        # Some dialects don't allow a single-argument CONCAT call
3430        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3431            return self.sql(expressions[0])
3432
3433        return self.func("CONCAT", *expressions)
def concatws_sql(self, expression: sqlglot.expressions.string.ConcatWs) -> str:
3435    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3436        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3437            # Dialect's CONCAT_WS function coalesces NULLs to empty strings, but the expression does not.
3438            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3439            all_args = expression.expressions
3440            expression.set("coalesce", True)
3441            return self.sql(
3442                exp.case()
3443                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3444                .else_(expression)
3445            )
3446
3447        return self.func(
3448            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3449        )
def check_sql(self, expression: sqlglot.expressions.core.Check) -> str:
3451    def check_sql(self, expression: exp.Check) -> str:
3452        this = self.sql(expression, key="this")
3453        return f"CHECK ({this})"
def foreignkey_sql(self, expression: sqlglot.expressions.constraints.ForeignKey) -> str:
3455    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3456        expressions = self.expressions(expression, flat=True)
3457        expressions = f" ({expressions})" if expressions else ""
3458        reference = self.sql(expression, "reference")
3459        reference = f" {reference}" if reference else ""
3460        delete = self.sql(expression, "delete")
3461        delete = f" ON DELETE {delete}" if delete else ""
3462        update = self.sql(expression, "update")
3463        update = f" ON UPDATE {update}" if update else ""
3464        options = self.expressions(expression, key="options", flat=True, sep=" ")
3465        options = f" {options}" if options else ""
3466        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
def primarykey_sql(self, expression: sqlglot.expressions.constraints.PrimaryKey) -> str:
3468    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3469        this = self.sql(expression, "this")
3470        this = f" {this}" if this else ""
3471        expressions = self.expressions(expression, flat=True)
3472        include = self.sql(expression, "include")
3473        options = self.expressions(expression, key="options", flat=True, sep=" ")
3474        options = f" {options}" if options else ""
3475        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
def if_sql(self, expression: sqlglot.expressions.functions.If) -> str:
3477    def if_sql(self, expression: exp.If) -> str:
3478        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
def matchagainst_sql(self, expression: sqlglot.expressions.string.MatchAgainst) -> str:
3480    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3481        if self.MATCH_AGAINST_TABLE_PREFIX:
3482            expressions = []
3483            for expr in expression.expressions:
3484                if isinstance(expr, exp.Table):
3485                    expressions.append(f"TABLE {self.sql(expr)}")
3486                else:
3487                    expressions.append(expr)
3488        else:
3489            expressions = expression.expressions
3490
3491        modifier = expression.args.get("modifier")
3492        modifier = f" {modifier}" if modifier else ""
3493        return (
3494            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3495        )
def jsonkeyvalue_sql(self, expression: sqlglot.expressions.query.JSONKeyValue) -> str:
3497    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3498        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:
3500    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3501        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3502
3503        if expression.args.get("escape"):
3504            path = self.escape_str(path)
3505
3506        if self.QUOTE_JSON_PATH:
3507            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3508
3509        return path
def json_path_part( self, expression: int | str | sqlglot.expressions.query.JSONPathPart) -> str:
3511    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3512        if isinstance(expression, exp.JSONPathPart):
3513            transform = self.TRANSFORMS.get(expression.__class__)
3514            if not callable(transform):
3515                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3516                return ""
3517
3518            return transform(self, expression)
3519
3520        if isinstance(expression, int):
3521            return str(expression)
3522
3523        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3524            escaped = expression.replace("'", "\\'")
3525            escaped = f"\\'{expression}\\'"
3526        else:
3527            escaped = expression.replace('"', '\\"')
3528            escaped = f'"{escaped}"'
3529
3530        return escaped
def formatjson_sql(self, expression: sqlglot.expressions.query.FormatJson) -> str:
3532    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3533        return f"{self.sql(expression, 'this')} FORMAT JSON"
def formatphrase_sql(self, expression: sqlglot.expressions.core.FormatPhrase) -> str:
3535    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3536        # Output the Teradata column FORMAT override.
3537        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3538        this = self.sql(expression, "this")
3539        fmt = self.sql(expression, "format")
3540        return f"{this} (FORMAT {fmt})"
def jsonarray_sql(self, expression: sqlglot.expressions.json.JSONArray) -> str:
3568    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3569        null_handling = expression.args.get("null_handling")
3570        null_handling = f" {null_handling}" if null_handling else ""
3571        return_type = self.sql(expression, "return_type")
3572        return_type = f" RETURNING {return_type}" if return_type else ""
3573        strict = " STRICT" if expression.args.get("strict") else ""
3574        return self.func(
3575            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3576        )
def jsonarrayagg_sql(self, expression: sqlglot.expressions.json.JSONArrayAgg) -> str:
3578    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3579        this = self.sql(expression, "this")
3580        order = self.sql(expression, "order")
3581        null_handling = expression.args.get("null_handling")
3582        null_handling = f" {null_handling}" if null_handling else ""
3583        return_type = self.sql(expression, "return_type")
3584        return_type = f" RETURNING {return_type}" if return_type else ""
3585        strict = " STRICT" if expression.args.get("strict") else ""
3586        return self.func(
3587            "JSON_ARRAYAGG",
3588            this,
3589            suffix=f"{order}{null_handling}{return_type}{strict})",
3590        )
def jsoncolumndef_sql(self, expression: sqlglot.expressions.query.JSONColumnDef) -> str:
3592    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3593        path = self.sql(expression, "path")
3594        path = f" PATH {path}" if path else ""
3595        nested_schema = self.sql(expression, "nested_schema")
3596
3597        if nested_schema:
3598            return f"NESTED{path} {nested_schema}"
3599
3600        this = self.sql(expression, "this")
3601        kind = self.sql(expression, "kind")
3602        kind = f" {kind}" if kind else ""
3603
3604        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3605        return f"{this}{kind}{path}{ordinality}"
def jsonschema_sql(self, expression: sqlglot.expressions.query.JSONSchema) -> str:
3607    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3608        return self.func("COLUMNS", *expression.expressions)
def jsontable_sql(self, expression: sqlglot.expressions.json.JSONTable) -> str:
3610    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3611        this = self.sql(expression, "this")
3612        path = self.sql(expression, "path")
3613        path = f", {path}" if path else ""
3614        error_handling = expression.args.get("error_handling")
3615        error_handling = f" {error_handling}" if error_handling else ""
3616        empty_handling = expression.args.get("empty_handling")
3617        empty_handling = f" {empty_handling}" if empty_handling else ""
3618        schema = self.sql(expression, "schema")
3619        return self.func(
3620            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3621        )
def openjsoncolumndef_sql(self, expression: sqlglot.expressions.query.OpenJSONColumnDef) -> str:
3623    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3624        this = self.sql(expression, "this")
3625        kind = self.sql(expression, "kind")
3626        path = self.sql(expression, "path")
3627        path = f" {path}" if path else ""
3628        as_json = " AS JSON" if expression.args.get("as_json") else ""
3629        return f"{this} {kind}{path}{as_json}"
def openjson_sql(self, expression: sqlglot.expressions.json.OpenJSON) -> str:
3631    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3632        this = self.sql(expression, "this")
3633        path = self.sql(expression, "path")
3634        path = f", {path}" if path else ""
3635        expressions = self.expressions(expression)
3636        with_ = (
3637            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3638            if expressions
3639            else ""
3640        )
3641        return f"OPENJSON({this}{path}){with_}"
def in_sql(self, expression: sqlglot.expressions.core.In) -> str:
3643    def in_sql(self, expression: exp.In) -> str:
3644        query = expression.args.get("query")
3645        unnest = expression.args.get("unnest")
3646        field = expression.args.get("field")
3647        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3648
3649        if query:
3650            in_sql = self.sql(query)
3651        elif unnest:
3652            in_sql = self.in_unnest_op(unnest)
3653        elif field:
3654            in_sql = self.sql(field)
3655        else:
3656            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3657
3658        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
def in_unnest_op(self, unnest: sqlglot.expressions.array.Unnest) -> str:
3660    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3661        return f"(SELECT {self.sql(unnest)})"
def interval_sql(self, expression: sqlglot.expressions.datatypes.Interval) -> str:
3663    def interval_sql(self, expression: exp.Interval) -> str:
3664        unit_expression = expression.args.get("unit")
3665        unit = self.sql(unit_expression) if unit_expression else ""
3666        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3667            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3668        unit = f" {unit}" if unit else ""
3669
3670        if self.SINGLE_STRING_INTERVAL:
3671            this = expression.this.name if expression.this else ""
3672            if this:
3673                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3674                    return f"INTERVAL '{this}'{unit}"
3675                return f"INTERVAL '{this}{unit}'"
3676            return f"INTERVAL{unit}"
3677
3678        this = self.sql(expression, "this")
3679        if this:
3680            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3681            this = f" {this}" if unwrapped else f" ({this})"
3682
3683        return f"INTERVAL{this}{unit}"
def return_sql(self, expression: sqlglot.expressions.query.Return) -> str:
3685    def return_sql(self, expression: exp.Return) -> str:
3686        return f"RETURN {self.sql(expression, 'this')}"
def reference_sql(self, expression: sqlglot.expressions.constraints.Reference) -> str:
3688    def reference_sql(self, expression: exp.Reference) -> str:
3689        this = self.sql(expression, "this")
3690        expressions = self.expressions(expression, flat=True)
3691        expressions = f"({expressions})" if expressions else ""
3692        options = self.expressions(expression, key="options", flat=True, sep=" ")
3693        options = f" {options}" if options else ""
3694        return f"REFERENCES {this}{expressions}{options}"
def anonymous_sql(self, expression: sqlglot.expressions.core.Anonymous) -> str:
3696    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3697        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3698        parent = expression.parent
3699        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3700
3701        return self.func(
3702            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3703        )
def paren_sql(self, expression: sqlglot.expressions.core.Paren) -> str:
3705    def paren_sql(self, expression: exp.Paren) -> str:
3706        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3707        return f"({sql}{self.seg(')', sep='')}"
def neg_sql(self, expression: sqlglot.expressions.core.Neg) -> str:
3709    def neg_sql(self, expression: exp.Neg) -> str:
3710        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3711        this_sql = self.sql(expression, "this")
3712        sep = " " if this_sql[0] == "-" else ""
3713        return f"-{sep}{this_sql}"
def not_sql(self, expression: sqlglot.expressions.core.Not) -> str:
3715    def not_sql(self, expression: exp.Not) -> str:
3716        return f"NOT {self.sql(expression, 'this')}"
def alias_sql(self, expression: sqlglot.expressions.core.Alias) -> str:
3718    def alias_sql(self, expression: exp.Alias) -> str:
3719        alias = self.sql(expression, "alias")
3720        alias = f" AS {alias}" if alias else ""
3721        return f"{self.sql(expression, 'this')}{alias}"
def pivotalias_sql(self, expression: sqlglot.expressions.core.PivotAlias) -> str:
3723    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3724        alias = expression.args["alias"]
3725
3726        parent = expression.parent
3727        pivot = parent and parent.parent
3728
3729        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3730            identifier_alias = isinstance(alias, exp.Identifier)
3731            literal_alias = isinstance(alias, exp.Literal)
3732
3733            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3734                alias.replace(exp.Literal.string(alias.output_name))
3735            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3736                alias.replace(exp.to_identifier(alias.output_name))
3737
3738        return self.alias_sql(expression)
def aliases_sql(self, expression: sqlglot.expressions.core.Aliases) -> str:
3740    def aliases_sql(self, expression: exp.Aliases) -> str:
3741        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
def atindex_sql(self, expression: sqlglot.expressions.core.AtIndex) -> str:
3743    def atindex_sql(self, expression: exp.AtIndex) -> str:
3744        this = self.sql(expression, "this")
3745        index = self.sql(expression, "expression")
3746        return f"{this} AT {index}"
def attimezone_sql(self, expression: sqlglot.expressions.core.AtTimeZone) -> str:
3748    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3749        this = self.sql(expression, "this")
3750        zone = self.sql(expression, "zone")
3751        return f"{this} AT TIME ZONE {zone}"
def fromtimezone_sql(self, expression: sqlglot.expressions.core.FromTimeZone) -> str:
3753    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3754        this = self.sql(expression, "this")
3755        zone = self.sql(expression, "zone")
3756        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
def add_sql(self, expression: sqlglot.expressions.core.Add) -> str:
3758    def add_sql(self, expression: exp.Add) -> str:
3759        return self.binary(expression, "+")
def and_sql( self, expression: sqlglot.expressions.core.And, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
3761    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
3762        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:
3764    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
3765        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:
3767    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
3768        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:
3770    def connector_sql(
3771        self,
3772        expression: exp.Connector,
3773        op: str,
3774        stack: list[str | exp.Expr] | None = None,
3775    ) -> str:
3776        if stack is not None:
3777            if expression.expressions:
3778                stack.append(self.expressions(expression, sep=f" {op} "))
3779            else:
3780                stack.append(expression.right)
3781                if expression.comments and self.comments:
3782                    for comment in expression.comments:
3783                        if comment:
3784                            op += f" /*{self.sanitize_comment(comment)}*/"
3785                stack.extend((op, expression.left))
3786            return op
3787
3788        stack = [expression]
3789        sqls: list[str] = []
3790        ops = set()
3791
3792        while stack:
3793            node = stack.pop()
3794            if isinstance(node, exp.Connector):
3795                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3796            else:
3797                sql = self.sql(node)
3798                if sqls and sqls[-1] in ops:
3799                    sqls[-1] += f" {sql}"
3800                else:
3801                    sqls.append(sql)
3802
3803        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3804        return sep.join(sqls)
def bitwiseand_sql(self, expression: sqlglot.expressions.core.BitwiseAnd) -> str:
3806    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3807        return self.binary(expression, "&")
def bitwiseleftshift_sql(self, expression: sqlglot.expressions.core.BitwiseLeftShift) -> str:
3809    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3810        return self.binary(expression, "<<")
def bitwisenot_sql(self, expression: sqlglot.expressions.core.BitwiseNot) -> str:
3812    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3813        return f"~{self.sql(expression, 'this')}"
def bitwiseor_sql(self, expression: sqlglot.expressions.core.BitwiseOr) -> str:
3815    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3816        return self.binary(expression, "|")
def bitwiserightshift_sql(self, expression: sqlglot.expressions.core.BitwiseRightShift) -> str:
3818    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3819        return self.binary(expression, ">>")
def bitwisexor_sql(self, expression: sqlglot.expressions.core.BitwiseXor) -> str:
3821    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3822        return self.binary(expression, "^")
def cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
3824    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
3825        format_sql = self.sql(expression, "format")
3826        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3827        to_sql = self.sql(expression, "to")
3828        to_sql = f" {to_sql}" if to_sql else ""
3829        action = self.sql(expression, "action")
3830        action = f" {action}" if action else ""
3831        default = self.sql(expression, "default")
3832        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3833        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:
3836    def strtotime_sql(self, expression: exp.StrToTime) -> str:
3837        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
def currentdate_sql(self, expression: sqlglot.expressions.temporal.CurrentDate) -> str:
3839    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3840        zone = self.sql(expression, "this")
3841        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
def collate_sql(self, expression: sqlglot.expressions.functions.Collate) -> str:
3843    def collate_sql(self, expression: exp.Collate) -> str:
3844        if self.COLLATE_IS_FUNC:
3845            return self.function_fallback_sql(expression)
3846        return self.binary(expression, "COLLATE")
def command_sql(self, expression: sqlglot.expressions.ddl.Command) -> str:
3848    def command_sql(self, expression: exp.Command) -> str:
3849        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
def comment_sql(self, expression: sqlglot.expressions.ddl.Comment) -> str:
3851    def comment_sql(self, expression: exp.Comment) -> str:
3852        this = self.sql(expression, "this")
3853        kind = expression.args["kind"]
3854        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3855        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3856        expression_sql = self.sql(expression, "expression")
3857        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
def mergetreettlaction_sql(self, expression: sqlglot.expressions.ddl.MergeTreeTTLAction) -> str:
3859    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3860        this = self.sql(expression, "this")
3861        delete = " DELETE" if expression.args.get("delete") else ""
3862        recompress = self.sql(expression, "recompress")
3863        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3864        to_disk = self.sql(expression, "to_disk")
3865        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3866        to_volume = self.sql(expression, "to_volume")
3867        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3868        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
def mergetreettl_sql(self, expression: sqlglot.expressions.ddl.MergeTreeTTL) -> str:
3870    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3871        where = self.sql(expression, "where")
3872        group = self.sql(expression, "group")
3873        aggregates = self.expressions(expression, key="aggregates")
3874        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3875
3876        if not (where or group or aggregates) and len(expression.expressions) == 1:
3877            return f"TTL {self.expressions(expression, flat=True)}"
3878
3879        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
def transaction_sql(self, expression: sqlglot.expressions.ddl.Transaction) -> str:
3881    def transaction_sql(self, expression: exp.Transaction) -> str:
3882        modes = self.expressions(expression, key="modes")
3883        modes = f" {modes}" if modes else ""
3884        return f"BEGIN{modes}"
def commit_sql(self, expression: sqlglot.expressions.ddl.Commit) -> str:
3886    def commit_sql(self, expression: exp.Commit) -> str:
3887        chain = expression.args.get("chain")
3888        if chain is not None:
3889            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3890
3891        return f"COMMIT{chain or ''}"
def rollback_sql(self, expression: sqlglot.expressions.ddl.Rollback) -> str:
3893    def rollback_sql(self, expression: exp.Rollback) -> str:
3894        savepoint = expression.args.get("savepoint")
3895        savepoint = f" TO {savepoint}" if savepoint else ""
3896        return f"ROLLBACK{savepoint}"
def altercolumn_sql(self, expression: sqlglot.expressions.ddl.AlterColumn) -> str:
3898    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3899        this = self.sql(expression, "this")
3900
3901        dtype = self.sql(expression, "dtype")
3902        if dtype:
3903            collate = self.sql(expression, "collate")
3904            collate = f" COLLATE {collate}" if collate else ""
3905            using = self.sql(expression, "using")
3906            using = f" USING {using}" if using else ""
3907            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3908            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3909
3910        default = self.sql(expression, "default")
3911        if default:
3912            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3913
3914        comment = self.sql(expression, "comment")
3915        if comment:
3916            return f"ALTER COLUMN {this} COMMENT {comment}"
3917
3918        visible = expression.args.get("visible")
3919        if visible:
3920            return f"ALTER COLUMN {this} SET {visible}"
3921
3922        allow_null = expression.args.get("allow_null")
3923        drop = expression.args.get("drop")
3924
3925        if not drop and not allow_null:
3926            self.unsupported("Unsupported ALTER COLUMN syntax")
3927
3928        if allow_null is not None:
3929            keyword = "DROP" if drop else "SET"
3930            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3931
3932        return f"ALTER COLUMN {this} DROP DEFAULT"
def alterindex_sql(self, expression: sqlglot.expressions.ddl.AlterIndex) -> str:
3934    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3935        this = self.sql(expression, "this")
3936
3937        visible = expression.args.get("visible")
3938        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3939
3940        return f"ALTER INDEX {this} {visible_sql}"
def alterdiststyle_sql(self, expression: sqlglot.expressions.ddl.AlterDistStyle) -> str:
3942    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3943        this = self.sql(expression, "this")
3944        if not isinstance(expression.this, exp.Var):
3945            this = f"KEY DISTKEY {this}"
3946        return f"ALTER DISTSTYLE {this}"
def altersortkey_sql(self, expression: sqlglot.expressions.ddl.AlterSortKey) -> str:
3948    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3949        compound = " COMPOUND" if expression.args.get("compound") else ""
3950        this = self.sql(expression, "this")
3951        expressions = self.expressions(expression, flat=True)
3952        expressions = f"({expressions})" if expressions else ""
3953        return f"ALTER{compound} SORTKEY {this or expressions}"
def alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
3955    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
3956        if not self.RENAME_TABLE_WITH_DB:
3957            # Remove db from tables
3958            expression = expression.transform(
3959                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3960            ).assert_is(exp.AlterRename)
3961        this = self.sql(expression, "this")
3962        to_kw = " TO" if include_to else ""
3963        return f"RENAME{to_kw} {this}"
def renamecolumn_sql(self, expression: sqlglot.expressions.ddl.RenameColumn) -> str:
3965    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3966        exists = " IF EXISTS" if expression.args.get("exists") else ""
3967        old_column = self.sql(expression, "this")
3968        new_column = self.sql(expression, "to")
3969        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
def alterset_sql(self, expression: sqlglot.expressions.ddl.AlterSet) -> str:
3971    def alterset_sql(self, expression: exp.AlterSet) -> str:
3972        exprs = self.expressions(expression, flat=True)
3973        if self.ALTER_SET_WRAPPED:
3974            exprs = f"({exprs})"
3975
3976        return f"SET {exprs}"
def alter_sql(self, expression: sqlglot.expressions.ddl.Alter) -> str:
3978    def alter_sql(self, expression: exp.Alter) -> str:
3979        actions = expression.args["actions"]
3980
3981        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3982            actions[0], exp.ColumnDef
3983        ):
3984            actions_sql = self.expressions(expression, key="actions", flat=True)
3985            actions_sql = f"ADD {actions_sql}"
3986        else:
3987            actions_list = []
3988            for action in actions:
3989                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3990                    action_sql = self.add_column_sql(action)
3991                else:
3992                    action_sql = self.sql(action)
3993                    if isinstance(action, exp.Query):
3994                        action_sql = f"AS {action_sql}"
3995
3996                actions_list.append(action_sql)
3997
3998            actions_sql = self.format_args(*actions_list).lstrip("\n")
3999
4000        iceberg = (
4001            "ICEBERG "
4002            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4003            else ""
4004        )
4005        exists = " IF EXISTS" if expression.args.get("exists") else ""
4006        on_cluster = self.sql(expression, "cluster")
4007        on_cluster = f" {on_cluster}" if on_cluster else ""
4008        only = " ONLY" if expression.args.get("only") else ""
4009        options = self.expressions(expression, key="options")
4010        options = f", {options}" if options else ""
4011        kind = self.sql(expression, "kind")
4012        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4013        check = " WITH CHECK" if expression.args.get("check") else ""
4014        cascade = (
4015            " CASCADE"
4016            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4017            else ""
4018        )
4019        this = self.sql(expression, "this")
4020        this = f" {this}" if this else ""
4021
4022        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
def altersession_sql(self, expression: sqlglot.expressions.ddl.AlterSession) -> str:
4024    def altersession_sql(self, expression: exp.AlterSession) -> str:
4025        items_sql = self.expressions(expression, flat=True)
4026        keyword = "UNSET" if expression.args.get("unset") else "SET"
4027        return f"{keyword} {items_sql}"
def add_column_sql(self, expression: sqlglot.expressions.core.Expr) -> str:
4029    def add_column_sql(self, expression: exp.Expr) -> str:
4030        sql = self.sql(expression)
4031        if isinstance(expression, exp.Schema):
4032            column_text = " COLUMNS"
4033        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4034            column_text = " COLUMN"
4035        else:
4036            column_text = ""
4037
4038        return f"ADD{column_text} {sql}"
def droppartition_sql(self, expression: sqlglot.expressions.query.DropPartition) -> str:
4040    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4041        expressions = self.expressions(expression)
4042        exists = " IF EXISTS " if expression.args.get("exists") else " "
4043        return f"DROP{exists}{expressions}"
def addconstraint_sql(self, expression: sqlglot.expressions.constraints.AddConstraint) -> str:
4045    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4046        return f"ADD {self.expressions(expression, indent=False)}"
def addpartition_sql(self, expression: sqlglot.expressions.query.AddPartition) -> str:
4048    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4049        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4050        location = self.sql(expression, "location")
4051        location = f" {location}" if location else ""
4052        return f"ADD {exists}{self.sql(expression.this)}{location}"
def distinct_sql(self, expression: sqlglot.expressions.core.Distinct) -> str:
4054    def distinct_sql(self, expression: exp.Distinct) -> str:
4055        this = self.expressions(expression, flat=True)
4056
4057        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4058            case = exp.case()
4059            for arg in expression.expressions:
4060                case = case.when(arg.is_(exp.null()), exp.null())
4061            this = self.sql(case.else_(f"({this})"))
4062
4063        this = f" {this}" if this else ""
4064
4065        on = self.sql(expression, "on")
4066        on = f" ON {on}" if on else ""
4067        return f"DISTINCT{this}{on}"
def ignorenulls_sql(self, expression: sqlglot.expressions.core.IgnoreNulls) -> str:
4069    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4070        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
def respectnulls_sql(self, expression: sqlglot.expressions.core.RespectNulls) -> str:
4072    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4073        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
def havingmax_sql(self, expression: sqlglot.expressions.core.HavingMax) -> str:
4075    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4076        this_sql = self.sql(expression, "this")
4077        expression_sql = self.sql(expression, "expression")
4078        kind = "MAX" if expression.args.get("max") else "MIN"
4079        return f"{this_sql} HAVING {kind} {expression_sql}"
def intdiv_sql(self, expression: sqlglot.expressions.core.IntDiv) -> str:
4081    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4082        return self.sql(
4083            exp.Cast(
4084                this=exp.Div(this=expression.this, expression=expression.expression),
4085                to=exp.DataType(this=exp.DType.INT),
4086            )
4087        )
def dpipe_sql(self, expression: sqlglot.expressions.core.DPipe) -> str:
4089    def dpipe_sql(self, expression: exp.DPipe) -> str:
4090        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4091            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4092        return self.binary(expression, "||")
def div_sql(self, expression: sqlglot.expressions.core.Div) -> str:
4094    def div_sql(self, expression: exp.Div) -> str:
4095        l, r = expression.left, expression.right
4096
4097        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4098            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4099
4100        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4101            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4102                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4103
4104        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4105            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4106                return self.sql(
4107                    exp.cast(
4108                        l / r,
4109                        to=exp.DType.BIGINT,
4110                    )
4111                )
4112
4113        return self.binary(expression, "/")
def safedivide_sql(self, expression: sqlglot.expressions.math.SafeDivide) -> str:
4115    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4116        n = exp._wrap(expression.this, exp.Binary)
4117        d = exp._wrap(expression.expression, exp.Binary)
4118        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:
4120    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4121        return self.binary(expression, "OVERLAPS")
def distance_sql(self, expression: sqlglot.expressions.core.Distance) -> str:
4123    def distance_sql(self, expression: exp.Distance) -> str:
4124        return self.binary(expression, "<->")
def dot_sql(self, expression: sqlglot.expressions.core.Dot) -> str:
4126    def dot_sql(self, expression: exp.Dot) -> str:
4127        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
def eq_sql(self, expression: sqlglot.expressions.core.EQ) -> str:
4129    def eq_sql(self, expression: exp.EQ) -> str:
4130        return self.binary(expression, "=")
def propertyeq_sql(self, expression: sqlglot.expressions.core.PropertyEQ) -> str:
4132    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4133        return self.binary(expression, ":=")
def escape_sql(self, expression: sqlglot.expressions.core.Escape) -> str:
4135    def escape_sql(self, expression: exp.Escape) -> str:
4136        this = expression.this
4137        if (
4138            isinstance(this, (exp.Like, exp.ILike))
4139            and isinstance(this.expression, (exp.All, exp.Any))
4140            and not self.SUPPORTS_LIKE_QUANTIFIERS
4141        ):
4142            return self._like_sql(this, escape=expression)
4143        return self.binary(expression, "ESCAPE")
def glob_sql(self, expression: sqlglot.expressions.core.Glob) -> str:
4145    def glob_sql(self, expression: exp.Glob) -> str:
4146        return self.binary(expression, "GLOB")
def gt_sql(self, expression: sqlglot.expressions.core.GT) -> str:
4148    def gt_sql(self, expression: exp.GT) -> str:
4149        return self.binary(expression, ">")
def gte_sql(self, expression: sqlglot.expressions.core.GTE) -> str:
4151    def gte_sql(self, expression: exp.GTE) -> str:
4152        return self.binary(expression, ">=")
def is_sql(self, expression: sqlglot.expressions.core.Is) -> str:
4154    def is_sql(self, expression: exp.Is) -> str:
4155        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4156            return self.sql(
4157                expression.this if expression.expression.this else exp.not_(expression.this)
4158            )
4159        return self.binary(expression, "IS")
def like_sql(self, expression: sqlglot.expressions.core.Like) -> str:
4206    def like_sql(self, expression: exp.Like) -> str:
4207        return self._like_sql(expression)
def ilike_sql(self, expression: sqlglot.expressions.core.ILike) -> str:
4209    def ilike_sql(self, expression: exp.ILike) -> str:
4210        return self._like_sql(expression)
def match_sql(self, expression: sqlglot.expressions.core.Match) -> str:
4212    def match_sql(self, expression: exp.Match) -> str:
4213        return self.binary(expression, "MATCH")
def similarto_sql(self, expression: sqlglot.expressions.core.SimilarTo) -> str:
4215    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4216        return self.binary(expression, "SIMILAR TO")
def lt_sql(self, expression: sqlglot.expressions.core.LT) -> str:
4218    def lt_sql(self, expression: exp.LT) -> str:
4219        return self.binary(expression, "<")
def lte_sql(self, expression: sqlglot.expressions.core.LTE) -> str:
4221    def lte_sql(self, expression: exp.LTE) -> str:
4222        return self.binary(expression, "<=")
def mod_sql(self, expression: sqlglot.expressions.core.Mod) -> str:
4224    def mod_sql(self, expression: exp.Mod) -> str:
4225        return self.binary(expression, "%")
def mul_sql(self, expression: sqlglot.expressions.core.Mul) -> str:
4227    def mul_sql(self, expression: exp.Mul) -> str:
4228        return self.binary(expression, "*")
def neq_sql(self, expression: sqlglot.expressions.core.NEQ) -> str:
4230    def neq_sql(self, expression: exp.NEQ) -> str:
4231        return self.binary(expression, "<>")
def nullsafeeq_sql(self, expression: sqlglot.expressions.core.NullSafeEQ) -> str:
4233    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4234        return self.binary(expression, "IS NOT DISTINCT FROM")
def nullsafeneq_sql(self, expression: sqlglot.expressions.core.NullSafeNEQ) -> str:
4236    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4237        return self.binary(expression, "IS DISTINCT FROM")
def sub_sql(self, expression: sqlglot.expressions.core.Sub) -> str:
4239    def sub_sql(self, expression: exp.Sub) -> str:
4240        return self.binary(expression, "-")
def trycast_sql(self, expression: sqlglot.expressions.functions.TryCast) -> str:
4242    def trycast_sql(self, expression: exp.TryCast) -> str:
4243        return self.cast_sql(expression, safe_prefix="TRY_")
def jsoncast_sql(self, expression: sqlglot.expressions.functions.JSONCast) -> str:
4245    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4246        return self.cast_sql(expression)
def try_sql(self, expression: sqlglot.expressions.functions.Try) -> str:
4248    def try_sql(self, expression: exp.Try) -> str:
4249        if not self.TRY_SUPPORTED:
4250            self.unsupported("Unsupported TRY function")
4251            return self.sql(expression, "this")
4252
4253        return self.func("TRY", expression.this)
def log_sql(self, expression: sqlglot.expressions.math.Log) -> str:
4255    def log_sql(self, expression: exp.Log) -> str:
4256        this = expression.this
4257        expr = expression.expression
4258
4259        if self.dialect.LOG_BASE_FIRST is False:
4260            this, expr = expr, this
4261        elif self.dialect.LOG_BASE_FIRST is None and expr:
4262            if this.name in ("2", "10"):
4263                return self.func(f"LOG{this.name}", expr)
4264
4265            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4266
4267        return self.func("LOG", this, expr)
def use_sql(self, expression: sqlglot.expressions.ddl.Use) -> str:
4269    def use_sql(self, expression: exp.Use) -> str:
4270        kind = self.sql(expression, "kind")
4271        kind = f" {kind}" if kind else ""
4272        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4273        this = f" {this}" if this else ""
4274        return f"USE{kind}{this}"
def binary(self, expression: sqlglot.expressions.core.Binary, op: str) -> str:
4276    def binary(self, expression: exp.Binary, op: str) -> str:
4277        sqls: list[str] = []
4278        stack: list[None | str | exp.Expr] = [expression]
4279        binary_type = type(expression)
4280
4281        while stack:
4282            node = stack.pop()
4283
4284            if type(node) is binary_type:
4285                op_func = node.args.get("operator")
4286                if op_func:
4287                    op = f"OPERATOR({self.sql(op_func)})"
4288
4289                stack.append(node.args.get("expression"))
4290                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4291                stack.append(node.args.get("this"))
4292            else:
4293                sqls.append(self.sql(node))
4294
4295        return "".join(sqls)
def ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4297    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4298        to_clause = self.sql(expression, "to")
4299        if to_clause:
4300            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4301
4302        return self.function_fallback_sql(expression)
def function_fallback_sql(self, expression: sqlglot.expressions.core.Func) -> str:
4304    def function_fallback_sql(self, expression: exp.Func) -> str:
4305        args = []
4306
4307        for key in expression.arg_types:
4308            arg_value = expression.args.get(key)
4309
4310            if isinstance(arg_value, list):
4311                for value in arg_value:
4312                    args.append(value)
4313            elif arg_value is not None:
4314                args.append(arg_value)
4315
4316        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4317            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
4318        else:
4319            name = expression.sql_name()
4320
4321        return self.func(name, *args)
def func( self, name: str, *args: Any, prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
4323    def func(
4324        self,
4325        name: str,
4326        *args: t.Any,
4327        prefix: str = "(",
4328        suffix: str = ")",
4329        normalize: bool = True,
4330    ) -> str:
4331        name = self.normalize_func(name) if normalize else name
4332        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def format_args(self, *args: Any, sep: str = ', ') -> str:
4334    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4335        arg_sqls = tuple(
4336            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4337        )
4338        if self.pretty and self.too_wide(arg_sqls):
4339            return self.indent(
4340                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4341            )
4342        return sep.join(arg_sqls)
def too_wide(self, args: Iterable) -> bool:
4344    def too_wide(self, args: t.Iterable) -> bool:
4345        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:
4347    def format_time(
4348        self,
4349        expression: exp.Expr,
4350        inverse_time_mapping: dict[str, str] | None = None,
4351        inverse_time_trie: dict | None = None,
4352    ) -> str | None:
4353        return format_time(
4354            self.sql(expression, "format"),
4355            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4356            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4357        )
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:
4359    def expressions(
4360        self,
4361        expression: exp.Expr | None = None,
4362        key: str | None = None,
4363        sqls: t.Collection[str | exp.Expr] | None = None,
4364        flat: bool = False,
4365        indent: bool = True,
4366        skip_first: bool = False,
4367        skip_last: bool = False,
4368        sep: str = ", ",
4369        prefix: str = "",
4370        dynamic: bool = False,
4371        new_line: bool = False,
4372    ) -> str:
4373        expressions = expression.args.get(key or "expressions") if expression else sqls
4374
4375        if not expressions:
4376            return ""
4377
4378        if flat:
4379            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4380
4381        num_sqls = len(expressions)
4382        result_sqls = []
4383
4384        for i, e in enumerate(expressions):
4385            sql = self.sql(e, comment=False)
4386            if not sql:
4387                continue
4388
4389            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4390
4391            if self.pretty:
4392                if self.leading_comma:
4393                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4394                else:
4395                    result_sqls.append(
4396                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4397                    )
4398            else:
4399                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4400
4401        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4402            if new_line:
4403                result_sqls.insert(0, "")
4404                result_sqls.append("")
4405            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4406        else:
4407            result_sql = "".join(result_sqls)
4408
4409        return (
4410            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4411            if indent
4412            else result_sql
4413        )
def op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
4415    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4416        flat = flat or isinstance(expression.parent, exp.Properties)
4417        expressions_sql = self.expressions(expression, flat=flat)
4418        if flat:
4419            return f"{op} {expressions_sql}"
4420        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
def naked_property(self, expression: sqlglot.expressions.properties.Property) -> str:
4422    def naked_property(self, expression: exp.Property) -> str:
4423        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4424        if not property_name:
4425            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4426        return f"{property_name} {self.sql(expression, 'this')}"
def tag_sql(self, expression: sqlglot.expressions.query.Tag) -> str:
4428    def tag_sql(self, expression: exp.Tag) -> str:
4429        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:
4431    def token_sql(self, token_type: TokenType) -> str:
4432        return self.TOKEN_MAPPING.get(token_type, token_type.name)
def userdefinedfunction_sql(self, expression: sqlglot.expressions.ddl.UserDefinedFunction) -> str:
4434    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4435        this = self.sql(expression, "this")
4436        expressions = self.no_identify(self.expressions, expression)
4437        expressions = (
4438            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4439        )
4440        return f"{this}{expressions}" if expressions.strip() != "" else this
def joinhint_sql(self, expression: sqlglot.expressions.core.JoinHint) -> str:
4442    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4443        this = self.sql(expression, "this")
4444        expressions = self.expressions(expression, flat=True)
4445        return f"{this}({expressions})"
def kwarg_sql(self, expression: sqlglot.expressions.core.Kwarg) -> str:
4447    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4448        return self.binary(expression, "=>")
def when_sql(self, expression: sqlglot.expressions.dml.When) -> str:
4450    def when_sql(self, expression: exp.When) -> str:
4451        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4452        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4453        condition = self.sql(expression, "condition")
4454        condition = f" AND {condition}" if condition else ""
4455
4456        then_expression = expression.args.get("then")
4457        if isinstance(then_expression, exp.Insert):
4458            this = self.sql(then_expression, "this")
4459            this = f"INSERT {this}" if this else "INSERT"
4460            then = self.sql(then_expression, "expression")
4461            then = f"{this} VALUES {then}" if then else this
4462        elif isinstance(then_expression, exp.Update):
4463            if isinstance(then_expression.args.get("expressions"), exp.Star):
4464                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4465            else:
4466                expressions_sql = self.expressions(then_expression)
4467                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4468        else:
4469            then = self.sql(then_expression)
4470
4471        if isinstance(then_expression, (exp.Insert, exp.Update)):
4472            where = self.sql(then_expression, "where")
4473            if where and not self.SUPPORTS_MERGE_WHERE:
4474                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4475                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4476                where = ""
4477            then = f"{then}{where}"
4478        return f"WHEN {matched}{source}{condition} THEN {then}"
def whens_sql(self, expression: sqlglot.expressions.dml.Whens) -> str:
4480    def whens_sql(self, expression: exp.Whens) -> str:
4481        return self.expressions(expression, sep=" ", indent=False)
def merge_sql(self, expression: sqlglot.expressions.dml.Merge) -> str:
4483    def merge_sql(self, expression: exp.Merge) -> str:
4484        table = expression.this
4485        table_alias = ""
4486
4487        hints = table.args.get("hints")
4488        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4489            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4490            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4491
4492        this = self.sql(table)
4493        using = f"USING {self.sql(expression, 'using')}"
4494        whens = self.sql(expression, "whens")
4495
4496        on = self.sql(expression, "on")
4497        on = f"ON {on}" if on else ""
4498
4499        if not on:
4500            on = self.expressions(expression, key="using_cond")
4501            on = f"USING ({on})" if on else ""
4502
4503        returning = self.sql(expression, "returning")
4504        if returning:
4505            whens = f"{whens}{returning}"
4506
4507        sep = self.sep()
4508
4509        return self.prepend_ctes(
4510            expression,
4511            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4512        )
@unsupported_args('format')
def tochar_sql(self, expression: sqlglot.expressions.string.ToChar) -> str:
4514    @unsupported_args("format")
4515    def tochar_sql(self, expression: exp.ToChar) -> str:
4516        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
def tonumber_sql(self, expression: sqlglot.expressions.string.ToNumber) -> str:
4518    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4519        if not self.SUPPORTS_TO_NUMBER:
4520            self.unsupported("Unsupported TO_NUMBER function")
4521            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4522
4523        fmt = expression.args.get("format")
4524        if not fmt:
4525            self.unsupported("Conversion format is required for TO_NUMBER")
4526            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4527
4528        return self.func("TO_NUMBER", expression.this, fmt)
def dictproperty_sql(self, expression: sqlglot.expressions.properties.DictProperty) -> str:
4530    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4531        this = self.sql(expression, "this")
4532        kind = self.sql(expression, "kind")
4533        settings_sql = self.expressions(expression, key="settings", sep=" ")
4534        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4535        return f"{this}({kind}{args})"
def dictrange_sql(self, expression: sqlglot.expressions.properties.DictRange) -> str:
4537    def dictrange_sql(self, expression: exp.DictRange) -> str:
4538        this = self.sql(expression, "this")
4539        max = self.sql(expression, "max")
4540        min = self.sql(expression, "min")
4541        return f"{this}(MIN {min} MAX {max})"
def dictsubproperty_sql(self, expression: sqlglot.expressions.properties.DictSubProperty) -> str:
4543    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4544        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
def duplicatekeyproperty_sql( self, expression: sqlglot.expressions.properties.DuplicateKeyProperty) -> str:
4546    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4547        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
def uniquekeyproperty_sql( self, expression: sqlglot.expressions.properties.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
4550    def uniquekeyproperty_sql(
4551        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4552    ) -> str:
4553        return f"{prefix} ({self.expressions(expression, flat=True)})"
def distributedbyproperty_sql( self, expression: sqlglot.expressions.properties.DistributedByProperty) -> str:
4556    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4557        expressions = self.expressions(expression, flat=True)
4558        expressions = f" {self.wrap(expressions)}" if expressions else ""
4559        buckets = self.sql(expression, "buckets")
4560        kind = self.sql(expression, "kind")
4561        buckets = f" BUCKETS {buckets}" if buckets else ""
4562        order = self.sql(expression, "order")
4563        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def oncluster_sql(self, expression: sqlglot.expressions.properties.OnCluster) -> str:
4565    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4566        return ""
def clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
4568    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4569        expressions = self.expressions(expression, key="expressions", flat=True)
4570        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4571        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4572        buckets = self.sql(expression, "buckets")
4573        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
def anyvalue_sql(self, expression: sqlglot.expressions.aggregate.AnyValue) -> str:
4575    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4576        this = self.sql(expression, "this")
4577        having = self.sql(expression, "having")
4578
4579        if having:
4580            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4581
4582        return self.func("ANY_VALUE", this)
def querytransform_sql(self, expression: sqlglot.expressions.properties.QueryTransform) -> str:
4584    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4585        transform = self.func("TRANSFORM", *expression.expressions)
4586        row_format_before = self.sql(expression, "row_format_before")
4587        row_format_before = f" {row_format_before}" if row_format_before else ""
4588        record_writer = self.sql(expression, "record_writer")
4589        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4590        using = f" USING {self.sql(expression, 'command_script')}"
4591        schema = self.sql(expression, "schema")
4592        schema = f" AS {schema}" if schema else ""
4593        row_format_after = self.sql(expression, "row_format_after")
4594        row_format_after = f" {row_format_after}" if row_format_after else ""
4595        record_reader = self.sql(expression, "record_reader")
4596        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4597        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:
4599    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4600        key_block_size = self.sql(expression, "key_block_size")
4601        if key_block_size:
4602            return f"KEY_BLOCK_SIZE = {key_block_size}"
4603
4604        using = self.sql(expression, "using")
4605        if using:
4606            return f"USING {using}"
4607
4608        parser = self.sql(expression, "parser")
4609        if parser:
4610            return f"WITH PARSER {parser}"
4611
4612        comment = self.sql(expression, "comment")
4613        if comment:
4614            return f"COMMENT {comment}"
4615
4616        visible = expression.args.get("visible")
4617        if visible is not None:
4618            return "VISIBLE" if visible else "INVISIBLE"
4619
4620        engine_attr = self.sql(expression, "engine_attr")
4621        if engine_attr:
4622            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4623
4624        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4625        if secondary_engine_attr:
4626            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4627
4628        self.unsupported("Unsupported index constraint option.")
4629        return ""
def checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
4631    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4632        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4633        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
def indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
4635    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4636        kind = self.sql(expression, "kind")
4637        kind = f"{kind} INDEX" if kind else "INDEX"
4638        this = self.sql(expression, "this")
4639        this = f" {this}" if this else ""
4640        index_type = self.sql(expression, "index_type")
4641        index_type = f" USING {index_type}" if index_type else ""
4642        expressions = self.expressions(expression, flat=True)
4643        expressions = f" ({expressions})" if expressions else ""
4644        options = self.expressions(expression, key="options", sep=" ")
4645        options = f" {options}" if options else ""
4646        return f"{kind}{this}{index_type}{expressions}{options}"
def nvl2_sql(self, expression: sqlglot.expressions.functions.Nvl2) -> str:
4648    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4649        if self.NVL2_SUPPORTED:
4650            return self.function_fallback_sql(expression)
4651
4652        case = exp.Case().when(
4653            expression.this.is_(exp.null()).not_(copy=False),
4654            expression.args["true"],
4655            copy=False,
4656        )
4657        else_cond = expression.args.get("false")
4658        if else_cond:
4659            case.else_(else_cond, copy=False)
4660
4661        return self.sql(case)
def comprehension_sql(self, expression: sqlglot.expressions.ddl.Comprehension) -> str:
4663    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4664        this = self.sql(expression, "this")
4665        expr = self.sql(expression, "expression")
4666        position = self.sql(expression, "position")
4667        position = f", {position}" if position else ""
4668        iterator = self.sql(expression, "iterator")
4669        condition = self.sql(expression, "condition")
4670        condition = f" IF {condition}" if condition else ""
4671        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def columnprefix_sql(self, expression: sqlglot.expressions.constraints.ColumnPrefix) -> str:
4673    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4674        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
def opclass_sql(self, expression: sqlglot.expressions.core.Opclass) -> str:
4676    def opclass_sql(self, expression: exp.Opclass) -> str:
4677        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def predict_sql(self, expression: sqlglot.expressions.functions.Predict) -> str:
4693    def predict_sql(self, expression: exp.Predict) -> str:
4694        return self._ml_sql(expression, "PREDICT")
def generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
4696    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4697        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4698        return self._ml_sql(expression, name)
def generatetext_sql(self, expression: sqlglot.expressions.functions.GenerateText) -> str:
4700    def generatetext_sql(self, expression: exp.GenerateText) -> str:
4701        return self._ml_sql(expression, "GENERATE_TEXT")
def generatetable_sql(self, expression: sqlglot.expressions.functions.GenerateTable) -> str:
4703    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
4704        return self._ml_sql(expression, "GENERATE_TABLE")
def generatebool_sql(self, expression: sqlglot.expressions.functions.GenerateBool) -> str:
4706    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
4707        return self._ml_sql(expression, "GENERATE_BOOL")
def generateint_sql(self, expression: sqlglot.expressions.functions.GenerateInt) -> str:
4709    def generateint_sql(self, expression: exp.GenerateInt) -> str:
4710        return self._ml_sql(expression, "GENERATE_INT")
def generatedouble_sql(self, expression: sqlglot.expressions.functions.GenerateDouble) -> str:
4712    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
4713        return self._ml_sql(expression, "GENERATE_DOUBLE")
def mltranslate_sql(self, expression: sqlglot.expressions.functions.MLTranslate) -> str:
4715    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
4716        return self._ml_sql(expression, "TRANSLATE")
def mlforecast_sql(self, expression: sqlglot.expressions.functions.MLForecast) -> str:
4718    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
4719        return self._ml_sql(expression, "FORECAST")
def aiforecast_sql(self, expression: sqlglot.expressions.functions.AIForecast) -> str:
4721    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
4722        this_sql = self.sql(expression, "this")
4723        if isinstance(expression.this, exp.Table):
4724            this_sql = f"TABLE {this_sql}"
4725
4726        return self.func(
4727            "FORECAST",
4728            this_sql,
4729            expression.args.get("data_col"),
4730            expression.args.get("timestamp_col"),
4731            expression.args.get("model"),
4732            expression.args.get("id_cols"),
4733            expression.args.get("horizon"),
4734            expression.args.get("forecast_end_timestamp"),
4735            expression.args.get("confidence_level"),
4736            expression.args.get("output_historical_time_series"),
4737            expression.args.get("context_window"),
4738        )
def featuresattime_sql(self, expression: sqlglot.expressions.functions.FeaturesAtTime) -> str:
4740    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4741        this_sql = self.sql(expression, "this")
4742        if isinstance(expression.this, exp.Table):
4743            this_sql = f"TABLE {this_sql}"
4744
4745        return self.func(
4746            "FEATURES_AT_TIME",
4747            this_sql,
4748            expression.args.get("time"),
4749            expression.args.get("num_rows"),
4750            expression.args.get("ignore_feature_nulls"),
4751        )
def vectorsearch_sql(self, expression: sqlglot.expressions.functions.VectorSearch) -> str:
4753    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
4754        this_sql = self.sql(expression, "this")
4755        if isinstance(expression.this, exp.Table):
4756            this_sql = f"TABLE {this_sql}"
4757
4758        query_table = self.sql(expression, "query_table")
4759        if isinstance(expression.args["query_table"], exp.Table):
4760            query_table = f"TABLE {query_table}"
4761
4762        return self.func(
4763            "VECTOR_SEARCH",
4764            this_sql,
4765            expression.args.get("column_to_search"),
4766            query_table,
4767            expression.args.get("query_column_to_search"),
4768            expression.args.get("top_k"),
4769            expression.args.get("distance_type"),
4770            expression.args.get("options"),
4771        )
def forin_sql(self, expression: sqlglot.expressions.core.ForIn) -> str:
4773    def forin_sql(self, expression: exp.ForIn) -> str:
4774        this = self.sql(expression, "this")
4775        expression_sql = self.sql(expression, "expression")
4776        return f"FOR {this} DO {expression_sql}"
def refresh_sql(self, expression: sqlglot.expressions.core.Refresh) -> str:
4778    def refresh_sql(self, expression: exp.Refresh) -> str:
4779        this = self.sql(expression, "this")
4780        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
4781        return f"REFRESH {kind}{this}"
def toarray_sql(self, expression: sqlglot.expressions.array.ToArray) -> str:
4783    def toarray_sql(self, expression: exp.ToArray) -> str:
4784        arg = expression.this
4785        if not arg.type:
4786            import sqlglot.optimizer.annotate_types
4787
4788            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
4789
4790        if arg.is_type(exp.DType.ARRAY):
4791            return self.sql(arg)
4792
4793        cond_for_null = arg.is_(exp.null())
4794        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:
4796    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4797        this = expression.this
4798        time_format = self.format_time(expression)
4799
4800        if time_format:
4801            return self.sql(
4802                exp.cast(
4803                    exp.StrToTime(this=this, format=expression.args["format"]),
4804                    exp.DType.TIME,
4805                )
4806            )
4807
4808        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
4809            return self.sql(this)
4810
4811        return self.sql(exp.cast(this, exp.DType.TIME))
def tsordstotimestamp_sql(self, expression: sqlglot.expressions.temporal.TsOrDsToTimestamp) -> str:
4813    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4814        this = expression.this
4815        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
4816            return self.sql(this)
4817
4818        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
def tsordstodatetime_sql(self, expression: sqlglot.expressions.temporal.TsOrDsToDatetime) -> str:
4820    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4821        this = expression.this
4822        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
4823            return self.sql(this)
4824
4825        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
def tsordstodate_sql(self, expression: sqlglot.expressions.temporal.TsOrDsToDate) -> str:
4827    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4828        this = expression.this
4829        time_format = self.format_time(expression)
4830        safe = expression.args.get("safe")
4831        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4832            return self.sql(
4833                exp.cast(
4834                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
4835                    exp.DType.DATE,
4836                )
4837            )
4838
4839        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
4840            return self.sql(this)
4841
4842        if safe:
4843            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
4844
4845        return self.sql(exp.cast(this, exp.DType.DATE))
def unixdate_sql(self, expression: sqlglot.expressions.temporal.UnixDate) -> str:
4847    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4848        return self.sql(
4849            exp.func(
4850                "DATEDIFF",
4851                expression.this,
4852                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
4853                "day",
4854            )
4855        )
def lastday_sql(self, expression: sqlglot.expressions.temporal.LastDay) -> str:
4857    def lastday_sql(self, expression: exp.LastDay) -> str:
4858        if self.LAST_DAY_SUPPORTS_DATE_PART:
4859            return self.function_fallback_sql(expression)
4860
4861        unit = expression.text("unit")
4862        if unit and unit != "MONTH":
4863            self.unsupported("Date parts are not supported in LAST_DAY.")
4864
4865        return self.func("LAST_DAY", expression.this)
def dateadd_sql(self, expression: sqlglot.expressions.temporal.DateAdd) -> str:
4867    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4868        import sqlglot.dialects.dialect
4869
4870        return self.func(
4871            "DATE_ADD",
4872            expression.this,
4873            expression.expression,
4874            sqlglot.dialects.dialect.unit_to_str(expression),
4875        )
def arrayany_sql(self, expression: sqlglot.expressions.array.ArrayAny) -> str:
4877    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4878        if self.CAN_IMPLEMENT_ARRAY_ANY:
4879            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4880            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4881            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4882            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4883
4884        import sqlglot.dialects.dialect
4885
4886        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4887        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
4888            self.unsupported("ARRAY_ANY is unsupported")
4889
4890        return self.function_fallback_sql(expression)
def struct_sql(self, expression: sqlglot.expressions.array.Struct) -> str:
4892    def struct_sql(self, expression: exp.Struct) -> str:
4893        expression.set(
4894            "expressions",
4895            [
4896                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4897                if isinstance(e, exp.PropertyEQ)
4898                else e
4899                for e in expression.expressions
4900            ],
4901        )
4902
4903        return self.function_fallback_sql(expression)
def partitionrange_sql(self, expression: sqlglot.expressions.query.PartitionRange) -> str:
4905    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4906        low = self.sql(expression, "this")
4907        high = self.sql(expression, "expression")
4908
4909        return f"{low} TO {high}"
def truncatetable_sql(self, expression: sqlglot.expressions.ddl.TruncateTable) -> str:
4911    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4912        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4913        tables = f" {self.expressions(expression)}"
4914
4915        exists = " IF EXISTS" if expression.args.get("exists") else ""
4916
4917        on_cluster = self.sql(expression, "cluster")
4918        on_cluster = f" {on_cluster}" if on_cluster else ""
4919
4920        identity = self.sql(expression, "identity")
4921        identity = f" {identity} IDENTITY" if identity else ""
4922
4923        option = self.sql(expression, "option")
4924        option = f" {option}" if option else ""
4925
4926        partition = self.sql(expression, "partition")
4927        partition = f" {partition}" if partition else ""
4928
4929        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
def convert_sql(self, expression: sqlglot.expressions.functions.Convert) -> str:
4933    def convert_sql(self, expression: exp.Convert) -> str:
4934        to = expression.this
4935        value = expression.expression
4936        style = expression.args.get("style")
4937        safe = expression.args.get("safe")
4938        strict = expression.args.get("strict")
4939
4940        if not to or not value:
4941            return ""
4942
4943        # Retrieve length of datatype and override to default if not specified
4944        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4945            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4946
4947        transformed: exp.Expr | None = None
4948        cast = exp.Cast if strict else exp.TryCast
4949
4950        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4951        if isinstance(style, exp.Literal) and style.is_int:
4952            import sqlglot.dialects.tsql
4953
4954            style_value = style.name
4955            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4956            if not converted_style:
4957                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4958
4959            fmt = exp.Literal.string(converted_style)
4960
4961            if to.this == exp.DType.DATE:
4962                transformed = exp.StrToDate(this=value, format=fmt)
4963            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
4964                transformed = exp.StrToTime(this=value, format=fmt)
4965            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4966                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4967            elif to.this == exp.DType.TEXT:
4968                transformed = exp.TimeToStr(this=value, format=fmt)
4969
4970        if not transformed:
4971            transformed = cast(this=value, to=to, safe=safe)
4972
4973        return self.sql(transformed)
def copyparameter_sql(self, expression: sqlglot.expressions.dml.CopyParameter) -> str:
5044    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5045        option = self.sql(expression, "this")
5046
5047        if expression.expressions:
5048            upper = option.upper()
5049
5050            # Snowflake FILE_FORMAT options are separated by whitespace
5051            sep = " " if upper == "FILE_FORMAT" else ", "
5052
5053            # Databricks copy/format options do not set their list of values with EQ
5054            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5055            values = self.expressions(expression, flat=True, sep=sep)
5056            return f"{option}{op}({values})"
5057
5058        value = self.sql(expression, "expression")
5059
5060        if not value:
5061            return option
5062
5063        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5064
5065        return f"{option}{op}{value}"
def credentials_sql(self, expression: sqlglot.expressions.dml.Credentials) -> str:
5067    def credentials_sql(self, expression: exp.Credentials) -> str:
5068        cred_expr = expression.args.get("credentials")
5069        if isinstance(cred_expr, exp.Literal):
5070            # Redshift case: CREDENTIALS <string>
5071            credentials = self.sql(expression, "credentials")
5072            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5073        else:
5074            # Snowflake case: CREDENTIALS = (...)
5075            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5076            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5077
5078        storage = self.sql(expression, "storage")
5079        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5080
5081        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5082        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5083
5084        iam_role = self.sql(expression, "iam_role")
5085        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5086
5087        region = self.sql(expression, "region")
5088        region = f" REGION {region}" if region else ""
5089
5090        return f"{credentials}{storage}{encryption}{iam_role}{region}"
def copy_sql(self, expression: sqlglot.expressions.dml.Copy) -> str:
5092    def copy_sql(self, expression: exp.Copy) -> str:
5093        this = self.sql(expression, "this")
5094        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5095
5096        credentials = self.sql(expression, "credentials")
5097        credentials = self.seg(credentials) if credentials else ""
5098        files = self.expressions(expression, key="files", flat=True)
5099        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5100
5101        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5102        params = self.expressions(
5103            expression,
5104            key="params",
5105            sep=sep,
5106            new_line=True,
5107            skip_last=True,
5108            skip_first=True,
5109            indent=self.COPY_PARAMS_ARE_WRAPPED,
5110        )
5111
5112        if params:
5113            if self.COPY_PARAMS_ARE_WRAPPED:
5114                params = f" WITH ({params})"
5115            elif not self.pretty and (files or credentials):
5116                params = f" {params}"
5117
5118        return f"COPY{this}{kind} {files}{credentials}{params}"
def semicolon_sql(self, expression: sqlglot.expressions.query.Semicolon) -> str:
5120    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5121        return ""
def datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5123    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5124        on_sql = "ON" if expression.args.get("on") else "OFF"
5125        filter_col: str | None = self.sql(expression, "filter_column")
5126        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5127        retention_period: str | None = self.sql(expression, "retention_period")
5128        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5129
5130        if filter_col or retention_period:
5131            on_sql = self.func("ON", filter_col, retention_period)
5132
5133        return f"DATA_DELETION={on_sql}"
def maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5135    def maskingpolicycolumnconstraint_sql(
5136        self, expression: exp.MaskingPolicyColumnConstraint
5137    ) -> str:
5138        this = self.sql(expression, "this")
5139        expressions = self.expressions(expression, flat=True)
5140        expressions = f" USING ({expressions})" if expressions else ""
5141        return f"MASKING POLICY {this}{expressions}"
def gapfill_sql(self, expression: sqlglot.expressions.temporal.GapFill) -> str:
5143    def gapfill_sql(self, expression: exp.GapFill) -> str:
5144        this = self.sql(expression, "this")
5145        this = f"TABLE {this}"
5146        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:
5148    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5149        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
def scoperesolution_sql(self, expression: sqlglot.expressions.query.ScopeResolution) -> str:
5151    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5152        this = self.sql(expression, "this")
5153        expr = expression.expression
5154
5155        if isinstance(expr, exp.Func):
5156            # T-SQL's CLR functions are case sensitive
5157            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5158        else:
5159            expr = self.sql(expression, "expression")
5160
5161        return self.scope_resolution(expr, this)
def parsejson_sql(self, expression: sqlglot.expressions.json.ParseJSON) -> str:
5163    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5164        if self.PARSE_JSON_NAME is None:
5165            return self.sql(expression.this)
5166
5167        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
def rand_sql(self, expression: sqlglot.expressions.functions.Rand) -> str:
5169    def rand_sql(self, expression: exp.Rand) -> str:
5170        lower = self.sql(expression, "lower")
5171        upper = self.sql(expression, "upper")
5172
5173        if lower and upper:
5174            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5175        return self.func("RAND", expression.this)
def changes_sql(self, expression: sqlglot.expressions.query.Changes) -> str:
5177    def changes_sql(self, expression: exp.Changes) -> str:
5178        information = self.sql(expression, "information")
5179        information = f"INFORMATION => {information}"
5180        at_before = self.sql(expression, "at_before")
5181        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5182        end = self.sql(expression, "end")
5183        end = f"{self.seg('')}{end}" if end else ""
5184
5185        return f"CHANGES ({information}){at_before}{end}"
def pad_sql(self, expression: sqlglot.expressions.string.Pad) -> str:
5187    def pad_sql(self, expression: exp.Pad) -> str:
5188        prefix = "L" if expression.args.get("is_left") else "R"
5189
5190        fill_pattern = self.sql(expression, "fill_pattern") or None
5191        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5192            fill_pattern = "' '"
5193
5194        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def summarize_sql(self, expression: sqlglot.expressions.ddl.Summarize) -> str:
5196    def summarize_sql(self, expression: exp.Summarize) -> str:
5197        table = " TABLE" if expression.args.get("table") else ""
5198        return f"SUMMARIZE{table} {self.sql(expression.this)}"
def explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5200    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5201        generate_series = exp.GenerateSeries(**expression.args)
5202
5203        parent = expression.parent
5204        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5205            parent = parent.parent
5206
5207        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5208            return self.sql(exp.Unnest(expressions=[generate_series]))
5209
5210        if isinstance(parent, exp.Select):
5211            self.unsupported("GenerateSeries projection unnesting is not supported.")
5212
5213        return self.sql(generate_series)
def converttimezone_sql(self, expression: sqlglot.expressions.temporal.ConvertTimezone) -> str:
5215    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5216        if self.SUPPORTS_CONVERT_TIMEZONE:
5217            return self.function_fallback_sql(expression)
5218
5219        source_tz = expression.args.get("source_tz")
5220        target_tz = expression.args.get("target_tz")
5221        timestamp = expression.args.get("timestamp")
5222
5223        if source_tz and timestamp:
5224            timestamp = exp.AtTimeZone(
5225                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5226            )
5227
5228        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5229
5230        return self.sql(expr)
def json_sql(self, expression: sqlglot.expressions.query.JSON) -> str:
5232    def json_sql(self, expression: exp.JSON) -> str:
5233        this = self.sql(expression, "this")
5234        this = f" {this}" if this else ""
5235
5236        _with = expression.args.get("with_")
5237
5238        if _with is None:
5239            with_sql = ""
5240        elif not _with:
5241            with_sql = " WITHOUT"
5242        else:
5243            with_sql = " WITH"
5244
5245        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5246
5247        return f"JSON{this}{with_sql}{unique_sql}"
def jsonvalue_sql(self, expression: sqlglot.expressions.query.JSONValue) -> str:
5249    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5250        path = self.sql(expression, "path")
5251        returning = self.sql(expression, "returning")
5252        returning = f" RETURNING {returning}" if returning else ""
5253
5254        on_condition = self.sql(expression, "on_condition")
5255        on_condition = f" {on_condition}" if on_condition else ""
5256
5257        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
def skipjsoncolumn_sql(self, expression: sqlglot.expressions.query.SkipJSONColumn) -> str:
5259    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5260        regexp = " REGEXP" if expression.args.get("regexp") else ""
5261        return f"SKIP{regexp} {self.sql(expression.expression)}"
def conditionalinsert_sql(self, expression: sqlglot.expressions.query.ConditionalInsert) -> str:
5263    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5264        else_ = "ELSE " if expression.args.get("else_") else ""
5265        condition = self.sql(expression, "expression")
5266        condition = f"WHEN {condition} THEN " if condition else else_
5267        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5268        return f"{condition}{insert}"
def multitableinserts_sql(self, expression: sqlglot.expressions.query.MultitableInserts) -> str:
5270    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5271        kind = self.sql(expression, "kind")
5272        expressions = self.seg(self.expressions(expression, sep=" "))
5273        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
5274        return res
def oncondition_sql(self, expression: sqlglot.expressions.query.OnCondition) -> str:
5276    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5277        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5278        empty = expression.args.get("empty")
5279        empty = (
5280            f"DEFAULT {empty} ON EMPTY"
5281            if isinstance(empty, exp.Expr)
5282            else self.sql(expression, "empty")
5283        )
5284
5285        error = expression.args.get("error")
5286        error = (
5287            f"DEFAULT {error} ON ERROR"
5288            if isinstance(error, exp.Expr)
5289            else self.sql(expression, "error")
5290        )
5291
5292        if error and empty:
5293            error = (
5294                f"{empty} {error}"
5295                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5296                else f"{error} {empty}"
5297            )
5298            empty = ""
5299
5300        null = self.sql(expression, "null")
5301
5302        return f"{empty}{error}{null}"
def jsonextractquote_sql(self, expression: sqlglot.expressions.query.JSONExtractQuote) -> str:
5304    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5305        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5306        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
def jsonexists_sql(self, expression: sqlglot.expressions.json.JSONExists) -> str:
5308    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5309        this = self.sql(expression, "this")
5310        path = self.sql(expression, "path")
5311
5312        passing = self.expressions(expression, "passing")
5313        passing = f" PASSING {passing}" if passing else ""
5314
5315        on_condition = self.sql(expression, "on_condition")
5316        on_condition = f" {on_condition}" if on_condition else ""
5317
5318        path = f"{path}{passing}{on_condition}"
5319
5320        return self.func("JSON_EXISTS", this, path)
def arrayagg_sql(self, expression: sqlglot.expressions.aggregate.ArrayAgg) -> str:
5362    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5363        array_agg = self.function_fallback_sql(expression)
5364        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
def slice_sql(self, expression: sqlglot.expressions.core.Slice) -> str:
5366    def slice_sql(self, expression: exp.Slice) -> str:
5367        step = self.sql(expression, "step")
5368        end = self.sql(expression.expression)
5369        begin = self.sql(expression.this)
5370
5371        sql = f"{end}:{step}" if step else end
5372        return f"{begin}:{sql}" if sql else f"{begin}:"
def apply_sql(self, expression: sqlglot.expressions.array.Apply) -> str:
5374    def apply_sql(self, expression: exp.Apply) -> str:
5375        this = self.sql(expression, "this")
5376        expr = self.sql(expression, "expression")
5377
5378        return f"{this} APPLY({expr})"
def grant_sql(self, expression: sqlglot.expressions.query.Grant) -> str:
5407    def grant_sql(self, expression: exp.Grant) -> str:
5408        return self._grant_or_revoke_sql(
5409            expression,
5410            keyword="GRANT",
5411            preposition="TO",
5412            grant_option_suffix=" WITH GRANT OPTION",
5413        )
def revoke_sql(self, expression: sqlglot.expressions.query.Revoke) -> str:
5415    def revoke_sql(self, expression: exp.Revoke) -> str:
5416        return self._grant_or_revoke_sql(
5417            expression,
5418            keyword="REVOKE",
5419            preposition="FROM",
5420            grant_option_prefix="GRANT OPTION FOR ",
5421        )
def grantprivilege_sql(self, expression: sqlglot.expressions.properties.GrantPrivilege) -> str:
5423    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5424        this = self.sql(expression, "this")
5425        columns = self.expressions(expression, flat=True)
5426        columns = f"({columns})" if columns else ""
5427
5428        return f"{this}{columns}"
def grantprincipal_sql(self, expression: sqlglot.expressions.properties.GrantPrincipal) -> str:
5430    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5431        this = self.sql(expression, "this")
5432
5433        kind = self.sql(expression, "kind")
5434        kind = f"{kind} " if kind else ""
5435
5436        return f"{kind}{this}"
def columns_sql(self, expression: sqlglot.expressions.functions.Columns) -> str:
5438    def columns_sql(self, expression: exp.Columns) -> str:
5439        func = self.function_fallback_sql(expression)
5440        if expression.args.get("unpack"):
5441            func = f"*{func}"
5442
5443        return func
def overlay_sql(self, expression: sqlglot.expressions.string.Overlay) -> str:
5445    def overlay_sql(self, expression: exp.Overlay) -> str:
5446        this = self.sql(expression, "this")
5447        expr = self.sql(expression, "expression")
5448        from_sql = self.sql(expression, "from_")
5449        for_sql = self.sql(expression, "for_")
5450        for_sql = f" FOR {for_sql}" if for_sql else ""
5451
5452        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
5454    @unsupported_args("format")
5455    def todouble_sql(self, expression: exp.ToDouble) -> str:
5456        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5457        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
def string_sql(self, expression: sqlglot.expressions.string.String) -> str:
5459    def string_sql(self, expression: exp.String) -> str:
5460        this = expression.this
5461        zone = expression.args.get("zone")
5462
5463        if zone:
5464            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5465            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5466            # set for source_tz to transpile the time conversion before the STRING cast
5467            this = exp.ConvertTimezone(
5468                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5469            )
5470
5471        return self.sql(exp.cast(this, exp.DType.VARCHAR))
def median_sql(self, expression: sqlglot.expressions.aggregate.Median) -> str:
5473    def median_sql(self, expression: exp.Median) -> str:
5474        if not self.SUPPORTS_MEDIAN:
5475            return self.sql(
5476                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5477            )
5478
5479        return self.function_fallback_sql(expression)
def overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
5481    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5482        filler = self.sql(expression, "this")
5483        filler = f" {filler}" if filler else ""
5484        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5485        return f"TRUNCATE{filler} {with_count}"
def unixseconds_sql(self, expression: sqlglot.expressions.temporal.UnixSeconds) -> str:
5487    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5488        if self.SUPPORTS_UNIX_SECONDS:
5489            return self.function_fallback_sql(expression)
5490
5491        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
5492
5493        return self.sql(
5494            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5495        )
def arraysize_sql(self, expression: sqlglot.expressions.array.ArraySize) -> str:
5497    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5498        dim = expression.expression
5499
5500        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5501        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5502            if not (dim.is_int and dim.name == "1"):
5503                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5504            dim = None
5505
5506        # If dimension is required but not specified, default initialize it
5507        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5508            dim = exp.Literal.number(1)
5509
5510        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
def attach_sql(self, expression: sqlglot.expressions.ddl.Attach) -> str:
5512    def attach_sql(self, expression: exp.Attach) -> str:
5513        this = self.sql(expression, "this")
5514        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5515        expressions = self.expressions(expression)
5516        expressions = f" ({expressions})" if expressions else ""
5517
5518        return f"ATTACH{exists_sql} {this}{expressions}"
def detach_sql(self, expression: sqlglot.expressions.ddl.Detach) -> str:
5520    def detach_sql(self, expression: exp.Detach) -> str:
5521        kind = self.sql(expression, "kind")
5522        kind = f" {kind}" if kind else ""
5523        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
5524        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5525        exists = " IF EXISTS" if expression.args.get("exists") else ""
5526        if exists:
5527            kind = kind or " DATABASE"
5528
5529        this = self.sql(expression, "this")
5530        this = f" {this}" if this else ""
5531        cluster = self.sql(expression, "cluster")
5532        cluster = f" {cluster}" if cluster else ""
5533        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
5534        sync = " SYNC" if expression.args.get("sync") else ""
5535        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def attachoption_sql(self, expression: sqlglot.expressions.query.AttachOption) -> str:
5537    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5538        this = self.sql(expression, "this")
5539        value = self.sql(expression, "expression")
5540        value = f" {value}" if value else ""
5541        return f"{this}{value}"
def watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
5543    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5544        return (
5545            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5546        )
def encodeproperty_sql(self, expression: sqlglot.expressions.properties.EncodeProperty) -> str:
5548    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5549        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5550        encode = f"{encode} {self.sql(expression, 'this')}"
5551
5552        properties = expression.args.get("properties")
5553        if properties:
5554            encode = f"{encode} {self.properties(properties)}"
5555
5556        return encode
def includeproperty_sql(self, expression: sqlglot.expressions.properties.IncludeProperty) -> str:
5558    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5559        this = self.sql(expression, "this")
5560        include = f"INCLUDE {this}"
5561
5562        column_def = self.sql(expression, "column_def")
5563        if column_def:
5564            include = f"{include} {column_def}"
5565
5566        alias = self.sql(expression, "alias")
5567        if alias:
5568            include = f"{include} AS {alias}"
5569
5570        return include
def xmlelement_sql(self, expression: sqlglot.expressions.functions.XMLElement) -> str:
5572    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5573        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
5574        name = f"{prefix} {self.sql(expression, 'this')}"
5575        return self.func("XMLELEMENT", name, *expression.expressions)
def xmlkeyvalueoption_sql(self, expression: sqlglot.expressions.query.XMLKeyValueOption) -> str:
5577    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5578        this = self.sql(expression, "this")
5579        expr = self.sql(expression, "expression")
5580        expr = f"({expr})" if expr else ""
5581        return f"{this}{expr}"
def partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
5583    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5584        partitions = self.expressions(expression, "partition_expressions")
5585        create = self.expressions(expression, "create_expressions")
5586        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
5588    def partitionbyrangepropertydynamic_sql(
5589        self, expression: exp.PartitionByRangePropertyDynamic
5590    ) -> str:
5591        start = self.sql(expression, "start")
5592        end = self.sql(expression, "end")
5593
5594        every = expression.args["every"]
5595        if isinstance(every, exp.Interval) and every.this.is_string:
5596            every.this.replace(exp.Literal.number(every.name))
5597
5598        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:
5600    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5601        name = self.sql(expression, "this")
5602        values = self.expressions(expression, flat=True)
5603
5604        return f"NAME {name} VALUE {values}"
def analyzesample_sql(self, expression: sqlglot.expressions.query.AnalyzeSample) -> str:
5606    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5607        kind = self.sql(expression, "kind")
5608        sample = self.sql(expression, "sample")
5609        return f"SAMPLE {sample} {kind}"
def analyzestatistics_sql(self, expression: sqlglot.expressions.query.AnalyzeStatistics) -> str:
5611    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5612        kind = self.sql(expression, "kind")
5613        option = self.sql(expression, "option")
5614        option = f" {option}" if option else ""
5615        this = self.sql(expression, "this")
5616        this = f" {this}" if this else ""
5617        columns = self.expressions(expression)
5618        columns = f" {columns}" if columns else ""
5619        return f"{kind}{option} STATISTICS{this}{columns}"
def analyzehistogram_sql(self, expression: sqlglot.expressions.query.AnalyzeHistogram) -> str:
5621    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5622        this = self.sql(expression, "this")
5623        columns = self.expressions(expression)
5624        inner_expression = self.sql(expression, "expression")
5625        inner_expression = f" {inner_expression}" if inner_expression else ""
5626        update_options = self.sql(expression, "update_options")
5627        update_options = f" {update_options} UPDATE" if update_options else ""
5628        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def analyzedelete_sql(self, expression: sqlglot.expressions.query.AnalyzeDelete) -> str:
5630    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5631        kind = self.sql(expression, "kind")
5632        kind = f" {kind}" if kind else ""
5633        return f"DELETE{kind} STATISTICS"
def analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
5635    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5636        inner_expression = self.sql(expression, "expression")
5637        return f"LIST CHAINED ROWS{inner_expression}"
def analyzevalidate_sql(self, expression: sqlglot.expressions.query.AnalyzeValidate) -> str:
5639    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5640        kind = self.sql(expression, "kind")
5641        this = self.sql(expression, "this")
5642        this = f" {this}" if this else ""
5643        inner_expression = self.sql(expression, "expression")
5644        return f"VALIDATE {kind}{this}{inner_expression}"
def analyze_sql(self, expression: sqlglot.expressions.query.Analyze) -> str:
5646    def analyze_sql(self, expression: exp.Analyze) -> str:
5647        options = self.expressions(expression, key="options", sep=" ")
5648        options = f" {options}" if options else ""
5649        kind = self.sql(expression, "kind")
5650        kind = f" {kind}" if kind else ""
5651        this = self.sql(expression, "this")
5652        this = f" {this}" if this else ""
5653        mode = self.sql(expression, "mode")
5654        mode = f" {mode}" if mode else ""
5655        properties = self.sql(expression, "properties")
5656        properties = f" {properties}" if properties else ""
5657        partition = self.sql(expression, "partition")
5658        partition = f" {partition}" if partition else ""
5659        inner_expression = self.sql(expression, "expression")
5660        inner_expression = f" {inner_expression}" if inner_expression else ""
5661        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
def xmltable_sql(self, expression: sqlglot.expressions.functions.XMLTable) -> str:
5663    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5664        this = self.sql(expression, "this")
5665        namespaces = self.expressions(expression, key="namespaces")
5666        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5667        passing = self.expressions(expression, key="passing")
5668        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5669        columns = self.expressions(expression, key="columns")
5670        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5671        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5672        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:
5674    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5675        this = self.sql(expression, "this")
5676        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
def export_sql(self, expression: sqlglot.expressions.dml.Export) -> str:
5678    def export_sql(self, expression: exp.Export) -> str:
5679        this = self.sql(expression, "this")
5680        connection = self.sql(expression, "connection")
5681        connection = f"WITH CONNECTION {connection} " if connection else ""
5682        options = self.sql(expression, "options")
5683        return f"EXPORT DATA {connection}{options} AS {this}"
def declare_sql(self, expression: sqlglot.expressions.ddl.Declare) -> str:
5685    def declare_sql(self, expression: exp.Declare) -> str:
5686        replace = "OR REPLACE " if expression.args.get("replace") else ""
5687        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
def declareitem_sql(self, expression: sqlglot.expressions.ddl.DeclareItem) -> str:
5689    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5690        variables = self.expressions(expression, "this")
5691        default = self.sql(expression, "default")
5692        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
5693
5694        kind = self.sql(expression, "kind")
5695        if isinstance(expression.args.get("kind"), exp.Schema):
5696            kind = f"TABLE {kind}"
5697
5698        kind = f" {kind}" if kind else ""
5699
5700        return f"{variables}{kind}{default}"
def recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
5702    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5703        kind = self.sql(expression, "kind")
5704        this = self.sql(expression, "this")
5705        set = self.sql(expression, "expression")
5706        using = self.sql(expression, "using")
5707        using = f" USING {using}" if using else ""
5708
5709        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5710
5711        return f"{kind_sql} {this} SET {set}{using}"
def parameterizedagg_sql(self, expression: sqlglot.expressions.core.ParameterizedAgg) -> str:
5713    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5714        params = self.expressions(expression, key="params", flat=True)
5715        return self.func(expression.name, *expression.expressions) + f"({params})"
def anonymousaggfunc_sql(self, expression: sqlglot.expressions.core.AnonymousAggFunc) -> str:
5717    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5718        return self.func(expression.name, *expression.expressions)
def combinedaggfunc_sql(self, expression: sqlglot.expressions.core.CombinedAggFunc) -> str:
5720    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5721        return self.anonymousaggfunc_sql(expression)
def combinedparameterizedagg_sql( self, expression: sqlglot.expressions.core.CombinedParameterizedAgg) -> str:
5723    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5724        return self.parameterizedagg_sql(expression)
def show_sql(self, expression: sqlglot.expressions.ddl.Show) -> str:
5726    def show_sql(self, expression: exp.Show) -> str:
5727        self.unsupported("Unsupported SHOW statement")
5728        return ""
def install_sql(self, expression: sqlglot.expressions.ddl.Install) -> str:
5730    def install_sql(self, expression: exp.Install) -> str:
5731        self.unsupported("Unsupported INSTALL statement")
5732        return ""
def get_put_sql( self, expression: sqlglot.expressions.query.Put | sqlglot.expressions.query.Get) -> str:
5734    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5735        # Snowflake GET/PUT statements:
5736        #   PUT <file> <internalStage> <properties>
5737        #   GET <internalStage> <file> <properties>
5738        props = expression.args.get("properties")
5739        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5740        this = self.sql(expression, "this")
5741        target = self.sql(expression, "target")
5742
5743        if isinstance(expression, exp.Put):
5744            return f"PUT {this} {target}{props_sql}"
5745        else:
5746            return f"GET {target} {this}{props_sql}"
def translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
5748    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
5749        this = self.sql(expression, "this")
5750        expr = self.sql(expression, "expression")
5751        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5752        return f"TRANSLATE({this} USING {expr}{with_error})"
def decodecase_sql(self, expression: sqlglot.expressions.functions.DecodeCase) -> str:
5754    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5755        if self.SUPPORTS_DECODE_CASE:
5756            return self.func("DECODE", *expression.expressions)
5757
5758        decode_expr, *expressions = expression.expressions
5759
5760        ifs = []
5761        for search, result in zip(expressions[::2], expressions[1::2]):
5762            if isinstance(search, exp.Literal):
5763                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
5764            elif isinstance(search, exp.Null):
5765                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
5766            else:
5767                if isinstance(search, exp.Binary):
5768                    search = exp.paren(search)
5769
5770                cond = exp.or_(
5771                    decode_expr.eq(search),
5772                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5773                    copy=False,
5774                )
5775                ifs.append(exp.If(this=cond, true=result))
5776
5777        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5778        return self.sql(case)
def semanticview_sql(self, expression: sqlglot.expressions.properties.SemanticView) -> str:
5780    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5781        this = self.sql(expression, "this")
5782        this = self.seg(this, sep="")
5783        dimensions = self.expressions(
5784            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5785        )
5786        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5787        metrics = self.expressions(
5788            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5789        )
5790        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5791        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
5792        facts = self.seg(f"FACTS {facts}") if facts else ""
5793        where = self.sql(expression, "where")
5794        where = self.seg(f"WHERE {where}") if where else ""
5795        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
5796        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
def getextract_sql(self, expression: sqlglot.expressions.temporal.GetExtract) -> str:
5798    def getextract_sql(self, expression: exp.GetExtract) -> str:
5799        this = expression.this
5800        expr = expression.expression
5801
5802        if not this.type or not expression.type:
5803            import sqlglot.optimizer.annotate_types
5804
5805            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
5806
5807        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
5808            return self.sql(exp.Bracket(this=this, expressions=[expr]))
5809
5810        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def datefromunixdate_sql(self, expression: sqlglot.expressions.temporal.DateFromUnixDate) -> str:
5812    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
5813        return self.sql(
5814            exp.DateAdd(
5815                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5816                expression=expression.this,
5817                unit=exp.var("DAY"),
5818            )
5819        )
def space_sql( self: Generator, expression: sqlglot.expressions.string.Space) -> str:
5821    def space_sql(self: Generator, expression: exp.Space) -> str:
5822        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
def buildproperty_sql(self, expression: sqlglot.expressions.properties.BuildProperty) -> str:
5824    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
5825        return f"BUILD {self.sql(expression, 'this')}"
def refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
5827    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
5828        method = self.sql(expression, "method")
5829        kind = expression.args.get("kind")
5830        if not kind:
5831            return f"REFRESH {method}"
5832
5833        every = self.sql(expression, "every")
5834        unit = self.sql(expression, "unit")
5835        every = f" EVERY {every} {unit}" if every else ""
5836        starts = self.sql(expression, "starts")
5837        starts = f" STARTS {starts}" if starts else ""
5838
5839        return f"REFRESH {method} ON {kind}{every}{starts}"
def modelattribute_sql(self, expression: sqlglot.expressions.query.ModelAttribute) -> str:
5841    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
5842        self.unsupported("The model!attribute syntax is not supported")
5843        return ""
def directorystage_sql(self, expression: sqlglot.expressions.dml.DirectoryStage) -> str:
5845    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
5846        return self.func("DIRECTORY", expression.this)
def uuid_sql(self, expression: sqlglot.expressions.functions.Uuid) -> str:
5848    def uuid_sql(self, expression: exp.Uuid) -> str:
5849        is_string = expression.args.get("is_string", False)
5850        uuid_func_sql = self.func("UUID")
5851
5852        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
5853            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
5854
5855        return uuid_func_sql
def initcap_sql(self, expression: sqlglot.expressions.string.Initcap) -> str:
5857    def initcap_sql(self, expression: exp.Initcap) -> str:
5858        delimiters = expression.expression
5859
5860        if delimiters:
5861            # do not generate delimiters arg if we are round-tripping from default delimiters
5862            if (
5863                delimiters.is_string
5864                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
5865            ):
5866                delimiters = None
5867            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
5868                self.unsupported("INITCAP does not support custom delimiters")
5869                delimiters = None
5870
5871        return self.func("INITCAP", expression.this, delimiters)
def localtime_sql(self, expression: sqlglot.expressions.temporal.Localtime) -> str:
5873    def localtime_sql(self, expression: exp.Localtime) -> str:
5874        this = expression.this
5875        return self.func("LOCALTIME", this) if this else "LOCALTIME"
def localtimestamp_sql(self, expression: sqlglot.expressions.temporal.Localtimestamp) -> str:
5877    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
5878        this = expression.this
5879        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
def weekstart_sql(self, expression: sqlglot.expressions.functions.WeekStart) -> str:
5881    def weekstart_sql(self, expression: exp.WeekStart) -> str:
5882        this = expression.this.name.upper()
5883        if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
5884            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
5885            return "WEEK"
5886
5887        return self.func("WEEK", expression.this)
def chr_sql( self, expression: sqlglot.expressions.string.Chr, name: str = 'CHR') -> str:
5889    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
5890        this = self.expressions(expression)
5891        charset = self.sql(expression, "charset")
5892        using = f" USING {charset}" if charset else ""
5893        return self.func(name, this + using)
def block_sql(self, expression: sqlglot.expressions.query.Block) -> str:
5895    def block_sql(self, expression: exp.Block) -> str:
5896        expressions = self.expressions(expression, sep="; ", flat=True)
5897        return f"{expressions}" if expressions else ""
def storedprocedure_sql(self, expression: sqlglot.expressions.query.StoredProcedure) -> str:
5899    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
5900        self.unsupported("Unsupported Stored Procedure syntax")
5901        return ""
def ifblock_sql(self, expression: sqlglot.expressions.query.IfBlock) -> str:
5903    def ifblock_sql(self, expression: exp.IfBlock) -> str:
5904        self.unsupported("Unsupported If block syntax")
5905        return ""
def whileblock_sql(self, expression: sqlglot.expressions.query.WhileBlock) -> str:
5907    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
5908        self.unsupported("Unsupported While block syntax")
5909        return ""
def execute_sql(self, expression: sqlglot.expressions.ddl.Execute) -> str:
5911    def execute_sql(self, expression: exp.Execute) -> str:
5912        self.unsupported("Unsupported Execute syntax")
5913        return ""
def executesql_sql(self, expression: sqlglot.expressions.ddl.ExecuteSql) -> str:
5915    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
5916        self.unsupported("Unsupported Execute syntax")
5917        return ""
def altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str:
5919    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
5920        props = self.expressions(expression, sep=" ")
5921        return f"MODIFY {props}"
def usingproperty_sql(self, expression: sqlglot.expressions.properties.UsingProperty) -> str:
5923    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
5924        kind = expression.args.get("kind")
5925        return f"USING {kind} {self.sql(expression, 'this')}"
def renameindex_sql(self, expression: sqlglot.expressions.ddl.RenameIndex) -> str:
5927    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
5928        this = self.sql(expression, "this")
5929        to = self.sql(expression, "to")
5930        return f"RENAME INDEX {this} TO {to}"