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 ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 468    SUPPORTS_MODIFY_COLUMN = False
 469
 470    # Whether the LikeProperty needs to be specified inside of the schema clause
 471    LIKE_PROPERTY_INSIDE_SCHEMA = False
 472
 473    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 474    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 475    MULTI_ARG_DISTINCT = True
 476
 477    # Whether the JSON extraction operators expect a value of type JSON
 478    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 479
 480    # Whether bracketed keys like ["foo"] are supported in JSON paths
 481    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 482
 483    # Whether to escape keys using single quotes in JSON paths
 484    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 485
 486    # The JSONPathPart expressions supported by this dialect
 487    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 488
 489    # Whether any(f(x) for x in array) can be implemented by this dialect
 490    CAN_IMPLEMENT_ARRAY_ANY = False
 491
 492    # Whether the function TO_NUMBER is supported
 493    SUPPORTS_TO_NUMBER = True
 494
 495    # Whether EXCLUDE in window specification is supported
 496    SUPPORTS_WINDOW_EXCLUDE = False
 497
 498    # Whether or not set op modifiers apply to the outer set op or select.
 499    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 500    # True means limit 1 happens after the set op, False means it it happens on y.
 501    SET_OP_MODIFIERS = True
 502
 503    # Whether parameters from COPY statement are wrapped in parentheses
 504    COPY_PARAMS_ARE_WRAPPED = True
 505
 506    # Whether values of params are set with "=" token or empty space
 507    COPY_PARAMS_EQ_REQUIRED = False
 508
 509    # Whether COPY statement has INTO keyword
 510    COPY_HAS_INTO_KEYWORD = True
 511
 512    # Whether the conditional TRY(expression) function is supported
 513    TRY_SUPPORTED = True
 514
 515    # Whether the UESCAPE syntax in unicode strings is supported
 516    SUPPORTS_UESCAPE = True
 517
 518    # Function used to replace escaped unicode codes in unicode strings
 519    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 520
 521    # The keyword to use when generating a star projection with excluded columns
 522    STAR_EXCEPT = "EXCEPT"
 523
 524    # The HEX function name
 525    HEX_FUNC = "HEX"
 526
 527    # The keywords to use when prefixing & separating WITH based properties
 528    WITH_PROPERTIES_PREFIX = "WITH"
 529
 530    # Whether to quote the generated expression of exp.JsonPath
 531    QUOTE_JSON_PATH = True
 532
 533    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 534    PAD_FILL_PATTERN_IS_REQUIRED = False
 535
 536    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 537    SUPPORTS_EXPLODING_PROJECTIONS = True
 538
 539    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 540    ARRAY_CONCAT_IS_VAR_LEN = True
 541
 542    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 543    SUPPORTS_CONVERT_TIMEZONE = False
 544
 545    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 546    SUPPORTS_MEDIAN = True
 547
 548    # Whether UNIX_SECONDS(timestamp) is supported
 549    SUPPORTS_UNIX_SECONDS = False
 550
 551    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 552    ALTER_SET_WRAPPED = False
 553
 554    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 555    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 556    # TODO: The normalization should be done by default once we've tested it across all dialects.
 557    NORMALIZE_EXTRACT_DATE_PARTS = False
 558
 559    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 560    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 561
 562    # The function name of the exp.ArraySize expression
 563    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 564
 565    # The syntax to use when altering the type of a column
 566    ALTER_SET_TYPE = "SET DATA TYPE"
 567
 568    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 569    # None -> Doesn't support it at all
 570    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 571    # True (Postgres) -> Explicitly requires it
 572    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 573
 574    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 575    SUPPORTS_DECODE_CASE = True
 576
 577    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 578    SUPPORTS_BETWEEN_FLAGS = False
 579
 580    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 581    SUPPORTS_LIKE_QUANTIFIERS = True
 582
 583    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 584    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 585
 586    # Whether to include the VARIABLE keyword for SET assignments
 587    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 588
 589    # The keyword to use for default value assignment in DECLARE statements
 590    DECLARE_DEFAULT_ASSIGNMENT = "="
 591
 592    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 593    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 594    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 595    UPDATE_STATEMENT_SUPPORTS_FROM = True
 596
 597    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 598    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 599
 600    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 601    # - Snowflake: DROP ICEBERG TABLE a.b;
 602    # - DuckDB:    DROP TABLE a.b;
 603    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 604
 605    TYPE_MAPPING: t.ClassVar = {
 606        exp.DType.DATETIME2: "TIMESTAMP",
 607        exp.DType.NCHAR: "CHAR",
 608        exp.DType.NVARCHAR: "VARCHAR",
 609        exp.DType.MEDIUMTEXT: "TEXT",
 610        exp.DType.LONGTEXT: "TEXT",
 611        exp.DType.TINYTEXT: "TEXT",
 612        exp.DType.BLOB: "VARBINARY",
 613        exp.DType.MEDIUMBLOB: "BLOB",
 614        exp.DType.LONGBLOB: "BLOB",
 615        exp.DType.TINYBLOB: "BLOB",
 616        exp.DType.INET: "INET",
 617        exp.DType.ROWVERSION: "VARBINARY",
 618        exp.DType.SMALLDATETIME: "TIMESTAMP",
 619    }
 620
 621    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 622
 623    TIME_PART_SINGULARS: t.ClassVar = {
 624        "MICROSECONDS": "MICROSECOND",
 625        "SECONDS": "SECOND",
 626        "MINUTES": "MINUTE",
 627        "HOURS": "HOUR",
 628        "DAYS": "DAY",
 629        "WEEKS": "WEEK",
 630        "MONTHS": "MONTH",
 631        "QUARTERS": "QUARTER",
 632        "YEARS": "YEAR",
 633    }
 634
 635    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 636        "cluster": lambda self, e: self.sql(e, "cluster"),
 637        "distribute": lambda self, e: self.sql(e, "distribute"),
 638        "sort": lambda self, e: self.sql(e, "sort"),
 639        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 640    }
 641
 642    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 643
 644    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 645
 646    PARAMETER_TOKEN = "@"
 647    NAMED_PLACEHOLDER_TOKEN = ":"
 648
 649    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 650
 651    PROPERTIES_LOCATION: t.ClassVar = {
 652        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 653        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 654        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 655        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 656        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 657        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 658        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 659        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 660        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 661        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 662        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 663        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 664        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 665        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 666        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 667        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 668        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 669        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 670        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 671        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 672        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 673        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 674        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 675        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 676        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 677        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 678        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 679        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 680        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 681        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 682        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 683        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 684        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 685        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 686        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 687        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 688        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 689        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 690        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 691        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 692        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 693        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 694        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 695        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 696        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 697        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 698        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 699        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 700        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 701        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 702        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 703        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 704        exp.LogProperty: exp.Properties.Location.POST_NAME,
 705        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 706        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 707        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 708        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 709        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 710        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 711        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 712        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 713        exp.Order: exp.Properties.Location.POST_SCHEMA,
 714        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 715        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 716        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 717        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 718        exp.Property: exp.Properties.Location.POST_WITH,
 719        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 720        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 721        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 722        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 723        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 724        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 725        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 726        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 727        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 728        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 729        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 730        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 731        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 732        exp.Set: exp.Properties.Location.POST_SCHEMA,
 733        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 734        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 735        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 736        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 737        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 738        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 739        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 740        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 741        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 742        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 743        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 744        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 745        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 746        exp.Tags: exp.Properties.Location.POST_WITH,
 747        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 748        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 749        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 750        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 751        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 752        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 753        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 754        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 755        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 756        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 757        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 758        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 759        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 760        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 761        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 762        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 763        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 764    }
 765
 766    # Keywords that can't be used as unquoted identifier names
 767    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 768
 769    # Exprs whose comments are separated from them for better formatting
 770    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 771        exp.Command,
 772        exp.Create,
 773        exp.Describe,
 774        exp.Delete,
 775        exp.Drop,
 776        exp.From,
 777        exp.Insert,
 778        exp.Join,
 779        exp.MultitableInserts,
 780        exp.Order,
 781        exp.Group,
 782        exp.Having,
 783        exp.Select,
 784        exp.SetOperation,
 785        exp.Update,
 786        exp.Where,
 787        exp.With,
 788    )
 789
 790    # Exprs that should not have their comments generated in maybe_comment
 791    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 792        exp.Binary,
 793        exp.SetOperation,
 794    )
 795
 796    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 797    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 798        exp.Column,
 799        exp.Literal,
 800        exp.Neg,
 801        exp.Paren,
 802    )
 803
 804    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 805        exp.DType.NVARCHAR,
 806        exp.DType.VARCHAR,
 807        exp.DType.CHAR,
 808        exp.DType.NCHAR,
 809    }
 810
 811    # Exprs that need to have all CTEs under them bubbled up to them
 812    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 813
 814    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 815
 816    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 817
 818    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 819
 820    __slots__ = (
 821        "pretty",
 822        "identify",
 823        "normalize",
 824        "pad",
 825        "_indent",
 826        "normalize_functions",
 827        "unsupported_level",
 828        "max_unsupported",
 829        "leading_comma",
 830        "max_text_width",
 831        "comments",
 832        "dialect",
 833        "unsupported_messages",
 834        "_escaped_quote_end",
 835        "_escaped_byte_quote_end",
 836        "_escaped_identifier_end",
 837        "_next_name",
 838        "_identifier_start",
 839        "_identifier_end",
 840        "_quote_json_path_key_using_brackets",
 841        "_dispatch",
 842    )
 843
 844    def __init__(
 845        self,
 846        pretty: bool | int | None = None,
 847        identify: str | bool = False,
 848        normalize: bool = False,
 849        pad: int = 2,
 850        indent: int = 2,
 851        normalize_functions: str | bool | None = None,
 852        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 853        max_unsupported: int = 3,
 854        leading_comma: bool = False,
 855        max_text_width: int = 80,
 856        comments: bool = True,
 857        dialect: DialectType = None,
 858    ):
 859        import sqlglot
 860        import sqlglot.dialects.dialect
 861
 862        self.pretty = pretty if pretty is not None else sqlglot.pretty
 863        self.identify = identify
 864        self.normalize = normalize
 865        self.pad = pad
 866        self._indent = indent
 867        self.unsupported_level = unsupported_level
 868        self.max_unsupported = max_unsupported
 869        self.leading_comma = leading_comma
 870        self.max_text_width = max_text_width
 871        self.comments = comments
 872        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 873
 874        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 875        self.normalize_functions = (
 876            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 877        )
 878
 879        self.unsupported_messages: list[str] = []
 880        self._escaped_quote_end: str = (
 881            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 882        )
 883        self._escaped_byte_quote_end: str = (
 884            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 885            if self.dialect.BYTE_END
 886            else ""
 887        )
 888        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 889
 890        self._next_name = name_sequence("_t")
 891
 892        self._identifier_start = self.dialect.IDENTIFIER_START
 893        self._identifier_end = self.dialect.IDENTIFIER_END
 894
 895        self._quote_json_path_key_using_brackets = True
 896
 897        cls = type(self)
 898        dispatch = _DISPATCH_CACHE.get(cls)
 899        if dispatch is None:
 900            dispatch = _build_dispatch(cls)
 901            _DISPATCH_CACHE[cls] = dispatch
 902        self._dispatch = dispatch
 903
 904    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 905        """
 906        Generates the SQL string corresponding to the given syntax tree.
 907
 908        Args:
 909            expression: The syntax tree.
 910            copy: Whether to copy the expression. The generator performs mutations so
 911                it is safer to copy.
 912
 913        Returns:
 914            The SQL string corresponding to `expression`.
 915        """
 916        if copy:
 917            expression = expression.copy()
 918
 919        expression = self.preprocess(expression)
 920
 921        self.unsupported_messages = []
 922        sql = self.sql(expression).strip()
 923
 924        if self.pretty:
 925            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 926
 927        if self.unsupported_level == ErrorLevel.IGNORE:
 928            return sql
 929
 930        if self.unsupported_level == ErrorLevel.WARN:
 931            for msg in self.unsupported_messages:
 932                logger.warning(msg)
 933        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 934            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 935
 936        return sql
 937
 938    def preprocess(self, expression: exp.Expr) -> exp.Expr:
 939        """Apply generic preprocessing transformations to a given expression."""
 940        expression = self._move_ctes_to_top_level(expression)
 941
 942        if self.ENSURE_BOOLS:
 943            import sqlglot.transforms
 944
 945            expression = sqlglot.transforms.ensure_bools(expression)
 946
 947        return expression
 948
 949    def _move_ctes_to_top_level(self, expression: E) -> E:
 950        if (
 951            not expression.parent
 952            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 953            and any(node.parent is not expression for node in expression.find_all(exp.With))
 954        ):
 955            import sqlglot.transforms
 956
 957            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
 958        return expression
 959
 960    def unsupported(self, message: str) -> None:
 961        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 962            raise UnsupportedError(message)
 963        self.unsupported_messages.append(message)
 964
 965    def sep(self, sep: str = " ") -> str:
 966        return f"{sep.strip()}\n" if self.pretty else sep
 967
 968    def seg(self, sql: str, sep: str = " ") -> str:
 969        return f"{self.sep(sep)}{sql}"
 970
 971    def sanitize_comment(self, comment: str) -> str:
 972        comment = " " + comment if comment[0].strip() else comment
 973        comment = comment + " " if comment[-1].strip() else comment
 974
 975        # Escape block comment markers to prevent premature closure or unintended nesting.
 976        # This is necessary because single-line comments (--) are converted to block comments
 977        # (/* */) on output, and any */ in the original text would close the comment early.
 978        comment = comment.replace("*/", "* /").replace("/*", "/ *")
 979
 980        return comment
 981
 982    def maybe_comment(
 983        self,
 984        sql: str,
 985        expression: exp.Expr | None = None,
 986        comments: list[str] | None = None,
 987        separated: bool = False,
 988    ) -> str:
 989        comments = (
 990            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 991            if self.comments
 992            else None
 993        )
 994
 995        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 996            return sql
 997
 998        comments_sql = " ".join(
 999            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
1000        )
1001
1002        if not comments_sql:
1003            return sql
1004
1005        comments_sql = self._replace_line_breaks(comments_sql)
1006
1007        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1008            return (
1009                f"{self.sep()}{comments_sql}{sql}"
1010                if not sql or sql[0].isspace()
1011                else f"{comments_sql}{self.sep()}{sql}"
1012            )
1013
1014        return f"{sql} {comments_sql}"
1015
1016    def wrap(self, expression: exp.Expr | str) -> str:
1017        this_sql = (
1018            self.sql(expression)
1019            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1020            else self.sql(expression, "this")
1021        )
1022        if not this_sql:
1023            return "()"
1024
1025        this_sql = self.indent(this_sql, level=1, pad=0)
1026        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1027
1028    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1029        original = self.identify
1030        self.identify = False
1031        result = func(*args, **kwargs)
1032        self.identify = original
1033        return result
1034
1035    def normalize_func(self, name: str) -> str:
1036        if self.normalize_functions == "upper" or self.normalize_functions is True:
1037            return name.upper()
1038        if self.normalize_functions == "lower":
1039            return name.lower()
1040        return name
1041
1042    def indent(
1043        self,
1044        sql: str,
1045        level: int = 0,
1046        pad: int | None = None,
1047        skip_first: bool = False,
1048        skip_last: bool = False,
1049    ) -> str:
1050        if not self.pretty or not sql:
1051            return sql
1052
1053        pad = self.pad if pad is None else pad
1054        lines = sql.split("\n")
1055
1056        return "\n".join(
1057            (
1058                line
1059                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1060                else f"{' ' * (level * self._indent + pad)}{line}"
1061            )
1062            for i, line in enumerate(lines)
1063        )
1064
1065    def sql(
1066        self,
1067        expression: str | exp.Expr | None,
1068        key: str | None = None,
1069        comment: bool = True,
1070    ) -> str:
1071        if not expression:
1072            return ""
1073
1074        if isinstance(expression, str):
1075            return expression
1076
1077        if key:
1078            value = expression.args.get(key)
1079            if value:
1080                return self.sql(value)
1081            return ""
1082
1083        handler = self._dispatch.get(expression.__class__)
1084
1085        if handler:
1086            sql = handler(self, expression)
1087        elif isinstance(expression, exp.Func):
1088            sql = self.function_fallback_sql(expression)
1089        elif isinstance(expression, exp.Property):
1090            sql = self.property_sql(expression)
1091        else:
1092            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1093
1094        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1095
1096    def uncache_sql(self, expression: exp.Uncache) -> str:
1097        table = self.sql(expression, "this")
1098        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1099        return f"UNCACHE TABLE{exists_sql} {table}"
1100
1101    def cache_sql(self, expression: exp.Cache) -> str:
1102        lazy = " LAZY" if expression.args.get("lazy") else ""
1103        table = self.sql(expression, "this")
1104        options = expression.args.get("options")
1105        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1106        sql = self.sql(expression, "expression")
1107        sql = f" AS{self.sep()}{sql}" if sql else ""
1108        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1109        return self.prepend_ctes(expression, sql)
1110
1111    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1112        default = "DEFAULT " if expression.args.get("default") else ""
1113        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1114
1115    def column_parts(self, expression: exp.Column) -> str:
1116        return ".".join(
1117            self.sql(part)
1118            for part in (
1119                expression.args.get("catalog"),
1120                expression.args.get("db"),
1121                expression.args.get("table"),
1122                expression.args.get("this"),
1123            )
1124            if part
1125        )
1126
1127    def column_sql(self, expression: exp.Column) -> str:
1128        join_mark = " (+)" if expression.args.get("join_mark") else ""
1129
1130        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1131            join_mark = ""
1132            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1133
1134        return f"{self.column_parts(expression)}{join_mark}"
1135
1136    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1137        return self.column_sql(expression)
1138
1139    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1140        this = self.sql(expression, "this")
1141        this = f" {this}" if this else ""
1142        position = self.sql(expression, "position")
1143        return f"{position}{this}"
1144
1145    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1146        column = self.sql(expression, "this")
1147        kind = self.sql(expression, "kind")
1148        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1149        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1150        kind = f"{sep}{kind}" if kind else ""
1151        constraints = f" {constraints}" if constraints else ""
1152        position = self.sql(expression, "position")
1153        position = f" {position}" if position else ""
1154
1155        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1156            kind = ""
1157
1158        return f"{exists}{column}{kind}{constraints}{position}"
1159
1160    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1161        this = self.sql(expression, "this")
1162        kind_sql = self.sql(expression, "kind").strip()
1163        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1164
1165    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1166        this = self.sql(expression, "this")
1167        if expression.args.get("not_null"):
1168            persisted = " PERSISTED NOT NULL"
1169        elif expression.args.get("persisted"):
1170            persisted = " PERSISTED"
1171        else:
1172            persisted = ""
1173
1174        return f"AS {this}{persisted}"
1175
1176    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1177        return self.token_sql(TokenType.AUTO_INCREMENT)
1178
1179    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1180        if isinstance(expression.this, list):
1181            this = self.wrap(self.expressions(expression, key="this", flat=True))
1182        else:
1183            this = self.sql(expression, "this")
1184
1185        return f"COMPRESS {this}"
1186
1187    def generatedasidentitycolumnconstraint_sql(
1188        self, expression: exp.GeneratedAsIdentityColumnConstraint
1189    ) -> str:
1190        this = ""
1191        if expression.this is not None:
1192            on_null = " ON NULL" if expression.args.get("on_null") else ""
1193            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1194
1195        start = expression.args.get("start")
1196        start = f"START WITH {start}" if start else ""
1197        increment = expression.args.get("increment")
1198        increment = f" INCREMENT BY {increment}" if increment else ""
1199        minvalue = expression.args.get("minvalue")
1200        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1201        maxvalue = expression.args.get("maxvalue")
1202        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1203        cycle = expression.args.get("cycle")
1204        cycle_sql = ""
1205
1206        if cycle is not None:
1207            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1208            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1209
1210        sequence_opts = ""
1211        if start or increment or cycle_sql:
1212            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1213            sequence_opts = f" ({sequence_opts.strip()})"
1214
1215        expr = self.sql(expression, "expression")
1216        expr = f"({expr})" if expr else "IDENTITY"
1217
1218        return f"GENERATED{this} AS {expr}{sequence_opts}"
1219
1220    def generatedasrowcolumnconstraint_sql(
1221        self, expression: exp.GeneratedAsRowColumnConstraint
1222    ) -> str:
1223        start = "START" if expression.args.get("start") else "END"
1224        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1225        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1226
1227    def periodforsystemtimeconstraint_sql(
1228        self, expression: exp.PeriodForSystemTimeConstraint
1229    ) -> str:
1230        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1231
1232    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1233        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1234
1235    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1236        desc = expression.args.get("desc")
1237        if desc is not None:
1238            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1239        options = self.expressions(expression, key="options", flat=True, sep=" ")
1240        options = f" {options}" if options else ""
1241        return f"PRIMARY KEY{options}"
1242
1243    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1244        this = self.sql(expression, "this")
1245        this = f" {this}" if this else ""
1246        index_type = expression.args.get("index_type")
1247        index_type = f" USING {index_type}" if index_type else ""
1248        on_conflict = self.sql(expression, "on_conflict")
1249        on_conflict = f" {on_conflict}" if on_conflict else ""
1250        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1251        options = self.expressions(expression, key="options", flat=True, sep=" ")
1252        options = f" {options}" if options else ""
1253        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1254
1255    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1256        input_ = expression.args.get("input_")
1257        output = expression.args.get("output")
1258        variadic = expression.args.get("variadic")
1259
1260        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1261        if variadic:
1262            return "VARIADIC"
1263
1264        if input_ and output:
1265            return f"IN{self.INOUT_SEPARATOR}OUT"
1266        if input_:
1267            return "IN"
1268        if output:
1269            return "OUT"
1270
1271        return ""
1272
1273    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1274        return self.sql(expression, "this")
1275
1276    def create_sql(self, expression: exp.Create) -> str:
1277        kind = self.sql(expression, "kind")
1278        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1279
1280        properties = expression.args.get("properties")
1281
1282        if (
1283            kind == "TRIGGER"
1284            and properties
1285            and properties.expressions
1286            and isinstance(properties.expressions[0], exp.TriggerProperties)
1287            and properties.expressions[0].args.get("constraint")
1288        ):
1289            kind = f"CONSTRAINT {kind}"
1290
1291        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1292
1293        this = self.createable_sql(expression, properties_locs)
1294
1295        properties_sql = ""
1296        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1297            exp.Properties.Location.POST_WITH
1298        ):
1299            props_ast = exp.Properties(
1300                expressions=[
1301                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1302                    *properties_locs[exp.Properties.Location.POST_WITH],
1303                ]
1304            )
1305            props_ast.parent = expression
1306            properties_sql = self.sql(props_ast)
1307
1308            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1309                properties_sql = self.sep() + properties_sql
1310            elif not self.pretty:
1311                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1312                properties_sql = f" {properties_sql}"
1313
1314        begin = " BEGIN" if expression.args.get("begin") else ""
1315
1316        expression_sql = self.sql(expression, "expression")
1317        if expression_sql:
1318            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1319
1320            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1321                postalias_props_sql = ""
1322                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1323                    postalias_props_sql = self.properties(
1324                        exp.Properties(
1325                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1326                        ),
1327                        wrapped=False,
1328                    )
1329                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1330                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1331
1332        postindex_props_sql = ""
1333        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1334            postindex_props_sql = self.properties(
1335                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1336                wrapped=False,
1337                prefix=" ",
1338            )
1339
1340        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1341        indexes = f" {indexes}" if indexes else ""
1342        index_sql = indexes + postindex_props_sql
1343
1344        replace = " OR REPLACE" if expression.args.get("replace") else ""
1345        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1346        unique = " UNIQUE" if expression.args.get("unique") else ""
1347
1348        clustered = expression.args.get("clustered")
1349        if clustered is None:
1350            clustered_sql = ""
1351        elif clustered:
1352            clustered_sql = " CLUSTERED COLUMNSTORE"
1353        else:
1354            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1355
1356        postcreate_props_sql = ""
1357        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1358            postcreate_props_sql = self.properties(
1359                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1360                sep=" ",
1361                prefix=" ",
1362                wrapped=False,
1363            )
1364
1365        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1366
1367        postexpression_props_sql = ""
1368        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1369            postexpression_props_sql = self.properties(
1370                exp.Properties(
1371                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1372                ),
1373                sep=" ",
1374                prefix=" ",
1375                wrapped=False,
1376            )
1377
1378        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1379        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1380        no_schema_binding = (
1381            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1382        )
1383
1384        clone = self.sql(expression, "clone")
1385        clone = f" {clone}" if clone else ""
1386
1387        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1388            properties_expression = f"{expression_sql}{properties_sql}"
1389        else:
1390            properties_expression = f"{properties_sql}{expression_sql}"
1391
1392        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1393        return self.prepend_ctes(expression, expression_sql)
1394
1395    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1396        start = self.sql(expression, "start")
1397        start = f"START WITH {start}" if start else ""
1398        increment = self.sql(expression, "increment")
1399        increment = f" INCREMENT BY {increment}" if increment else ""
1400        minvalue = self.sql(expression, "minvalue")
1401        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1402        maxvalue = self.sql(expression, "maxvalue")
1403        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1404        owned = self.sql(expression, "owned")
1405        owned = f" OWNED BY {owned}" if owned else ""
1406
1407        cache = expression.args.get("cache")
1408        if cache is None:
1409            cache_str = ""
1410        elif cache is True:
1411            cache_str = " CACHE"
1412        else:
1413            cache_str = f" CACHE {cache}"
1414
1415        options = self.expressions(expression, key="options", flat=True, sep=" ")
1416        options = f" {options}" if options else ""
1417
1418        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1419
1420    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1421        timing = expression.args.get("timing", "")
1422        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1423        timing_events = f"{timing} {events}".strip() if timing or events else ""
1424
1425        parts = [timing_events, "ON", self.sql(expression, "table")]
1426
1427        if referenced_table := expression.args.get("referenced_table"):
1428            parts.extend(["FROM", self.sql(referenced_table)])
1429
1430        if deferrable := expression.args.get("deferrable"):
1431            parts.append(deferrable)
1432
1433        if initially := expression.args.get("initially"):
1434            parts.append(f"INITIALLY {initially}")
1435
1436        if referencing := expression.args.get("referencing"):
1437            parts.append(self.sql(referencing))
1438
1439        if for_each := expression.args.get("for_each"):
1440            parts.append(f"FOR EACH {for_each}")
1441
1442        if when := expression.args.get("when"):
1443            parts.append(f"WHEN ({self.sql(when)})")
1444
1445        parts.append(self.sql(expression, "execute"))
1446
1447        return self.sep().join(parts)
1448
1449    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1450        parts = []
1451
1452        if old_alias := expression.args.get("old"):
1453            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1454
1455        if new_alias := expression.args.get("new"):
1456            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1457
1458        return f"REFERENCING {' '.join(parts)}"
1459
1460    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1461        columns = expression.args.get("columns")
1462        if columns:
1463            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1464
1465        return self.sql(expression, "this")
1466
1467    def clone_sql(self, expression: exp.Clone) -> str:
1468        this = self.sql(expression, "this")
1469        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1470        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1471        return f"{shallow}{keyword} {this}"
1472
1473    def describe_sql(self, expression: exp.Describe) -> str:
1474        style = expression.args.get("style")
1475        style = f" {style}" if style else ""
1476        partition = self.sql(expression, "partition")
1477        partition = f" {partition}" if partition else ""
1478        format = self.sql(expression, "format")
1479        format = f" {format}" if format else ""
1480        as_json = " AS JSON" if expression.args.get("as_json") else ""
1481
1482        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1483
1484    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1485        tag = self.sql(expression, "tag")
1486        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1487
1488    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1489        with_ = self.sql(expression, "with_")
1490        if with_:
1491            sql = f"{with_}{self.sep()}{sql}"
1492        return sql
1493
1494    def with_sql(self, expression: exp.With) -> str:
1495        sql = self.expressions(expression, flat=True)
1496        recursive = (
1497            "RECURSIVE "
1498            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1499            else ""
1500        )
1501        search = self.sql(expression, "search")
1502        search = f" {search}" if search else ""
1503
1504        return f"WITH {recursive}{sql}{search}"
1505
1506    def cte_sql(self, expression: exp.CTE) -> str:
1507        alias = expression.args.get("alias")
1508        if alias:
1509            alias.add_comments(expression.pop_comments())
1510
1511        alias_sql = self.sql(expression, "alias")
1512
1513        materialized = expression.args.get("materialized")
1514        if materialized is False:
1515            materialized = "NOT MATERIALIZED "
1516        elif materialized:
1517            materialized = "MATERIALIZED "
1518
1519        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1520        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1521
1522        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1523
1524    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1525        alias = self.sql(expression, "this")
1526        columns = self.expressions(expression, key="columns", flat=True)
1527        columns = f"({columns})" if columns else ""
1528
1529        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1530            columns = ""
1531            self.unsupported("Named columns are not supported in table alias.")
1532
1533        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1534            alias = self._next_name()
1535
1536        return f"{alias}{columns}"
1537
1538    def bitstring_sql(self, expression: exp.BitString) -> str:
1539        this = self.sql(expression, "this")
1540        if self.dialect.BIT_START:
1541            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1542        return f"{int(this, 2)}"
1543
1544    def hexstring_sql(
1545        self, expression: exp.HexString, binary_function_repr: str | None = None
1546    ) -> str:
1547        this = self.sql(expression, "this")
1548        is_integer_type = expression.args.get("is_integer")
1549
1550        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1551            not self.dialect.HEX_START and not binary_function_repr
1552        ):
1553            # Integer representation will be returned if:
1554            # - The read dialect treats the hex value as integer literal but not the write
1555            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1556            return f"{int(this, 16)}"
1557
1558        if not is_integer_type:
1559            # Read dialect treats the hex value as BINARY/BLOB
1560            if binary_function_repr:
1561                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1562                return self.func(binary_function_repr, exp.Literal.string(this))
1563            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1564                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1565                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1566
1567        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1568
1569    def bytestring_sql(self, expression: exp.ByteString) -> str:
1570        this = self.sql(expression, "this")
1571        if self.dialect.BYTE_START:
1572            escaped_byte_string = self.escape_str(
1573                this,
1574                escape_backslash=False,
1575                delimiter=self.dialect.BYTE_END,
1576                escaped_delimiter=self._escaped_byte_quote_end,
1577                is_byte_string=True,
1578            )
1579            is_bytes = expression.args.get("is_bytes", False)
1580            delimited_byte_string = (
1581                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1582            )
1583            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1584                return self.sql(
1585                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1586                )
1587            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1588                return self.sql(
1589                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1590                )
1591
1592            return delimited_byte_string
1593        return this
1594
1595    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1596        this = self.sql(expression, "this")
1597        escape = expression.args.get("escape")
1598
1599        if self.dialect.UNICODE_START:
1600            escape_substitute = r"\\\1"
1601            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1602        else:
1603            escape_substitute = r"\\u\1"
1604            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1605
1606        if escape:
1607            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1608            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1609        else:
1610            escape_pattern = ESCAPED_UNICODE_RE
1611            escape_sql = ""
1612
1613        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1614            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1615
1616        return f"{left_quote}{this}{right_quote}{escape_sql}"
1617
1618    def rawstring_sql(self, expression: exp.RawString) -> str:
1619        string = expression.this
1620        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1621            string = string.replace("\\", "\\\\")
1622
1623        string = self.escape_str(string, escape_backslash=False)
1624        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1625
1626    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1627        this = self.sql(expression, "this")
1628        specifier = self.sql(expression, "expression")
1629        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1630        return f"{this}{specifier}"
1631
1632    def datatype_sql(self, expression: exp.DataType) -> str:
1633        nested = ""
1634        values = ""
1635
1636        expr_nested = expression.args.get("nested")
1637        interior = (
1638            self.expressions(
1639                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1640            )
1641            if expr_nested and self.pretty
1642            else self.expressions(expression, flat=True)
1643        )
1644
1645        type_value = expression.this
1646        if type_value in self.UNSUPPORTED_TYPES:
1647            self.unsupported(
1648                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1649            )
1650
1651        type_sql: t.Any = ""
1652        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1653            type_sql = self.sql(expression, "kind")
1654        elif type_value == exp.DType.CHARACTER_SET:
1655            return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}"
1656        else:
1657            type_sql = (
1658                self.TYPE_MAPPING.get(type_value, type_value.value)
1659                if isinstance(type_value, exp.DType)
1660                else type_value
1661            )
1662
1663        if interior:
1664            if expr_nested:
1665                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1666                if expression.args.get("values") is not None:
1667                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1668                    values = self.expressions(expression, key="values", flat=True)
1669                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1670            elif type_value == exp.DType.INTERVAL:
1671                nested = f" {interior}"
1672            else:
1673                nested = f"({interior})"
1674
1675        type_sql = f"{type_sql}{nested}{values}"
1676        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1677            exp.DType.TIMETZ,
1678            exp.DType.TIMESTAMPTZ,
1679        ):
1680            type_sql = f"{type_sql} WITH TIME ZONE"
1681
1682        return type_sql
1683
1684    def directory_sql(self, expression: exp.Directory) -> str:
1685        local = "LOCAL " if expression.args.get("local") else ""
1686        row_format = self.sql(expression, "row_format")
1687        row_format = f" {row_format}" if row_format else ""
1688        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1689
1690    def delete_sql(self, expression: exp.Delete) -> str:
1691        hint = self.sql(expression, "hint")
1692        this = self.sql(expression, "this")
1693        this = f" FROM {this}" if this else ""
1694        using = self.expressions(expression, key="using")
1695        using = f" USING {using}" if using else ""
1696        cluster = self.sql(expression, "cluster")
1697        cluster = f" {cluster}" if cluster else ""
1698        where = self.sql(expression, "where")
1699        returning = self.sql(expression, "returning")
1700        order = self.sql(expression, "order")
1701        limit = self.sql(expression, "limit")
1702        tables = self.expressions(expression, key="tables")
1703        tables = f" {tables}" if tables else ""
1704        if self.RETURNING_END:
1705            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1706        else:
1707            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1708        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1709
1710    def drop_sql(self, expression: exp.Drop) -> str:
1711        this = self.sql(expression, "this")
1712        expressions = self.expressions(expression, flat=True)
1713        expressions = f" ({expressions})" if expressions else ""
1714        kind = expression.args["kind"]
1715        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1716        iceberg = (
1717            " ICEBERG"
1718            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1719            else ""
1720        )
1721        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1722        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1723        on_cluster = self.sql(expression, "cluster")
1724        on_cluster = f" {on_cluster}" if on_cluster else ""
1725        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1726        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1727        cascade = " CASCADE" if expression.args.get("cascade") else ""
1728        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1729        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1730        purge = " PURGE" if expression.args.get("purge") else ""
1731        sync = " SYNC" if expression.args.get("sync") else ""
1732        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}"
1733
1734    def set_operation(self, expression: exp.SetOperation) -> str:
1735        op_type = type(expression)
1736        op_name = op_type.key.upper()
1737
1738        distinct = expression.args.get("distinct")
1739        if (
1740            distinct is False
1741            and op_type in (exp.Except, exp.Intersect)
1742            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1743        ):
1744            self.unsupported(f"{op_name} ALL is not supported")
1745
1746        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1747
1748        if distinct is None:
1749            distinct = default_distinct
1750            if distinct is None:
1751                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1752
1753        if distinct is default_distinct:
1754            distinct_or_all = ""
1755        else:
1756            distinct_or_all = " DISTINCT" if distinct else " ALL"
1757
1758        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1759        side_kind = f"{side_kind} " if side_kind else ""
1760
1761        by_name = " BY NAME" if expression.args.get("by_name") else ""
1762        on = self.expressions(expression, key="on", flat=True)
1763        on = f" ON ({on})" if on else ""
1764
1765        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1766
1767    def set_operations(self, expression: exp.SetOperation) -> str:
1768        if not self.SET_OP_MODIFIERS:
1769            limit = expression.args.get("limit")
1770            order = expression.args.get("order")
1771
1772            if limit or order:
1773                select = self._move_ctes_to_top_level(
1774                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1775                )
1776
1777                if limit:
1778                    select = select.limit(limit.pop(), copy=False)
1779                if order:
1780                    select = select.order_by(order.pop(), copy=False)
1781                return self.sql(select)
1782
1783        sqls: list[str] = []
1784        stack: list[str | exp.Expr] = [expression]
1785
1786        while stack:
1787            node = stack.pop()
1788
1789            if isinstance(node, exp.SetOperation):
1790                stack.append(node.expression)
1791                stack.append(
1792                    self.maybe_comment(
1793                        self.set_operation(node), comments=node.comments, separated=True
1794                    )
1795                )
1796                stack.append(node.this)
1797            else:
1798                sqls.append(self.sql(node))
1799
1800        this = self.sep().join(sqls)
1801        this = self.query_modifiers(expression, this)
1802        return self.prepend_ctes(expression, this)
1803
1804    def fetch_sql(self, expression: exp.Fetch) -> str:
1805        direction = expression.args.get("direction")
1806        direction = f" {direction}" if direction else ""
1807        count = self.sql(expression, "count")
1808        count = f" {count}" if count else ""
1809        limit_options = self.sql(expression, "limit_options")
1810        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1811        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1812
1813    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1814        percent = " PERCENT" if expression.args.get("percent") else ""
1815        rows = " ROWS" if expression.args.get("rows") else ""
1816        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1817        if not with_ties and rows:
1818            with_ties = " ONLY"
1819        return f"{percent}{rows}{with_ties}"
1820
1821    def filter_sql(self, expression: exp.Filter) -> str:
1822        if self.AGGREGATE_FILTER_SUPPORTED:
1823            this = self.sql(expression, "this")
1824            where = self.sql(expression, "expression").strip()
1825            return f"{this} FILTER({where})"
1826
1827        agg = expression.this
1828        agg_arg = agg.this
1829        cond = expression.expression.this
1830        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1831        return self.sql(agg)
1832
1833    def hint_sql(self, expression: exp.Hint) -> str:
1834        if not self.QUERY_HINTS:
1835            self.unsupported("Hints are not supported")
1836            return ""
1837
1838        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1839
1840    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1841        using = self.sql(expression, "using")
1842        using = f" USING {using}" if using else ""
1843        columns = self.expressions(expression, key="columns", flat=True)
1844        columns = f"({columns})" if columns else ""
1845        partition_by = self.expressions(expression, key="partition_by", flat=True)
1846        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1847        where = self.sql(expression, "where")
1848        include = self.expressions(expression, key="include", flat=True)
1849        if include:
1850            include = f" INCLUDE ({include})"
1851        with_storage = self.expressions(expression, key="with_storage", flat=True)
1852        with_storage = f" WITH ({with_storage})" if with_storage else ""
1853        tablespace = self.sql(expression, "tablespace")
1854        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1855        on = self.sql(expression, "on")
1856        on = f" ON {on}" if on else ""
1857
1858        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1859
1860    def index_sql(self, expression: exp.Index) -> str:
1861        unique = "UNIQUE " if expression.args.get("unique") else ""
1862        primary = "PRIMARY " if expression.args.get("primary") else ""
1863        amp = "AMP " if expression.args.get("amp") else ""
1864        name = self.sql(expression, "this")
1865        name = f"{name} " if name else ""
1866        table = self.sql(expression, "table")
1867        table = f"{self.INDEX_ON} {table}" if table else ""
1868
1869        index = "INDEX " if not table else ""
1870
1871        params = self.sql(expression, "params")
1872        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1873
1874    def identifier_sql(self, expression: exp.Identifier) -> str:
1875        text = expression.name
1876        lower = text.lower()
1877        quoted = expression.quoted
1878        text = lower if self.normalize and not quoted else text
1879        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1880        if (
1881            quoted
1882            or self.dialect.can_quote(expression, self.identify)
1883            or lower in self.RESERVED_KEYWORDS
1884            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1885        ):
1886            text = (
1887                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
1888            )
1889        return text
1890
1891    def hex_sql(self, expression: exp.Hex) -> str:
1892        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1893        if self.dialect.HEX_LOWERCASE:
1894            text = self.func("LOWER", text)
1895
1896        return text
1897
1898    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1899        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1900        if not self.dialect.HEX_LOWERCASE:
1901            text = self.func("LOWER", text)
1902        return text
1903
1904    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1905        input_format = self.sql(expression, "input_format")
1906        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1907        output_format = self.sql(expression, "output_format")
1908        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1909        return self.sep().join((input_format, output_format))
1910
1911    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1912        string = self.sql(exp.Literal.string(expression.name))
1913        return f"{prefix}{string}"
1914
1915    def partition_sql(self, expression: exp.Partition) -> str:
1916        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1917        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1918
1919    def properties_sql(self, expression: exp.Properties) -> str:
1920        root_properties = []
1921        with_properties = []
1922
1923        for p in expression.expressions:
1924            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1925            if p_loc == exp.Properties.Location.POST_WITH:
1926                with_properties.append(p)
1927            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1928                root_properties.append(p)
1929
1930        root_props_ast = exp.Properties(expressions=root_properties)
1931        root_props_ast.parent = expression.parent
1932
1933        with_props_ast = exp.Properties(expressions=with_properties)
1934        with_props_ast.parent = expression.parent
1935
1936        root_props = self.root_properties(root_props_ast)
1937        with_props = self.with_properties(with_props_ast)
1938
1939        if root_props and with_props and not self.pretty:
1940            with_props = " " + with_props
1941
1942        return root_props + with_props
1943
1944    def root_properties(self, properties: exp.Properties) -> str:
1945        if properties.expressions:
1946            return self.expressions(properties, indent=False, sep=" ")
1947        return ""
1948
1949    def properties(
1950        self,
1951        properties: exp.Properties,
1952        prefix: str = "",
1953        sep: str = ", ",
1954        suffix: str = "",
1955        wrapped: bool = True,
1956    ) -> str:
1957        if properties.expressions:
1958            expressions = self.expressions(properties, sep=sep, indent=False)
1959            if expressions:
1960                expressions = self.wrap(expressions) if wrapped else expressions
1961                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1962        return ""
1963
1964    def with_properties(self, properties: exp.Properties) -> str:
1965        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1966
1967    def locate_properties(self, properties: exp.Properties) -> defaultdict:
1968        properties_locs = defaultdict(list)
1969        for p in properties.expressions:
1970            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1971            if p_loc != exp.Properties.Location.UNSUPPORTED:
1972                properties_locs[p_loc].append(p)
1973            else:
1974                self.unsupported(f"Unsupported property {p.key}")
1975
1976        return properties_locs
1977
1978    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1979        if isinstance(expression.this, exp.Dot):
1980            return self.sql(expression, "this")
1981        return f"'{expression.name}'" if string_key else expression.name
1982
1983    def property_sql(self, expression: exp.Property) -> str:
1984        property_cls = expression.__class__
1985        if property_cls == exp.Property:
1986            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1987
1988        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1989        if not property_name:
1990            self.unsupported(f"Unsupported property {expression.key}")
1991
1992        return f"{property_name}={self.sql(expression, 'this')}"
1993
1994    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
1995        return f"UUID {self.sql(expression, 'this')}"
1996
1997    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1998        if self.SUPPORTS_CREATE_TABLE_LIKE:
1999            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2000            options = f" {options}" if options else ""
2001
2002            like = f"LIKE {self.sql(expression, 'this')}{options}"
2003            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2004                like = f"({like})"
2005
2006            return like
2007
2008        if expression.expressions:
2009            self.unsupported("Transpilation of LIKE property options is unsupported")
2010
2011        select = exp.select("*").from_(expression.this).limit(0)
2012        return f"AS {self.sql(select)}"
2013
2014    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2015        no = "NO " if expression.args.get("no") else ""
2016        protection = " PROTECTION" if expression.args.get("protection") else ""
2017        return f"{no}FALLBACK{protection}"
2018
2019    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2020        no = "NO " if expression.args.get("no") else ""
2021        local = expression.args.get("local")
2022        local = f"{local} " if local else ""
2023        dual = "DUAL " if expression.args.get("dual") else ""
2024        before = "BEFORE " if expression.args.get("before") else ""
2025        after = "AFTER " if expression.args.get("after") else ""
2026        return f"{no}{local}{dual}{before}{after}JOURNAL"
2027
2028    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2029        freespace = self.sql(expression, "this")
2030        percent = " PERCENT" if expression.args.get("percent") else ""
2031        return f"FREESPACE={freespace}{percent}"
2032
2033    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2034        if expression.args.get("default"):
2035            property = "DEFAULT"
2036        elif expression.args.get("on"):
2037            property = "ON"
2038        else:
2039            property = "OFF"
2040        return f"CHECKSUM={property}"
2041
2042    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2043        if expression.args.get("no"):
2044            return "NO MERGEBLOCKRATIO"
2045        if expression.args.get("default"):
2046            return "DEFAULT MERGEBLOCKRATIO"
2047
2048        percent = " PERCENT" if expression.args.get("percent") else ""
2049        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2050
2051    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2052        expressions = self.expressions(expression, flat=True)
2053        expressions = f"({expressions})" if expressions else ""
2054        return f"USING {self.sql(expression, 'this')}{expressions}"
2055
2056    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2057        default = expression.args.get("default")
2058        minimum = expression.args.get("minimum")
2059        maximum = expression.args.get("maximum")
2060        if default or minimum or maximum:
2061            if default:
2062                prop = "DEFAULT"
2063            elif minimum:
2064                prop = "MINIMUM"
2065            else:
2066                prop = "MAXIMUM"
2067            return f"{prop} DATABLOCKSIZE"
2068        units = expression.args.get("units")
2069        units = f" {units}" if units else ""
2070        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2071
2072    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2073        autotemp = expression.args.get("autotemp")
2074        always = expression.args.get("always")
2075        default = expression.args.get("default")
2076        manual = expression.args.get("manual")
2077        never = expression.args.get("never")
2078
2079        if autotemp is not None:
2080            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2081        elif always:
2082            prop = "ALWAYS"
2083        elif default:
2084            prop = "DEFAULT"
2085        elif manual:
2086            prop = "MANUAL"
2087        elif never:
2088            prop = "NEVER"
2089        return f"BLOCKCOMPRESSION={prop}"
2090
2091    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2092        no = expression.args.get("no")
2093        no = " NO" if no else ""
2094        concurrent = expression.args.get("concurrent")
2095        concurrent = " CONCURRENT" if concurrent else ""
2096        target = self.sql(expression, "target")
2097        target = f" {target}" if target else ""
2098        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2099
2100    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2101        if isinstance(expression.this, list):
2102            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2103        if expression.this:
2104            modulus = self.sql(expression, "this")
2105            remainder = self.sql(expression, "expression")
2106            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2107
2108        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2109        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2110        return f"FROM ({from_expressions}) TO ({to_expressions})"
2111
2112    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2113        this = self.sql(expression, "this")
2114
2115        for_values_or_default = expression.expression
2116        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2117            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2118        else:
2119            for_values_or_default = " DEFAULT"
2120
2121        return f"PARTITION OF {this}{for_values_or_default}"
2122
2123    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2124        kind = expression.args.get("kind")
2125        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2126        for_or_in = expression.args.get("for_or_in")
2127        for_or_in = f" {for_or_in}" if for_or_in else ""
2128        lock_type = expression.args.get("lock_type")
2129        override = " OVERRIDE" if expression.args.get("override") else ""
2130        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2131
2132    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2133        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2134        statistics = expression.args.get("statistics")
2135        statistics_sql = ""
2136        if statistics is not None:
2137            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2138        return f"{data_sql}{statistics_sql}"
2139
2140    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2141        this = self.sql(expression, "this")
2142        this = f"HISTORY_TABLE={this}" if this else ""
2143        data_consistency: str | None = self.sql(expression, "data_consistency")
2144        data_consistency = (
2145            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2146        )
2147        retention_period: str | None = self.sql(expression, "retention_period")
2148        retention_period = (
2149            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2150        )
2151
2152        if this:
2153            on_sql = self.func("ON", this, data_consistency, retention_period)
2154        else:
2155            on_sql = "ON" if expression.args.get("on") else "OFF"
2156
2157        sql = f"SYSTEM_VERSIONING={on_sql}"
2158
2159        return f"WITH({sql})" if expression.args.get("with_") else sql
2160
2161    def insert_sql(self, expression: exp.Insert) -> str:
2162        hint = self.sql(expression, "hint")
2163        overwrite = expression.args.get("overwrite")
2164
2165        if isinstance(expression.this, exp.Directory):
2166            this = " OVERWRITE" if overwrite else " INTO"
2167        else:
2168            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2169
2170        stored = self.sql(expression, "stored")
2171        stored = f" {stored}" if stored else ""
2172        alternative = expression.args.get("alternative")
2173        alternative = f" OR {alternative}" if alternative else ""
2174        ignore = " IGNORE" if expression.args.get("ignore") else ""
2175        is_function = expression.args.get("is_function")
2176        if is_function:
2177            this = f"{this} FUNCTION"
2178        this = f"{this} {self.sql(expression, 'this')}"
2179
2180        exists = " IF EXISTS" if expression.args.get("exists") else ""
2181        where = self.sql(expression, "where")
2182        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2183        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2184        on_conflict = self.sql(expression, "conflict")
2185        on_conflict = f" {on_conflict}" if on_conflict else ""
2186        by_name = " BY NAME" if expression.args.get("by_name") else ""
2187        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2188        returning = self.sql(expression, "returning")
2189
2190        if self.RETURNING_END:
2191            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2192        else:
2193            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2194
2195        partition_by = self.sql(expression, "partition")
2196        partition_by = f" {partition_by}" if partition_by else ""
2197        settings = self.sql(expression, "settings")
2198        settings = f" {settings}" if settings else ""
2199
2200        source = self.sql(expression, "source")
2201        source = f"TABLE {source}" if source else ""
2202
2203        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2204        return self.prepend_ctes(expression, sql)
2205
2206    def introducer_sql(self, expression: exp.Introducer) -> str:
2207        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2208
2209    def kill_sql(self, expression: exp.Kill) -> str:
2210        kind = self.sql(expression, "kind")
2211        kind = f" {kind}" if kind else ""
2212        this = self.sql(expression, "this")
2213        this = f" {this}" if this else ""
2214        return f"KILL{kind}{this}"
2215
2216    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2217        return expression.name
2218
2219    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2220        return expression.name
2221
2222    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2223        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2224
2225        constraint = self.sql(expression, "constraint")
2226        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2227
2228        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2229        if conflict_keys:
2230            conflict_keys = f"({conflict_keys})"
2231
2232        index_predicate = self.sql(expression, "index_predicate")
2233        conflict_keys = f"{conflict_keys}{index_predicate} "
2234
2235        action = self.sql(expression, "action")
2236
2237        expressions = self.expressions(expression, flat=True)
2238        if expressions:
2239            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2240            expressions = f" {set_keyword}{expressions}"
2241
2242        where = self.sql(expression, "where")
2243        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2244
2245    def returning_sql(self, expression: exp.Returning) -> str:
2246        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2247
2248    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2249        fields = self.sql(expression, "fields")
2250        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2251        escaped = self.sql(expression, "escaped")
2252        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2253        items = self.sql(expression, "collection_items")
2254        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2255        keys = self.sql(expression, "map_keys")
2256        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2257        lines = self.sql(expression, "lines")
2258        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2259        null = self.sql(expression, "null")
2260        null = f" NULL DEFINED AS {null}" if null else ""
2261        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2262
2263    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2264        return f"WITH ({self.expressions(expression, flat=True)})"
2265
2266    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2267        this = f"{self.sql(expression, 'this')} INDEX"
2268        target = self.sql(expression, "target")
2269        target = f" FOR {target}" if target else ""
2270        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2271
2272    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2273        this = self.sql(expression, "this")
2274        kind = self.sql(expression, "kind")
2275        expr = self.sql(expression, "expression")
2276        return f"{this} ({kind} => {expr})"
2277
2278    def table_parts(self, expression: exp.Table) -> str:
2279        return ".".join(
2280            self.sql(part)
2281            for part in (
2282                expression.args.get("catalog"),
2283                expression.args.get("db"),
2284                expression.args.get("this"),
2285            )
2286            if part is not None
2287        )
2288
2289    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2290        table = self.table_parts(expression)
2291        only = "ONLY " if expression.args.get("only") else ""
2292        partition = self.sql(expression, "partition")
2293        partition = f" {partition}" if partition else ""
2294        version = self.sql(expression, "version")
2295        version = f" {version}" if version else ""
2296        alias = self.sql(expression, "alias")
2297        alias = f"{sep}{alias}" if alias else ""
2298
2299        sample = self.sql(expression, "sample")
2300        post_alias = ""
2301        pre_alias = ""
2302
2303        if self.dialect.ALIAS_POST_TABLESAMPLE:
2304            pre_alias = sample
2305        else:
2306            post_alias = sample
2307
2308        if self.dialect.ALIAS_POST_VERSION:
2309            pre_alias = f"{pre_alias}{version}"
2310        else:
2311            post_alias = f"{post_alias}{version}"
2312
2313        hints = self.expressions(expression, key="hints", sep=" ")
2314        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2315        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2316        joins = self.indent(
2317            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2318        )
2319        laterals = self.expressions(expression, key="laterals", sep="")
2320
2321        file_format = self.sql(expression, "format")
2322        if file_format:
2323            pattern = self.sql(expression, "pattern")
2324            pattern = f", PATTERN => {pattern}" if pattern else ""
2325            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2326
2327        ordinality = expression.args.get("ordinality") or ""
2328        if ordinality:
2329            ordinality = f" WITH ORDINALITY{alias}"
2330            alias = ""
2331
2332        when = self.sql(expression, "when")
2333        if when:
2334            table = f"{table} {when}"
2335
2336        changes = self.sql(expression, "changes")
2337        changes = f" {changes}" if changes else ""
2338
2339        rows_from = self.expressions(expression, key="rows_from")
2340        if rows_from:
2341            table = f"ROWS FROM {self.wrap(rows_from)}"
2342
2343        indexed = expression.args.get("indexed")
2344        if indexed is not None:
2345            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2346        else:
2347            indexed = ""
2348
2349        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2350
2351    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2352        table = self.func("TABLE", expression.this)
2353        alias = self.sql(expression, "alias")
2354        alias = f" AS {alias}" if alias else ""
2355        sample = self.sql(expression, "sample")
2356        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2357        joins = self.indent(
2358            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2359        )
2360        return f"{table}{alias}{pivots}{sample}{joins}"
2361
2362    def tablesample_sql(
2363        self,
2364        expression: exp.TableSample,
2365        tablesample_keyword: str | None = None,
2366    ) -> str:
2367        method = self.sql(expression, "method")
2368        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2369        numerator = self.sql(expression, "bucket_numerator")
2370        denominator = self.sql(expression, "bucket_denominator")
2371        field = self.sql(expression, "bucket_field")
2372        field = f" ON {field}" if field else ""
2373        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2374        seed = self.sql(expression, "seed")
2375        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2376
2377        size = self.sql(expression, "size")
2378        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2379            size = f"{size} ROWS"
2380
2381        percent = self.sql(expression, "percent")
2382        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2383            percent = f"{percent} PERCENT"
2384
2385        expr = f"{bucket}{percent}{size}"
2386        if self.TABLESAMPLE_REQUIRES_PARENS:
2387            expr = f"({expr})"
2388
2389        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2390
2391    def pivot_sql(self, expression: exp.Pivot) -> str:
2392        expressions = self.expressions(expression, flat=True)
2393        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2394
2395        group = self.sql(expression, "group")
2396
2397        if expression.this:
2398            this = self.sql(expression, "this")
2399            if not expressions:
2400                sql = f"UNPIVOT {this}"
2401            else:
2402                on = f"{self.seg('ON')} {expressions}"
2403                into = self.sql(expression, "into")
2404                into = f"{self.seg('INTO')} {into}" if into else ""
2405                using = self.expressions(expression, key="using", flat=True)
2406                using = f"{self.seg('USING')} {using}" if using else ""
2407                sql = f"{direction} {this}{on}{into}{using}{group}"
2408            return self.prepend_ctes(expression, sql)
2409
2410        alias = self.sql(expression, "alias")
2411        alias = f" AS {alias}" if alias else ""
2412
2413        fields = self.expressions(
2414            expression,
2415            "fields",
2416            sep=" ",
2417            dynamic=True,
2418            new_line=True,
2419            skip_first=True,
2420            skip_last=True,
2421        )
2422
2423        include_nulls = expression.args.get("include_nulls")
2424        if include_nulls is not None:
2425            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2426        else:
2427            nulls = ""
2428
2429        default_on_null = self.sql(expression, "default_on_null")
2430        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2431        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2432        return self.prepend_ctes(expression, sql)
2433
2434    def version_sql(self, expression: exp.Version) -> str:
2435        this = f"FOR {expression.name}"
2436        kind = expression.text("kind")
2437        expr = self.sql(expression, "expression")
2438        return f"{this} {kind} {expr}"
2439
2440    def tuple_sql(self, expression: exp.Tuple) -> str:
2441        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2442
2443    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2444        """
2445        Returns (join_sql, from_sql) for UPDATE statements.
2446        - join_sql: placed after UPDATE table, before SET
2447        - from_sql: placed after SET clause (standard position)
2448        Dialects like MySQL need to convert FROM to JOIN syntax.
2449        """
2450        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2451            return ("", self.sql(expression, "from_"))
2452
2453        # Qualify unqualified columns in SET clause with the target table
2454        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2455        target_table = expression.this
2456        if isinstance(target_table, exp.Table):
2457            target_name = exp.to_identifier(target_table.alias_or_name)
2458            for eq in expression.expressions:
2459                col = eq.this
2460                if isinstance(col, exp.Column) and not col.table:
2461                    col.set("table", target_name)
2462
2463        table = from_expr.this
2464        if nested_joins := table.args.get("joins", []):
2465            table.set("joins", None)
2466
2467        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2468        for nested in nested_joins:
2469            if not nested.args.get("on") and not nested.args.get("using"):
2470                nested.set("on", exp.true())
2471            join_sql += self.sql(nested)
2472
2473        return (join_sql, "")
2474
2475    def update_sql(self, expression: exp.Update) -> str:
2476        hint = self.sql(expression, "hint")
2477        this = self.sql(expression, "this")
2478        join_sql, from_sql = self._update_from_joins_sql(expression)
2479        set_sql = self.expressions(expression, flat=True)
2480        where_sql = self.sql(expression, "where")
2481        returning = self.sql(expression, "returning")
2482        order = self.sql(expression, "order")
2483        limit = self.sql(expression, "limit")
2484        if self.RETURNING_END:
2485            expression_sql = f"{from_sql}{where_sql}{returning}"
2486        else:
2487            expression_sql = f"{returning}{from_sql}{where_sql}"
2488        options = self.expressions(expression, key="options")
2489        options = f" OPTION({options})" if options else ""
2490        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2491        return self.prepend_ctes(expression, sql)
2492
2493    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2494        values_as_table = values_as_table and self.VALUES_AS_TABLE
2495
2496        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2497        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2498            args = self.expressions(expression)
2499            alias = self.sql(expression, "alias")
2500            values = f"VALUES{self.seg('')}{args}"
2501            values = (
2502                f"({values})"
2503                if self.WRAP_DERIVED_VALUES
2504                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2505                else values
2506            )
2507            values = self.query_modifiers(expression, values)
2508            return f"{values} AS {alias}" if alias else values
2509
2510        # Converts `VALUES...` expression into a series of select unions.
2511        alias_node = expression.args.get("alias")
2512        column_names = alias_node and alias_node.columns
2513
2514        selects: list[exp.Query] = []
2515
2516        for i, tup in enumerate(expression.expressions):
2517            row = tup.expressions
2518
2519            if i == 0 and column_names:
2520                row = [
2521                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2522                ]
2523
2524            selects.append(exp.Select(expressions=row))
2525
2526        if self.pretty:
2527            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2528            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2529            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2530            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2531            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2532
2533        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2534        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2535        return f"({unions}){alias}"
2536
2537    def var_sql(self, expression: exp.Var) -> str:
2538        return self.sql(expression, "this")
2539
2540    @unsupported_args("expressions")
2541    def into_sql(self, expression: exp.Into) -> str:
2542        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2543        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2544        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2545
2546    def from_sql(self, expression: exp.From) -> str:
2547        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2548
2549    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2550        grouping_sets = self.expressions(expression, indent=False)
2551        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2552
2553    def rollup_sql(self, expression: exp.Rollup) -> str:
2554        expressions = self.expressions(expression, indent=False)
2555        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2556
2557    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2558        this = self.sql(expression, "this")
2559
2560        columns = self.expressions(expression, flat=True)
2561
2562        from_sql = self.sql(expression, "from_index")
2563        from_sql = f" FROM {from_sql}" if from_sql else ""
2564
2565        properties = expression.args.get("properties")
2566        properties_sql = (
2567            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2568        )
2569
2570        return f"{this}({columns}){from_sql}{properties_sql}"
2571
2572    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2573        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2574
2575    def cube_sql(self, expression: exp.Cube) -> str:
2576        expressions = self.expressions(expression, indent=False)
2577        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2578
2579    def group_sql(self, expression: exp.Group) -> str:
2580        group_by_all = expression.args.get("all")
2581        if group_by_all is True:
2582            modifier = " ALL"
2583        elif group_by_all is False:
2584            modifier = " DISTINCT"
2585        else:
2586            modifier = ""
2587
2588        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2589
2590        grouping_sets = self.expressions(expression, key="grouping_sets")
2591        cube = self.expressions(expression, key="cube")
2592        rollup = self.expressions(expression, key="rollup")
2593
2594        groupings = csv(
2595            self.seg(grouping_sets) if grouping_sets else "",
2596            self.seg(cube) if cube else "",
2597            self.seg(rollup) if rollup else "",
2598            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2599            sep=self.GROUPINGS_SEP,
2600        )
2601
2602        if (
2603            expression.expressions
2604            and groupings
2605            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2606        ):
2607            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2608
2609        return f"{group_by}{groupings}"
2610
2611    def having_sql(self, expression: exp.Having) -> str:
2612        this = self.indent(self.sql(expression, "this"))
2613        return f"{self.seg('HAVING')}{self.sep()}{this}"
2614
2615    def connect_sql(self, expression: exp.Connect) -> str:
2616        start = self.sql(expression, "start")
2617        start = self.seg(f"START WITH {start}") if start else ""
2618        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2619        connect = self.sql(expression, "connect")
2620        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2621        return start + connect
2622
2623    def prior_sql(self, expression: exp.Prior) -> str:
2624        return f"PRIOR {self.sql(expression, 'this')}"
2625
2626    def join_sql(self, expression: exp.Join) -> str:
2627        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2628            side = None
2629        else:
2630            side = expression.side
2631
2632        op_sql = " ".join(
2633            op
2634            for op in (
2635                expression.method,
2636                "GLOBAL" if expression.args.get("global_") else None,
2637                side,
2638                expression.kind,
2639                expression.hint if self.JOIN_HINTS else None,
2640                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2641            )
2642            if op
2643        )
2644        match_cond = self.sql(expression, "match_condition")
2645        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2646        on_sql = self.sql(expression, "on")
2647        using = expression.args.get("using")
2648
2649        if not on_sql and using:
2650            on_sql = csv(*(self.sql(column) for column in using))
2651
2652        this = expression.this
2653        this_sql = self.sql(this)
2654
2655        exprs = self.expressions(expression)
2656        if exprs:
2657            this_sql = f"{this_sql},{self.seg(exprs)}"
2658
2659        if on_sql:
2660            on_sql = self.indent(on_sql, skip_first=True)
2661            space = self.seg(" " * self.pad) if self.pretty else " "
2662            if using:
2663                on_sql = f"{space}USING ({on_sql})"
2664            else:
2665                on_sql = f"{space}ON {on_sql}"
2666        elif not op_sql:
2667            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2668                return f" {this_sql}"
2669
2670            return f", {this_sql}"
2671
2672        if op_sql != "STRAIGHT_JOIN":
2673            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2674
2675        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2676        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2677
2678    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2679        args = self.expressions(expression, flat=True)
2680        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2681        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2682
2683    def lateral_op(self, expression: exp.Lateral) -> str:
2684        cross_apply = expression.args.get("cross_apply")
2685
2686        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2687        if cross_apply is True:
2688            op = "INNER JOIN "
2689        elif cross_apply is False:
2690            op = "LEFT JOIN "
2691        else:
2692            op = ""
2693
2694        return f"{op}LATERAL"
2695
2696    def lateral_sql(self, expression: exp.Lateral) -> str:
2697        this = self.sql(expression, "this")
2698
2699        if expression.args.get("view"):
2700            alias = expression.args["alias"]
2701            columns = self.expressions(alias, key="columns", flat=True)
2702            table = f" {alias.name}" if alias.name else ""
2703            columns = f" AS {columns}" if columns else ""
2704            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2705            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2706
2707        alias = self.sql(expression, "alias")
2708        alias = f" AS {alias}" if alias else ""
2709
2710        ordinality = expression.args.get("ordinality") or ""
2711        if ordinality:
2712            ordinality = f" WITH ORDINALITY{alias}"
2713            alias = ""
2714
2715        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2716
2717    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2718        this = self.sql(expression, "this")
2719
2720        args = [
2721            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2722            for e in (expression.args.get(k) for k in ("offset", "expression"))
2723            if e
2724        ]
2725
2726        args_sql = ", ".join(self.sql(e) for e in args)
2727        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2728        expressions = self.expressions(expression, flat=True)
2729        limit_options = self.sql(expression, "limit_options")
2730        expressions = f" BY {expressions}" if expressions else ""
2731
2732        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2733
2734    def offset_sql(self, expression: exp.Offset) -> str:
2735        this = self.sql(expression, "this")
2736        value = expression.expression
2737        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2738        expressions = self.expressions(expression, flat=True)
2739        expressions = f" BY {expressions}" if expressions else ""
2740        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2741
2742    def setitem_sql(self, expression: exp.SetItem) -> str:
2743        kind = self.sql(expression, "kind")
2744        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2745            kind = ""
2746        else:
2747            kind = f"{kind} " if kind else ""
2748        this = self.sql(expression, "this")
2749        expressions = self.expressions(expression)
2750        collate = self.sql(expression, "collate")
2751        collate = f" COLLATE {collate}" if collate else ""
2752        global_ = "GLOBAL " if expression.args.get("global_") else ""
2753        return f"{global_}{kind}{this}{expressions}{collate}"
2754
2755    def set_sql(self, expression: exp.Set) -> str:
2756        expressions = f" {self.expressions(expression, flat=True)}"
2757        tag = " TAG" if expression.args.get("tag") else ""
2758        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2759
2760    def queryband_sql(self, expression: exp.QueryBand) -> str:
2761        this = self.sql(expression, "this")
2762        update = " UPDATE" if expression.args.get("update") else ""
2763        scope = self.sql(expression, "scope")
2764        scope = f" FOR {scope}" if scope else ""
2765
2766        return f"QUERY_BAND = {this}{update}{scope}"
2767
2768    def pragma_sql(self, expression: exp.Pragma) -> str:
2769        return f"PRAGMA {self.sql(expression, 'this')}"
2770
2771    def lock_sql(self, expression: exp.Lock) -> str:
2772        if not self.LOCKING_READS_SUPPORTED:
2773            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2774            return ""
2775
2776        update = expression.args["update"]
2777        key = expression.args.get("key")
2778        if update:
2779            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2780        else:
2781            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2782        expressions = self.expressions(expression, flat=True)
2783        expressions = f" OF {expressions}" if expressions else ""
2784        wait = expression.args.get("wait")
2785
2786        if wait is not None:
2787            if isinstance(wait, exp.Literal):
2788                wait = f" WAIT {self.sql(wait)}"
2789            else:
2790                wait = " NOWAIT" if wait else " SKIP LOCKED"
2791
2792        return f"{lock_type}{expressions}{wait or ''}"
2793
2794    def literal_sql(self, expression: exp.Literal) -> str:
2795        text = expression.this or ""
2796        if expression.is_string:
2797            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2798        return text
2799
2800    def escape_str(
2801        self,
2802        text: str,
2803        escape_backslash: bool = True,
2804        delimiter: str | None = None,
2805        escaped_delimiter: str | None = None,
2806        is_byte_string: bool = False,
2807    ) -> str:
2808        if is_byte_string:
2809            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
2810        else:
2811            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
2812
2813        if supports_escape_sequences:
2814            text = "".join(
2815                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
2816                for ch in text
2817            )
2818
2819        delimiter = delimiter or self.dialect.QUOTE_END
2820        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2821
2822        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2823
2824    def loaddata_sql(self, expression: exp.LoadData) -> str:
2825        is_overwrite = expression.args.get("overwrite")
2826        overwrite = " OVERWRITE" if is_overwrite else ""
2827        this = self.sql(expression, "this")
2828
2829        files = expression.args.get("files")
2830        if files:
2831            files_sql = self.expressions(files, flat=True)
2832            files_sql = f"FILES{self.wrap(files_sql)}"
2833            this = f" {this}" if is_overwrite else f" INTO TABLE {this}"
2834            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
2835
2836        local = " LOCAL" if expression.args.get("local") else ""
2837        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2838        this = f" INTO TABLE {this}"
2839        partition = self.sql(expression, "partition")
2840        partition = f" {partition}" if partition else ""
2841        input_format = self.sql(expression, "input_format")
2842        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2843        serde = self.sql(expression, "serde")
2844        serde = f" SERDE {serde}" if serde else ""
2845        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2846
2847    def null_sql(self, *_) -> str:
2848        return "NULL"
2849
2850    def boolean_sql(self, expression: exp.Boolean) -> str:
2851        return "TRUE" if expression.this else "FALSE"
2852
2853    def booland_sql(self, expression: exp.Booland) -> str:
2854        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
2855
2856    def boolor_sql(self, expression: exp.Boolor) -> str:
2857        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
2858
2859    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2860        this = self.sql(expression, "this")
2861        this = f"{this} " if this else this
2862        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2863        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
2864
2865    def withfill_sql(self, expression: exp.WithFill) -> str:
2866        from_sql = self.sql(expression, "from_")
2867        from_sql = f" FROM {from_sql}" if from_sql else ""
2868        to_sql = self.sql(expression, "to")
2869        to_sql = f" TO {to_sql}" if to_sql else ""
2870        step_sql = self.sql(expression, "step")
2871        step_sql = f" STEP {step_sql}" if step_sql else ""
2872        interpolated_values = [
2873            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2874            if isinstance(e, exp.Alias)
2875            else self.sql(e, "this")
2876            for e in expression.args.get("interpolate") or []
2877        ]
2878        interpolate = (
2879            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2880        )
2881        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2882
2883    def cluster_sql(self, expression: exp.Cluster) -> str:
2884        return self.op_expressions("CLUSTER BY", expression)
2885
2886    def distribute_sql(self, expression: exp.Distribute) -> str:
2887        return self.op_expressions("DISTRIBUTE BY", expression)
2888
2889    def sort_sql(self, expression: exp.Sort) -> str:
2890        return self.op_expressions("SORT BY", expression)
2891
2892    def ordered_sql(self, expression: exp.Ordered) -> str:
2893        desc = expression.args.get("desc")
2894        asc = not desc
2895
2896        nulls_first = expression.args.get("nulls_first")
2897        nulls_last = not nulls_first
2898        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2899        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2900        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2901
2902        this = self.sql(expression, "this")
2903
2904        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2905        nulls_sort_change = ""
2906        if nulls_first and (
2907            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2908        ):
2909            nulls_sort_change = " NULLS FIRST"
2910        elif (
2911            nulls_last
2912            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2913            and not nulls_are_last
2914        ):
2915            nulls_sort_change = " NULLS LAST"
2916
2917        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2918        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2919            window = expression.find_ancestor(exp.Window, exp.Select)
2920
2921            if isinstance(window, exp.Window):
2922                window_this = window.this
2923                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
2924                    window_this = window_this.this
2925                spec = window.args.get("spec")
2926            else:
2927                window_this = None
2928                spec = None
2929
2930            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
2931            # without a spec or with a ROWS spec, but not with RANGE
2932            if not (
2933                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
2934                and (not spec or spec.text("kind").upper() == "ROWS")
2935            ):
2936                if window_this and spec:
2937                    self.unsupported(
2938                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
2939                    )
2940                    nulls_sort_change = ""
2941                elif self.NULL_ORDERING_SUPPORTED is False and (
2942                    (asc and nulls_sort_change == " NULLS LAST")
2943                    or (desc and nulls_sort_change == " NULLS FIRST")
2944                ):
2945                    # BigQuery does not allow these ordering/nulls combinations when used under
2946                    # an aggregation func or under a window containing one
2947                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2948
2949                    if isinstance(ancestor, exp.Window):
2950                        ancestor = ancestor.this
2951                    if isinstance(ancestor, exp.AggFunc):
2952                        self.unsupported(
2953                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
2954                        )
2955                        nulls_sort_change = ""
2956                elif self.NULL_ORDERING_SUPPORTED is None:
2957                    if expression.this.is_int:
2958                        self.unsupported(
2959                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2960                        )
2961                    elif not isinstance(expression.this, exp.Rand):
2962                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2963                        this = (
2964                            f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2965                        )
2966                    nulls_sort_change = ""
2967
2968        with_fill = self.sql(expression, "with_fill")
2969        with_fill = f" {with_fill}" if with_fill else ""
2970
2971        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2972
2973    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2974        window_frame = self.sql(expression, "window_frame")
2975        window_frame = f"{window_frame} " if window_frame else ""
2976
2977        this = self.sql(expression, "this")
2978
2979        return f"{window_frame}{this}"
2980
2981    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2982        partition = self.partition_by_sql(expression)
2983        order = self.sql(expression, "order")
2984        measures = self.expressions(expression, key="measures")
2985        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2986        rows = self.sql(expression, "rows")
2987        rows = self.seg(rows) if rows else ""
2988        after = self.sql(expression, "after")
2989        after = self.seg(after) if after else ""
2990        pattern = self.sql(expression, "pattern")
2991        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2992        definition_sqls = [
2993            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2994            for definition in expression.args.get("define", [])
2995        ]
2996        definitions = self.expressions(sqls=definition_sqls)
2997        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2998        body = "".join(
2999            (
3000                partition,
3001                order,
3002                measures,
3003                rows,
3004                after,
3005                pattern,
3006                define,
3007            )
3008        )
3009        alias = self.sql(expression, "alias")
3010        alias = f" {alias}" if alias else ""
3011        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3012
3013    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3014        limit = expression.args.get("limit")
3015
3016        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3017            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
3018        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
3019            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3020
3021        return csv(
3022            *sqls,
3023            *[self.sql(join) for join in expression.args.get("joins") or []],
3024            self.sql(expression, "match"),
3025            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3026            self.sql(expression, "prewhere"),
3027            self.sql(expression, "where"),
3028            self.sql(expression, "connect"),
3029            self.sql(expression, "group"),
3030            self.sql(expression, "having"),
3031            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3032            self.sql(expression, "order"),
3033            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3034            *self.after_limit_modifiers(expression),
3035            self.options_modifier(expression),
3036            self.for_modifiers(expression),
3037            sep="",
3038        )
3039
3040    def options_modifier(self, expression: exp.Expr) -> str:
3041        options = self.expressions(expression, key="options")
3042        return f" {options}" if options else ""
3043
3044    def for_modifiers(self, expression: exp.Expr) -> str:
3045        for_modifiers = self.expressions(expression, key="for_")
3046        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
3047
3048    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3049        self.unsupported("Unsupported query option.")
3050        return ""
3051
3052    def offset_limit_modifiers(
3053        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3054    ) -> list[str]:
3055        return [
3056            self.sql(expression, "offset") if fetch else self.sql(limit),
3057            self.sql(limit) if fetch else self.sql(expression, "offset"),
3058        ]
3059
3060    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3061        locks = self.expressions(expression, key="locks", sep=" ")
3062        locks = f" {locks}" if locks else ""
3063        return [locks, self.sql(expression, "sample")]
3064
3065    def select_sql(self, expression: exp.Select) -> str:
3066        into = expression.args.get("into")
3067        if not self.SUPPORTS_SELECT_INTO and into:
3068            into.pop()
3069
3070        hint = self.sql(expression, "hint")
3071        distinct = self.sql(expression, "distinct")
3072        distinct = f" {distinct}" if distinct else ""
3073        kind = self.sql(expression, "kind")
3074
3075        limit = expression.args.get("limit")
3076        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3077            top = self.limit_sql(limit, top=True)
3078            limit.pop()
3079        else:
3080            top = ""
3081
3082        expressions = self.expressions(expression)
3083
3084        if kind:
3085            if kind in self.SELECT_KINDS:
3086                kind = f" AS {kind}"
3087            else:
3088                if kind == "STRUCT":
3089                    expressions = self.expressions(
3090                        sqls=[
3091                            self.sql(
3092                                exp.Struct(
3093                                    expressions=[
3094                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3095                                        if isinstance(e, exp.Alias)
3096                                        else e
3097                                        for e in expression.expressions
3098                                    ]
3099                                )
3100                            )
3101                        ]
3102                    )
3103                kind = ""
3104
3105        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3106        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3107
3108        exclude = expression.args.get("exclude")
3109
3110        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3111            exclude_sql = self.expressions(sqls=exclude, flat=True)
3112            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3113
3114        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3115        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3116        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3117        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3118        sql = self.query_modifiers(
3119            expression,
3120            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3121            self.sql(expression, "into", comment=False),
3122            self.sql(expression, "from_", comment=False),
3123        )
3124
3125        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3126        if expression.args.get("with_"):
3127            sql = self.maybe_comment(sql, expression)
3128            expression.pop_comments()
3129
3130        sql = self.prepend_ctes(expression, sql)
3131
3132        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3133            expression.set("exclude", None)
3134            subquery = expression.subquery(copy=False)
3135            star = exp.Star(except_=exclude)
3136            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3137
3138        if not self.SUPPORTS_SELECT_INTO and into:
3139            if into.args.get("temporary"):
3140                table_kind = " TEMPORARY"
3141            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3142                table_kind = " UNLOGGED"
3143            else:
3144                table_kind = ""
3145            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3146
3147        return sql
3148
3149    def schema_sql(self, expression: exp.Schema) -> str:
3150        this = self.sql(expression, "this")
3151        sql = self.schema_columns_sql(expression)
3152        return f"{this} {sql}" if this and sql else this or sql
3153
3154    def schema_columns_sql(self, expression: exp.Expr) -> str:
3155        if expression.expressions:
3156            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3157        return ""
3158
3159    def star_sql(self, expression: exp.Star) -> str:
3160        except_ = self.expressions(expression, key="except_", flat=True)
3161        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3162        replace = self.expressions(expression, key="replace", flat=True)
3163        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3164        rename = self.expressions(expression, key="rename", flat=True)
3165        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3166        return f"*{except_}{replace}{rename}"
3167
3168    def parameter_sql(self, expression: exp.Parameter) -> str:
3169        this = self.sql(expression, "this")
3170        return f"{self.PARAMETER_TOKEN}{this}"
3171
3172    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3173        this = self.sql(expression, "this")
3174        kind = expression.text("kind")
3175        if kind:
3176            kind = f"{kind}."
3177        return f"@@{kind}{this}"
3178
3179    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3180        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3181
3182    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3183        alias = self.sql(expression, "alias")
3184        alias = f"{sep}{alias}" if alias else ""
3185        sample = self.sql(expression, "sample")
3186        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3187            alias = f"{sample}{alias}"
3188
3189            # Set to None so it's not generated again by self.query_modifiers()
3190            expression.set("sample", None)
3191
3192        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3193        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3194        return self.prepend_ctes(expression, sql)
3195
3196    def qualify_sql(self, expression: exp.Qualify) -> str:
3197        this = self.indent(self.sql(expression, "this"))
3198        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3199
3200    def unnest_sql(self, expression: exp.Unnest) -> str:
3201        args = self.expressions(expression, flat=True)
3202
3203        alias = expression.args.get("alias")
3204        offset = expression.args.get("offset")
3205
3206        if self.UNNEST_WITH_ORDINALITY:
3207            if alias and isinstance(offset, exp.Expr):
3208                alias.append("columns", offset)
3209                expression.set("offset", None)
3210
3211        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3212            columns = alias.columns
3213            alias = self.sql(columns[0]) if columns else ""
3214        else:
3215            alias = self.sql(alias)
3216
3217        alias = f" AS {alias}" if alias else alias
3218        if self.UNNEST_WITH_ORDINALITY:
3219            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3220        else:
3221            if isinstance(offset, exp.Expr):
3222                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3223            elif offset:
3224                suffix = f"{alias} WITH OFFSET"
3225            else:
3226                suffix = alias
3227
3228        return f"UNNEST({args}){suffix}"
3229
3230    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3231        return ""
3232
3233    def where_sql(self, expression: exp.Where) -> str:
3234        this = self.indent(self.sql(expression, "this"))
3235        return f"{self.seg('WHERE')}{self.sep()}{this}"
3236
3237    def window_sql(self, expression: exp.Window) -> str:
3238        this = self.sql(expression, "this")
3239        partition = self.partition_by_sql(expression)
3240        order = expression.args.get("order")
3241        order = self.order_sql(order, flat=True) if order else ""
3242        spec = self.sql(expression, "spec")
3243        alias = self.sql(expression, "alias")
3244        over = self.sql(expression, "over") or "OVER"
3245
3246        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3247
3248        first = expression.args.get("first")
3249        if first is None:
3250            first = ""
3251        else:
3252            first = "FIRST" if first else "LAST"
3253
3254        if not partition and not order and not spec and alias:
3255            return f"{this} {alias}"
3256
3257        args = self.format_args(
3258            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3259        )
3260        return f"{this} ({args})"
3261
3262    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3263        partition = self.expressions(expression, key="partition_by", flat=True)
3264        return f"PARTITION BY {partition}" if partition else ""
3265
3266    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3267        kind = self.sql(expression, "kind")
3268        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3269        end = (
3270            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3271            or "CURRENT ROW"
3272        )
3273
3274        window_spec = f"{kind} BETWEEN {start} AND {end}"
3275
3276        exclude = self.sql(expression, "exclude")
3277        if exclude:
3278            if self.SUPPORTS_WINDOW_EXCLUDE:
3279                window_spec += f" EXCLUDE {exclude}"
3280            else:
3281                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3282
3283        return window_spec
3284
3285    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3286        this = self.sql(expression, "this")
3287        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3288        return f"{this} WITHIN GROUP ({expression_sql})"
3289
3290    def between_sql(self, expression: exp.Between) -> str:
3291        this = self.sql(expression, "this")
3292        low = self.sql(expression, "low")
3293        high = self.sql(expression, "high")
3294        symmetric = expression.args.get("symmetric")
3295
3296        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3297            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3298
3299        flag = (
3300            " SYMMETRIC"
3301            if symmetric
3302            else " ASYMMETRIC"
3303            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3304            else ""  # silently drop ASYMMETRIC – semantics identical
3305        )
3306        return f"{this} BETWEEN{flag} {low} AND {high}"
3307
3308    def bracket_offset_expressions(
3309        self, expression: exp.Bracket, index_offset: int | None = None
3310    ) -> list[exp.Expr]:
3311        if expression.args.get("json_access"):
3312            return expression.expressions
3313
3314        return apply_index_offset(
3315            expression.this,
3316            expression.expressions,
3317            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3318            dialect=self.dialect,
3319        )
3320
3321    def bracket_sql(self, expression: exp.Bracket) -> str:
3322        expressions = self.bracket_offset_expressions(expression)
3323        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3324        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3325
3326    def all_sql(self, expression: exp.All) -> str:
3327        this = self.sql(expression, "this")
3328        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3329            this = self.wrap(this)
3330        return f"ALL {this}"
3331
3332    def any_sql(self, expression: exp.Any) -> str:
3333        this = self.sql(expression, "this")
3334        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3335            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3336                this = self.wrap(this)
3337            return f"ANY{this}"
3338        return f"ANY {this}"
3339
3340    def exists_sql(self, expression: exp.Exists) -> str:
3341        return f"EXISTS{self.wrap(expression)}"
3342
3343    def case_sql(self, expression: exp.Case) -> str:
3344        this = self.sql(expression, "this")
3345        statements = [f"CASE {this}" if this else "CASE"]
3346
3347        for e in expression.args["ifs"]:
3348            statements.append(f"WHEN {self.sql(e, 'this')}")
3349            statements.append(f"THEN {self.sql(e, 'true')}")
3350
3351        default = self.sql(expression, "default")
3352
3353        if default:
3354            statements.append(f"ELSE {default}")
3355
3356        statements.append("END")
3357
3358        if self.pretty and self.too_wide(statements):
3359            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3360
3361        return " ".join(statements)
3362
3363    def constraint_sql(self, expression: exp.Constraint) -> str:
3364        this = self.sql(expression, "this")
3365        expressions = self.expressions(expression, flat=True)
3366        return f"CONSTRAINT {this} {expressions}"
3367
3368    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3369        order = expression.args.get("order")
3370        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3371        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3372
3373    def extract_sql(self, expression: exp.Extract) -> str:
3374        import sqlglot.dialects.dialect
3375
3376        this = (
3377            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3378            if self.NORMALIZE_EXTRACT_DATE_PARTS
3379            else expression.this
3380        )
3381        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3382        expression_sql = self.sql(expression, "expression")
3383
3384        return f"EXTRACT({this_sql} FROM {expression_sql})"
3385
3386    def trim_sql(self, expression: exp.Trim) -> str:
3387        trim_type = self.sql(expression, "position")
3388
3389        if trim_type == "LEADING":
3390            func_name = "LTRIM"
3391        elif trim_type == "TRAILING":
3392            func_name = "RTRIM"
3393        else:
3394            func_name = "TRIM"
3395
3396        return self.func(func_name, expression.this, expression.expression)
3397
3398    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3399        args = expression.expressions
3400        if isinstance(expression, exp.ConcatWs):
3401            args = args[1:]  # Skip the delimiter
3402
3403        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3404            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3405
3406        concat_coalesce = (
3407            self.dialect.CONCAT_WS_COALESCE
3408            if isinstance(expression, exp.ConcatWs)
3409            else self.dialect.CONCAT_COALESCE
3410        )
3411
3412        if not concat_coalesce and expression.args.get("coalesce"):
3413
3414            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3415                if not e.type:
3416                    import sqlglot.optimizer.annotate_types
3417
3418                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3419
3420                if e.is_string or e.is_type(exp.DType.ARRAY):
3421                    return e
3422
3423                return exp.func("coalesce", e, exp.Literal.string(""))
3424
3425            args = [_wrap_with_coalesce(e) for e in args]
3426
3427        return args
3428
3429    def concat_sql(self, expression: exp.Concat) -> str:
3430        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3431            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3432            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3433            # instead of coalescing them to empty string.
3434            import sqlglot.dialects.dialect
3435
3436            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3437
3438        expressions = self.convert_concat_args(expression)
3439
3440        # Some dialects don't allow a single-argument CONCAT call
3441        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3442            return self.sql(expressions[0])
3443
3444        return self.func("CONCAT", *expressions)
3445
3446    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3447        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3448            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3449            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3450            all_args = expression.expressions
3451            expression.set("coalesce", True)
3452            return self.sql(
3453                exp.case()
3454                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3455                .else_(expression)
3456            )
3457
3458        return self.func(
3459            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3460        )
3461
3462    def check_sql(self, expression: exp.Check) -> str:
3463        this = self.sql(expression, key="this")
3464        return f"CHECK ({this})"
3465
3466    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3467        expressions = self.expressions(expression, flat=True)
3468        expressions = f" ({expressions})" if expressions else ""
3469        reference = self.sql(expression, "reference")
3470        reference = f" {reference}" if reference else ""
3471        delete = self.sql(expression, "delete")
3472        delete = f" ON DELETE {delete}" if delete else ""
3473        update = self.sql(expression, "update")
3474        update = f" ON UPDATE {update}" if update else ""
3475        options = self.expressions(expression, key="options", flat=True, sep=" ")
3476        options = f" {options}" if options else ""
3477        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3478
3479    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3480        this = self.sql(expression, "this")
3481        this = f" {this}" if this else ""
3482        expressions = self.expressions(expression, flat=True)
3483        include = self.sql(expression, "include")
3484        options = self.expressions(expression, key="options", flat=True, sep=" ")
3485        options = f" {options}" if options else ""
3486        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3487
3488    def if_sql(self, expression: exp.If) -> str:
3489        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3490
3491    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3492        if self.MATCH_AGAINST_TABLE_PREFIX:
3493            expressions = []
3494            for expr in expression.expressions:
3495                if isinstance(expr, exp.Table):
3496                    expressions.append(f"TABLE {self.sql(expr)}")
3497                else:
3498                    expressions.append(expr)
3499        else:
3500            expressions = expression.expressions
3501
3502        modifier = expression.args.get("modifier")
3503        modifier = f" {modifier}" if modifier else ""
3504        return (
3505            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3506        )
3507
3508    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3509        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3510
3511    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3512        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3513
3514        if expression.args.get("escape"):
3515            path = self.escape_str(path)
3516
3517        if self.QUOTE_JSON_PATH:
3518            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3519
3520        return path
3521
3522    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3523        if isinstance(expression, exp.JSONPathPart):
3524            transform = self.TRANSFORMS.get(expression.__class__)
3525            if not callable(transform):
3526                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3527                return ""
3528
3529            return transform(self, expression)
3530
3531        if isinstance(expression, int):
3532            return str(expression)
3533
3534        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3535            escaped = expression.replace("'", "\\'")
3536            escaped = f"\\'{expression}\\'"
3537        else:
3538            escaped = expression.replace('"', '\\"')
3539            escaped = f'"{escaped}"'
3540
3541        return escaped
3542
3543    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3544        return f"{self.sql(expression, 'this')} FORMAT JSON"
3545
3546    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3547        # Output the Teradata column FORMAT override.
3548        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3549        this = self.sql(expression, "this")
3550        fmt = self.sql(expression, "format")
3551        return f"{this} (FORMAT {fmt})"
3552
3553    def _jsonobject_sql(
3554        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
3555    ) -> str:
3556        null_handling = expression.args.get("null_handling")
3557        null_handling = f" {null_handling}" if null_handling else ""
3558
3559        unique_keys = expression.args.get("unique_keys")
3560        if unique_keys is not None:
3561            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3562        else:
3563            unique_keys = ""
3564
3565        return_type = self.sql(expression, "return_type")
3566        return_type = f" RETURNING {return_type}" if return_type else ""
3567        encoding = self.sql(expression, "encoding")
3568        encoding = f" ENCODING {encoding}" if encoding else ""
3569
3570        if not name:
3571            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
3572
3573        return self.func(
3574            name,
3575            *expression.expressions,
3576            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3577        )
3578
3579    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
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_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3587        )
3588
3589    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3590        this = self.sql(expression, "this")
3591        order = self.sql(expression, "order")
3592        null_handling = expression.args.get("null_handling")
3593        null_handling = f" {null_handling}" if null_handling else ""
3594        return_type = self.sql(expression, "return_type")
3595        return_type = f" RETURNING {return_type}" if return_type else ""
3596        strict = " STRICT" if expression.args.get("strict") else ""
3597        return self.func(
3598            "JSON_ARRAYAGG",
3599            this,
3600            suffix=f"{order}{null_handling}{return_type}{strict})",
3601        )
3602
3603    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3604        path = self.sql(expression, "path")
3605        path = f" PATH {path}" if path else ""
3606        nested_schema = self.sql(expression, "nested_schema")
3607
3608        if nested_schema:
3609            return f"NESTED{path} {nested_schema}"
3610
3611        this = self.sql(expression, "this")
3612        kind = self.sql(expression, "kind")
3613        kind = f" {kind}" if kind else ""
3614        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3615
3616        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3617        return f"{this}{kind}{format_json}{path}{ordinality}"
3618
3619    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3620        return self.func("COLUMNS", *expression.expressions)
3621
3622    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3623        this = self.sql(expression, "this")
3624        path = self.sql(expression, "path")
3625        path = f", {path}" if path else ""
3626        error_handling = expression.args.get("error_handling")
3627        error_handling = f" {error_handling}" if error_handling else ""
3628        empty_handling = expression.args.get("empty_handling")
3629        empty_handling = f" {empty_handling}" if empty_handling else ""
3630        schema = self.sql(expression, "schema")
3631        return self.func(
3632            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3633        )
3634
3635    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3636        this = self.sql(expression, "this")
3637        kind = self.sql(expression, "kind")
3638        path = self.sql(expression, "path")
3639        path = f" {path}" if path else ""
3640        as_json = " AS JSON" if expression.args.get("as_json") else ""
3641        return f"{this} {kind}{path}{as_json}"
3642
3643    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3644        this = self.sql(expression, "this")
3645        path = self.sql(expression, "path")
3646        path = f", {path}" if path else ""
3647        expressions = self.expressions(expression)
3648        with_ = (
3649            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3650            if expressions
3651            else ""
3652        )
3653        return f"OPENJSON({this}{path}){with_}"
3654
3655    def in_sql(self, expression: exp.In) -> str:
3656        query = expression.args.get("query")
3657        unnest = expression.args.get("unnest")
3658        field = expression.args.get("field")
3659        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3660
3661        if query:
3662            in_sql = self.sql(query)
3663        elif unnest:
3664            in_sql = self.in_unnest_op(unnest)
3665        elif field:
3666            in_sql = self.sql(field)
3667        else:
3668            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3669
3670        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3671
3672    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3673        return f"(SELECT {self.sql(unnest)})"
3674
3675    def interval_sql(self, expression: exp.Interval) -> str:
3676        unit_expression = expression.args.get("unit")
3677        unit = self.sql(unit_expression) if unit_expression else ""
3678        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3679            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3680        unit = f" {unit}" if unit else ""
3681
3682        if self.SINGLE_STRING_INTERVAL:
3683            this = expression.this.name if expression.this else ""
3684            if this:
3685                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3686                    return f"INTERVAL '{this}'{unit}"
3687                return f"INTERVAL '{this}{unit}'"
3688            return f"INTERVAL{unit}"
3689
3690        this = self.sql(expression, "this")
3691        if this:
3692            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3693            this = f" {this}" if unwrapped else f" ({this})"
3694
3695        return f"INTERVAL{this}{unit}"
3696
3697    def return_sql(self, expression: exp.Return) -> str:
3698        return f"RETURN {self.sql(expression, 'this')}"
3699
3700    def reference_sql(self, expression: exp.Reference) -> str:
3701        this = self.sql(expression, "this")
3702        expressions = self.expressions(expression, flat=True)
3703        expressions = f"({expressions})" if expressions else ""
3704        options = self.expressions(expression, key="options", flat=True, sep=" ")
3705        options = f" {options}" if options else ""
3706        return f"REFERENCES {this}{expressions}{options}"
3707
3708    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3709        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3710        parent = expression.parent
3711        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3712
3713        return self.func(
3714            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3715        )
3716
3717    def paren_sql(self, expression: exp.Paren) -> str:
3718        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3719        return f"({sql}{self.seg(')', sep='')}"
3720
3721    def neg_sql(self, expression: exp.Neg) -> str:
3722        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3723        this_sql = self.sql(expression, "this")
3724        sep = " " if this_sql[0] == "-" else ""
3725        return f"-{sep}{this_sql}"
3726
3727    def not_sql(self, expression: exp.Not) -> str:
3728        return f"NOT {self.sql(expression, 'this')}"
3729
3730    def alias_sql(self, expression: exp.Alias) -> str:
3731        alias = self.sql(expression, "alias")
3732        alias = f" AS {alias}" if alias else ""
3733        return f"{self.sql(expression, 'this')}{alias}"
3734
3735    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3736        alias = expression.args["alias"]
3737
3738        parent = expression.parent
3739        pivot = parent and parent.parent
3740
3741        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3742            identifier_alias = isinstance(alias, exp.Identifier)
3743            literal_alias = isinstance(alias, exp.Literal)
3744
3745            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3746                alias.replace(exp.Literal.string(alias.output_name))
3747            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3748                alias.replace(exp.to_identifier(alias.output_name))
3749
3750        return self.alias_sql(expression)
3751
3752    def aliases_sql(self, expression: exp.Aliases) -> str:
3753        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3754
3755    def atindex_sql(self, expression: exp.AtIndex) -> str:
3756        this = self.sql(expression, "this")
3757        index = self.sql(expression, "expression")
3758        return f"{this} AT {index}"
3759
3760    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3761        this = self.sql(expression, "this")
3762        zone = self.sql(expression, "zone")
3763        return f"{this} AT TIME ZONE {zone}"
3764
3765    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3766        this = self.sql(expression, "this")
3767        zone = self.sql(expression, "zone")
3768        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3769
3770    def add_sql(self, expression: exp.Add) -> str:
3771        return self.binary(expression, "+")
3772
3773    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
3774        return self.connector_sql(expression, "AND", stack)
3775
3776    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
3777        return self.connector_sql(expression, "OR", stack)
3778
3779    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
3780        return self.connector_sql(expression, "XOR", stack)
3781
3782    def connector_sql(
3783        self,
3784        expression: exp.Connector,
3785        op: str,
3786        stack: list[str | exp.Expr] | None = None,
3787    ) -> str:
3788        if stack is not None:
3789            if expression.expressions:
3790                stack.append(self.expressions(expression, sep=f" {op} "))
3791            else:
3792                stack.append(expression.right)
3793                if expression.comments and self.comments:
3794                    for comment in expression.comments:
3795                        if comment:
3796                            op += f" /*{self.sanitize_comment(comment)}*/"
3797                stack.extend((op, expression.left))
3798            return op
3799
3800        stack = [expression]
3801        sqls: list[str] = []
3802        ops = set()
3803
3804        while stack:
3805            node = stack.pop()
3806            if isinstance(node, exp.Connector):
3807                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3808            else:
3809                sql = self.sql(node)
3810                if sqls and sqls[-1] in ops:
3811                    sqls[-1] += f" {sql}"
3812                else:
3813                    sqls.append(sql)
3814
3815        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3816        return sep.join(sqls)
3817
3818    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3819        return self.binary(expression, "&")
3820
3821    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3822        return self.binary(expression, "<<")
3823
3824    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3825        return f"~{self.sql(expression, 'this')}"
3826
3827    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3828        return self.binary(expression, "|")
3829
3830    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3831        return self.binary(expression, ">>")
3832
3833    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3834        return self.binary(expression, "^")
3835
3836    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
3837        format_sql = self.sql(expression, "format")
3838        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3839        to_sql = self.sql(expression, "to")
3840        to_sql = f" {to_sql}" if to_sql else ""
3841        action = self.sql(expression, "action")
3842        action = f" {action}" if action else ""
3843        default = self.sql(expression, "default")
3844        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3845        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3846
3847    # Base implementation that excludes safe, zone, and target_type metadata args
3848    def strtotime_sql(self, expression: exp.StrToTime) -> str:
3849        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
3850
3851    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3852        zone = self.sql(expression, "this")
3853        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3854
3855    def collate_sql(self, expression: exp.Collate) -> str:
3856        if self.COLLATE_IS_FUNC:
3857            return self.function_fallback_sql(expression)
3858        return self.binary(expression, "COLLATE")
3859
3860    def command_sql(self, expression: exp.Command) -> str:
3861        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3862
3863    def comment_sql(self, expression: exp.Comment) -> str:
3864        this = self.sql(expression, "this")
3865        kind = expression.args["kind"]
3866        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3867        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3868        expression_sql = self.sql(expression, "expression")
3869        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3870
3871    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3872        this = self.sql(expression, "this")
3873        delete = " DELETE" if expression.args.get("delete") else ""
3874        recompress = self.sql(expression, "recompress")
3875        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3876        to_disk = self.sql(expression, "to_disk")
3877        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3878        to_volume = self.sql(expression, "to_volume")
3879        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3880        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3881
3882    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3883        where = self.sql(expression, "where")
3884        group = self.sql(expression, "group")
3885        aggregates = self.expressions(expression, key="aggregates")
3886        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3887
3888        if not (where or group or aggregates) and len(expression.expressions) == 1:
3889            return f"TTL {self.expressions(expression, flat=True)}"
3890
3891        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3892
3893    def transaction_sql(self, expression: exp.Transaction) -> str:
3894        modes = self.expressions(expression, key="modes")
3895        modes = f" {modes}" if modes else ""
3896        return f"BEGIN{modes}"
3897
3898    def commit_sql(self, expression: exp.Commit) -> str:
3899        chain = expression.args.get("chain")
3900        if chain is not None:
3901            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3902
3903        return f"COMMIT{chain or ''}"
3904
3905    def rollback_sql(self, expression: exp.Rollback) -> str:
3906        savepoint = expression.args.get("savepoint")
3907        savepoint = f" TO {savepoint}" if savepoint else ""
3908        return f"ROLLBACK{savepoint}"
3909
3910    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3911        this = self.sql(expression, "this")
3912
3913        dtype = self.sql(expression, "dtype")
3914        if dtype:
3915            collate = self.sql(expression, "collate")
3916            collate = f" COLLATE {collate}" if collate else ""
3917            using = self.sql(expression, "using")
3918            using = f" USING {using}" if using else ""
3919            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3920            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3921
3922        default = self.sql(expression, "default")
3923        if default:
3924            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3925
3926        comment = self.sql(expression, "comment")
3927        if comment:
3928            return f"ALTER COLUMN {this} COMMENT {comment}"
3929
3930        visible = expression.args.get("visible")
3931        if visible:
3932            return f"ALTER COLUMN {this} SET {visible}"
3933
3934        allow_null = expression.args.get("allow_null")
3935        drop = expression.args.get("drop")
3936
3937        if not drop and not allow_null:
3938            self.unsupported("Unsupported ALTER COLUMN syntax")
3939
3940        if allow_null is not None:
3941            keyword = "DROP" if drop else "SET"
3942            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3943
3944        return f"ALTER COLUMN {this} DROP DEFAULT"
3945
3946    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
3947        if not self.SUPPORTS_MODIFY_COLUMN:
3948            self.unsupported("MODIFY COLUMN is not supported in this dialect")
3949        return f"MODIFY COLUMN {self.sql(expression, 'this')}"
3950
3951    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3952        this = self.sql(expression, "this")
3953
3954        visible = expression.args.get("visible")
3955        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3956
3957        return f"ALTER INDEX {this} {visible_sql}"
3958
3959    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3960        this = self.sql(expression, "this")
3961        if not isinstance(expression.this, exp.Var):
3962            this = f"KEY DISTKEY {this}"
3963        return f"ALTER DISTSTYLE {this}"
3964
3965    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3966        compound = " COMPOUND" if expression.args.get("compound") else ""
3967        this = self.sql(expression, "this")
3968        expressions = self.expressions(expression, flat=True)
3969        expressions = f"({expressions})" if expressions else ""
3970        return f"ALTER{compound} SORTKEY {this or expressions}"
3971
3972    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
3973        if not self.RENAME_TABLE_WITH_DB:
3974            # Remove db from tables
3975            expression = expression.transform(
3976                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3977            ).assert_is(exp.AlterRename)
3978        this = self.sql(expression, "this")
3979        to_kw = " TO" if include_to else ""
3980        return f"RENAME{to_kw} {this}"
3981
3982    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3983        exists = " IF EXISTS" if expression.args.get("exists") else ""
3984        old_column = self.sql(expression, "this")
3985        new_column = self.sql(expression, "to")
3986        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3987
3988    def alterset_sql(self, expression: exp.AlterSet) -> str:
3989        exprs = self.expressions(expression, flat=True)
3990        if self.ALTER_SET_WRAPPED:
3991            exprs = f"({exprs})"
3992
3993        return f"SET {exprs}"
3994
3995    def alter_sql(self, expression: exp.Alter) -> str:
3996        actions = expression.args["actions"]
3997
3998        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3999            actions[0], exp.ColumnDef
4000        ):
4001            actions_sql = self.expressions(expression, key="actions", flat=True)
4002            actions_sql = f"ADD {actions_sql}"
4003        else:
4004            actions_list = []
4005            for action in actions:
4006                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4007                    action_sql = self.add_column_sql(action)
4008                else:
4009                    action_sql = self.sql(action)
4010                    if isinstance(action, exp.Query):
4011                        action_sql = f"AS {action_sql}"
4012
4013                actions_list.append(action_sql)
4014
4015            actions_sql = self.format_args(*actions_list).lstrip("\n")
4016
4017        iceberg = (
4018            "ICEBERG "
4019            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4020            else ""
4021        )
4022        exists = " IF EXISTS" if expression.args.get("exists") else ""
4023        on_cluster = self.sql(expression, "cluster")
4024        on_cluster = f" {on_cluster}" if on_cluster else ""
4025        only = " ONLY" if expression.args.get("only") else ""
4026        options = self.expressions(expression, key="options")
4027        options = f", {options}" if options else ""
4028        kind = self.sql(expression, "kind")
4029        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4030        check = " WITH CHECK" if expression.args.get("check") else ""
4031        cascade = (
4032            " CASCADE"
4033            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4034            else ""
4035        )
4036        this = self.sql(expression, "this")
4037        this = f" {this}" if this else ""
4038
4039        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4040
4041    def altersession_sql(self, expression: exp.AlterSession) -> str:
4042        items_sql = self.expressions(expression, flat=True)
4043        keyword = "UNSET" if expression.args.get("unset") else "SET"
4044        return f"{keyword} {items_sql}"
4045
4046    def add_column_sql(self, expression: exp.Expr) -> str:
4047        sql = self.sql(expression)
4048        if isinstance(expression, exp.Schema):
4049            column_text = " COLUMNS"
4050        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4051            column_text = " COLUMN"
4052        else:
4053            column_text = ""
4054
4055        return f"ADD{column_text} {sql}"
4056
4057    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4058        expressions = self.expressions(expression)
4059        exists = " IF EXISTS " if expression.args.get("exists") else " "
4060        return f"DROP{exists}{expressions}"
4061
4062    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4063        return "DROP PRIMARY KEY"
4064
4065    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4066        return f"ADD {self.expressions(expression, indent=False)}"
4067
4068    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4069        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4070        location = self.sql(expression, "location")
4071        location = f" {location}" if location else ""
4072        return f"ADD {exists}{self.sql(expression.this)}{location}"
4073
4074    def distinct_sql(self, expression: exp.Distinct) -> str:
4075        this = self.expressions(expression, flat=True)
4076
4077        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4078            case = exp.case()
4079            for arg in expression.expressions:
4080                case = case.when(arg.is_(exp.null()), exp.null())
4081            this = self.sql(case.else_(f"({this})"))
4082
4083        this = f" {this}" if this else ""
4084
4085        on = self.sql(expression, "on")
4086        on = f" ON {on}" if on else ""
4087        return f"DISTINCT{this}{on}"
4088
4089    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4090        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4091
4092    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4093        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4094
4095    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4096        this_sql = self.sql(expression, "this")
4097        expression_sql = self.sql(expression, "expression")
4098        kind = "MAX" if expression.args.get("max") else "MIN"
4099        return f"{this_sql} HAVING {kind} {expression_sql}"
4100
4101    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4102        return self.sql(
4103            exp.Cast(
4104                this=exp.Div(this=expression.this, expression=expression.expression),
4105                to=exp.DataType(this=exp.DType.INT),
4106            )
4107        )
4108
4109    def dpipe_sql(self, expression: exp.DPipe) -> str:
4110        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4111            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4112        return self.binary(expression, "||")
4113
4114    def div_sql(self, expression: exp.Div) -> str:
4115        l, r = expression.left, expression.right
4116
4117        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4118            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4119
4120        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4121            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4122                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4123
4124        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4125            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4126                return self.sql(
4127                    exp.cast(
4128                        l / r,
4129                        to=exp.DType.BIGINT,
4130                    )
4131                )
4132
4133        return self.binary(expression, "/")
4134
4135    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4136        n = exp._wrap(expression.this, exp.Binary)
4137        d = exp._wrap(expression.expression, exp.Binary)
4138        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4139
4140    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4141        return self.binary(expression, "OVERLAPS")
4142
4143    def distance_sql(self, expression: exp.Distance) -> str:
4144        return self.binary(expression, "<->")
4145
4146    def dot_sql(self, expression: exp.Dot) -> str:
4147        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4148
4149    def eq_sql(self, expression: exp.EQ) -> str:
4150        return self.binary(expression, "=")
4151
4152    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4153        return self.binary(expression, ":=")
4154
4155    def escape_sql(self, expression: exp.Escape) -> str:
4156        this = expression.this
4157        if (
4158            isinstance(this, (exp.Like, exp.ILike))
4159            and isinstance(this.expression, (exp.All, exp.Any))
4160            and not self.SUPPORTS_LIKE_QUANTIFIERS
4161        ):
4162            return self._like_sql(this, escape=expression)
4163        return self.binary(expression, "ESCAPE")
4164
4165    def glob_sql(self, expression: exp.Glob) -> str:
4166        return self.binary(expression, "GLOB")
4167
4168    def gt_sql(self, expression: exp.GT) -> str:
4169        return self.binary(expression, ">")
4170
4171    def gte_sql(self, expression: exp.GTE) -> str:
4172        return self.binary(expression, ">=")
4173
4174    def is_sql(self, expression: exp.Is) -> str:
4175        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4176            return self.sql(
4177                expression.this if expression.expression.this else exp.not_(expression.this)
4178            )
4179        return self.binary(expression, "IS")
4180
4181    def _like_sql(
4182        self,
4183        expression: exp.Like | exp.ILike,
4184        escape: exp.Escape | None = None,
4185    ) -> str:
4186        this = expression.this
4187        rhs = expression.expression
4188
4189        if isinstance(expression, exp.Like):
4190            exp_class: type[exp.Like | exp.ILike] = exp.Like
4191            op = "LIKE"
4192        else:
4193            exp_class = exp.ILike
4194            op = "ILIKE"
4195
4196        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4197            exprs = rhs.this.unnest()
4198
4199            if isinstance(exprs, exp.Tuple):
4200                exprs = exprs.expressions
4201            else:
4202                exprs = [exprs]
4203
4204            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4205
4206            def _make_like(expr: exp.Expression) -> exp.Expression:
4207                like: exp.Expression = exp_class(this=this, expression=expr)
4208                if escape:
4209                    like = exp.Escape(this=like, expression=escape.expression.copy())
4210                return like
4211
4212            like_expr: exp.Expr = _make_like(exprs[0])
4213            for expr in exprs[1:]:
4214                like_expr = connective(like_expr, _make_like(expr), copy=False)
4215
4216            parent = escape.parent if escape else expression.parent
4217            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4218                parent, exp.Condition
4219            ):
4220                like_expr = exp.paren(like_expr, copy=False)
4221
4222            return self.sql(like_expr)
4223
4224        return self.binary(expression, op)
4225
4226    def like_sql(self, expression: exp.Like) -> str:
4227        return self._like_sql(expression)
4228
4229    def ilike_sql(self, expression: exp.ILike) -> str:
4230        return self._like_sql(expression)
4231
4232    def match_sql(self, expression: exp.Match) -> str:
4233        return self.binary(expression, "MATCH")
4234
4235    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4236        return self.binary(expression, "SIMILAR TO")
4237
4238    def lt_sql(self, expression: exp.LT) -> str:
4239        return self.binary(expression, "<")
4240
4241    def lte_sql(self, expression: exp.LTE) -> str:
4242        return self.binary(expression, "<=")
4243
4244    def mod_sql(self, expression: exp.Mod) -> str:
4245        return self.binary(expression, "%")
4246
4247    def mul_sql(self, expression: exp.Mul) -> str:
4248        return self.binary(expression, "*")
4249
4250    def neq_sql(self, expression: exp.NEQ) -> str:
4251        return self.binary(expression, "<>")
4252
4253    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4254        return self.binary(expression, "IS NOT DISTINCT FROM")
4255
4256    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4257        return self.binary(expression, "IS DISTINCT FROM")
4258
4259    def sub_sql(self, expression: exp.Sub) -> str:
4260        return self.binary(expression, "-")
4261
4262    def trycast_sql(self, expression: exp.TryCast) -> str:
4263        return self.cast_sql(expression, safe_prefix="TRY_")
4264
4265    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4266        return self.cast_sql(expression)
4267
4268    def try_sql(self, expression: exp.Try) -> str:
4269        if not self.TRY_SUPPORTED:
4270            self.unsupported("Unsupported TRY function")
4271            return self.sql(expression, "this")
4272
4273        return self.func("TRY", expression.this)
4274
4275    def log_sql(self, expression: exp.Log) -> str:
4276        this = expression.this
4277        expr = expression.expression
4278
4279        if self.dialect.LOG_BASE_FIRST is False:
4280            this, expr = expr, this
4281        elif self.dialect.LOG_BASE_FIRST is None and expr:
4282            if this.name in ("2", "10"):
4283                return self.func(f"LOG{this.name}", expr)
4284
4285            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4286
4287        return self.func("LOG", this, expr)
4288
4289    def use_sql(self, expression: exp.Use) -> str:
4290        kind = self.sql(expression, "kind")
4291        kind = f" {kind}" if kind else ""
4292        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4293        this = f" {this}" if this else ""
4294        return f"USE{kind}{this}"
4295
4296    def binary(self, expression: exp.Binary, op: str) -> str:
4297        sqls: list[str] = []
4298        stack: list[None | str | exp.Expr] = [expression]
4299        binary_type = type(expression)
4300
4301        while stack:
4302            node = stack.pop()
4303
4304            if type(node) is binary_type:
4305                op_func = node.args.get("operator")
4306                if op_func:
4307                    op = f"OPERATOR({self.sql(op_func)})"
4308
4309                stack.append(node.args.get("expression"))
4310                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4311                stack.append(node.args.get("this"))
4312            else:
4313                sqls.append(self.sql(node))
4314
4315        return "".join(sqls)
4316
4317    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4318        to_clause = self.sql(expression, "to")
4319        if to_clause:
4320            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4321
4322        return self.function_fallback_sql(expression)
4323
4324    def function_fallback_sql(self, expression: exp.Func) -> str:
4325        args = []
4326
4327        for key in expression.arg_types:
4328            arg_value = expression.args.get(key)
4329
4330            if isinstance(arg_value, list):
4331                for value in arg_value:
4332                    args.append(value)
4333            elif arg_value is not None:
4334                args.append(arg_value)
4335
4336        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4337            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
4338        else:
4339            name = expression.sql_name()
4340
4341        return self.func(name, *args)
4342
4343    def func(
4344        self,
4345        name: str,
4346        *args: t.Any,
4347        prefix: str = "(",
4348        suffix: str = ")",
4349        normalize: bool = True,
4350    ) -> str:
4351        name = self.normalize_func(name) if normalize else name
4352        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4353
4354    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4355        arg_sqls = tuple(
4356            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4357        )
4358        if self.pretty and self.too_wide(arg_sqls):
4359            return self.indent(
4360                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4361            )
4362        return sep.join(arg_sqls)
4363
4364    def too_wide(self, args: t.Iterable) -> bool:
4365        return sum(len(arg) for arg in args) > self.max_text_width
4366
4367    def format_time(
4368        self,
4369        expression: exp.Expr,
4370        inverse_time_mapping: dict[str, str] | None = None,
4371        inverse_time_trie: dict | None = None,
4372    ) -> str | None:
4373        return format_time(
4374            self.sql(expression, "format"),
4375            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4376            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4377        )
4378
4379    def expressions(
4380        self,
4381        expression: exp.Expr | None = None,
4382        key: str | None = None,
4383        sqls: t.Collection[str | exp.Expr] | None = None,
4384        flat: bool = False,
4385        indent: bool = True,
4386        skip_first: bool = False,
4387        skip_last: bool = False,
4388        sep: str = ", ",
4389        prefix: str = "",
4390        dynamic: bool = False,
4391        new_line: bool = False,
4392    ) -> str:
4393        expressions = expression.args.get(key or "expressions") if expression else sqls
4394
4395        if not expressions:
4396            return ""
4397
4398        if flat:
4399            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4400
4401        num_sqls = len(expressions)
4402        result_sqls = []
4403
4404        for i, e in enumerate(expressions):
4405            sql = self.sql(e, comment=False)
4406            if not sql:
4407                continue
4408
4409            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4410
4411            if self.pretty:
4412                if self.leading_comma:
4413                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4414                else:
4415                    result_sqls.append(
4416                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4417                    )
4418            else:
4419                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4420
4421        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4422            if new_line:
4423                result_sqls.insert(0, "")
4424                result_sqls.append("")
4425            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4426        else:
4427            result_sql = "".join(result_sqls)
4428
4429        return (
4430            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4431            if indent
4432            else result_sql
4433        )
4434
4435    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4436        flat = flat or isinstance(expression.parent, exp.Properties)
4437        expressions_sql = self.expressions(expression, flat=flat)
4438        if flat:
4439            return f"{op} {expressions_sql}"
4440        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4441
4442    def naked_property(self, expression: exp.Property) -> str:
4443        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4444        if not property_name:
4445            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4446        return f"{property_name} {self.sql(expression, 'this')}"
4447
4448    def tag_sql(self, expression: exp.Tag) -> str:
4449        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4450
4451    def token_sql(self, token_type: TokenType) -> str:
4452        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4453
4454    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4455        this = self.sql(expression, "this")
4456        expressions = self.no_identify(self.expressions, expression)
4457        expressions = (
4458            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4459        )
4460        return f"{this}{expressions}" if expressions.strip() != "" else this
4461
4462    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4463        this = self.sql(expression, "this")
4464        expressions = self.expressions(expression, flat=True)
4465        return f"{this}({expressions})"
4466
4467    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4468        return self.binary(expression, "=>")
4469
4470    def when_sql(self, expression: exp.When) -> str:
4471        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4472        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4473        condition = self.sql(expression, "condition")
4474        condition = f" AND {condition}" if condition else ""
4475
4476        then_expression = expression.args.get("then")
4477        if isinstance(then_expression, exp.Insert):
4478            this = self.sql(then_expression, "this")
4479            this = f"INSERT {this}" if this else "INSERT"
4480            then = self.sql(then_expression, "expression")
4481            then = f"{this} VALUES {then}" if then else this
4482        elif isinstance(then_expression, exp.Update):
4483            if isinstance(then_expression.args.get("expressions"), exp.Star):
4484                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4485            else:
4486                expressions_sql = self.expressions(then_expression)
4487                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4488        else:
4489            then = self.sql(then_expression)
4490
4491        if isinstance(then_expression, (exp.Insert, exp.Update)):
4492            where = self.sql(then_expression, "where")
4493            if where and not self.SUPPORTS_MERGE_WHERE:
4494                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4495                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4496                where = ""
4497            then = f"{then}{where}"
4498        return f"WHEN {matched}{source}{condition} THEN {then}"
4499
4500    def whens_sql(self, expression: exp.Whens) -> str:
4501        return self.expressions(expression, sep=" ", indent=False)
4502
4503    def merge_sql(self, expression: exp.Merge) -> str:
4504        table = expression.this
4505        table_alias = ""
4506
4507        hints = table.args.get("hints")
4508        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4509            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4510            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4511
4512        this = self.sql(table)
4513        using = f"USING {self.sql(expression, 'using')}"
4514        whens = self.sql(expression, "whens")
4515
4516        on = self.sql(expression, "on")
4517        on = f"ON {on}" if on else ""
4518
4519        if not on:
4520            on = self.expressions(expression, key="using_cond")
4521            on = f"USING ({on})" if on else ""
4522
4523        returning = self.sql(expression, "returning")
4524        if returning:
4525            whens = f"{whens}{returning}"
4526
4527        sep = self.sep()
4528
4529        return self.prepend_ctes(
4530            expression,
4531            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4532        )
4533
4534    @unsupported_args("format")
4535    def tochar_sql(self, expression: exp.ToChar) -> str:
4536        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
4537
4538    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4539        if not self.SUPPORTS_TO_NUMBER:
4540            self.unsupported("Unsupported TO_NUMBER function")
4541            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4542
4543        fmt = expression.args.get("format")
4544        if not fmt:
4545            self.unsupported("Conversion format is required for TO_NUMBER")
4546            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4547
4548        return self.func("TO_NUMBER", expression.this, fmt)
4549
4550    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4551        this = self.sql(expression, "this")
4552        kind = self.sql(expression, "kind")
4553        settings_sql = self.expressions(expression, key="settings", sep=" ")
4554        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4555        return f"{this}({kind}{args})"
4556
4557    def dictrange_sql(self, expression: exp.DictRange) -> str:
4558        this = self.sql(expression, "this")
4559        max = self.sql(expression, "max")
4560        min = self.sql(expression, "min")
4561        return f"{this}(MIN {min} MAX {max})"
4562
4563    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4564        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4565
4566    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4567        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4568
4569    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4570    def uniquekeyproperty_sql(
4571        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4572    ) -> str:
4573        return f"{prefix} ({self.expressions(expression, flat=True)})"
4574
4575    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4576    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4577        expressions = self.expressions(expression, flat=True)
4578        expressions = f" {self.wrap(expressions)}" if expressions else ""
4579        buckets = self.sql(expression, "buckets")
4580        kind = self.sql(expression, "kind")
4581        buckets = f" BUCKETS {buckets}" if buckets else ""
4582        order = self.sql(expression, "order")
4583        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4584
4585    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4586        return ""
4587
4588    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4589        expressions = self.expressions(expression, key="expressions", flat=True)
4590        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4591        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4592        buckets = self.sql(expression, "buckets")
4593        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4594
4595    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4596        this = self.sql(expression, "this")
4597        having = self.sql(expression, "having")
4598
4599        if having:
4600            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4601
4602        return self.func("ANY_VALUE", this)
4603
4604    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4605        transform = self.func("TRANSFORM", *expression.expressions)
4606        row_format_before = self.sql(expression, "row_format_before")
4607        row_format_before = f" {row_format_before}" if row_format_before else ""
4608        record_writer = self.sql(expression, "record_writer")
4609        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4610        using = f" USING {self.sql(expression, 'command_script')}"
4611        schema = self.sql(expression, "schema")
4612        schema = f" AS {schema}" if schema else ""
4613        row_format_after = self.sql(expression, "row_format_after")
4614        row_format_after = f" {row_format_after}" if row_format_after else ""
4615        record_reader = self.sql(expression, "record_reader")
4616        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4617        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4618
4619    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4620        key_block_size = self.sql(expression, "key_block_size")
4621        if key_block_size:
4622            return f"KEY_BLOCK_SIZE = {key_block_size}"
4623
4624        using = self.sql(expression, "using")
4625        if using:
4626            return f"USING {using}"
4627
4628        parser = self.sql(expression, "parser")
4629        if parser:
4630            return f"WITH PARSER {parser}"
4631
4632        comment = self.sql(expression, "comment")
4633        if comment:
4634            return f"COMMENT {comment}"
4635
4636        visible = expression.args.get("visible")
4637        if visible is not None:
4638            return "VISIBLE" if visible else "INVISIBLE"
4639
4640        engine_attr = self.sql(expression, "engine_attr")
4641        if engine_attr:
4642            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4643
4644        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4645        if secondary_engine_attr:
4646            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4647
4648        self.unsupported("Unsupported index constraint option.")
4649        return ""
4650
4651    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4652        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4653        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4654
4655    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4656        kind = self.sql(expression, "kind")
4657        kind = f"{kind} INDEX" if kind else "INDEX"
4658        this = self.sql(expression, "this")
4659        this = f" {this}" if this else ""
4660        index_type = self.sql(expression, "index_type")
4661        index_type = f" USING {index_type}" if index_type else ""
4662        expressions = self.expressions(expression, flat=True)
4663        expressions = f" ({expressions})" if expressions else ""
4664        options = self.expressions(expression, key="options", sep=" ")
4665        options = f" {options}" if options else ""
4666        return f"{kind}{this}{index_type}{expressions}{options}"
4667
4668    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4669        if self.NVL2_SUPPORTED:
4670            return self.function_fallback_sql(expression)
4671
4672        case = exp.Case().when(
4673            expression.this.is_(exp.null()).not_(copy=False),
4674            expression.args["true"],
4675            copy=False,
4676        )
4677        else_cond = expression.args.get("false")
4678        if else_cond:
4679            case.else_(else_cond, copy=False)
4680
4681        return self.sql(case)
4682
4683    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4684        this = self.sql(expression, "this")
4685        expr = self.sql(expression, "expression")
4686        position = self.sql(expression, "position")
4687        position = f", {position}" if position else ""
4688        iterator = self.sql(expression, "iterator")
4689        condition = self.sql(expression, "condition")
4690        condition = f" IF {condition}" if condition else ""
4691        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
4692
4693    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4694        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4695
4696    def opclass_sql(self, expression: exp.Opclass) -> str:
4697        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4698
4699    def _ml_sql(self, expression: exp.Func, name: str) -> str:
4700        model = self.sql(expression, "this")
4701        model = f"MODEL {model}"
4702        expr = expression.expression
4703        if expr:
4704            expr_sql = self.sql(expression, "expression")
4705            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
4706        else:
4707            expr_sql = None
4708
4709        parameters = self.sql(expression, "params_struct") or None
4710
4711        return self.func(name, model, expr_sql, parameters)
4712
4713    def predict_sql(self, expression: exp.Predict) -> str:
4714        return self._ml_sql(expression, "PREDICT")
4715
4716    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4717        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4718        return self._ml_sql(expression, name)
4719
4720    def generatetext_sql(self, expression: exp.GenerateText) -> str:
4721        return self._ml_sql(expression, "GENERATE_TEXT")
4722
4723    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
4724        return self._ml_sql(expression, "GENERATE_TABLE")
4725
4726    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
4727        return self._ml_sql(expression, "GENERATE_BOOL")
4728
4729    def generateint_sql(self, expression: exp.GenerateInt) -> str:
4730        return self._ml_sql(expression, "GENERATE_INT")
4731
4732    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
4733        return self._ml_sql(expression, "GENERATE_DOUBLE")
4734
4735    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
4736        return self._ml_sql(expression, "TRANSLATE")
4737
4738    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
4739        return self._ml_sql(expression, "FORECAST")
4740
4741    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
4742        this_sql = self.sql(expression, "this")
4743        if isinstance(expression.this, exp.Table):
4744            this_sql = f"TABLE {this_sql}"
4745
4746        return self.func(
4747            "FORECAST",
4748            this_sql,
4749            expression.args.get("data_col"),
4750            expression.args.get("timestamp_col"),
4751            expression.args.get("model"),
4752            expression.args.get("id_cols"),
4753            expression.args.get("horizon"),
4754            expression.args.get("forecast_end_timestamp"),
4755            expression.args.get("confidence_level"),
4756            expression.args.get("output_historical_time_series"),
4757            expression.args.get("context_window"),
4758        )
4759
4760    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4761        this_sql = self.sql(expression, "this")
4762        if isinstance(expression.this, exp.Table):
4763            this_sql = f"TABLE {this_sql}"
4764
4765        return self.func(
4766            "FEATURES_AT_TIME",
4767            this_sql,
4768            expression.args.get("time"),
4769            expression.args.get("num_rows"),
4770            expression.args.get("ignore_feature_nulls"),
4771        )
4772
4773    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
4774        this_sql = self.sql(expression, "this")
4775        if isinstance(expression.this, exp.Table):
4776            this_sql = f"TABLE {this_sql}"
4777
4778        query_table = self.sql(expression, "query_table")
4779        if isinstance(expression.args["query_table"], exp.Table):
4780            query_table = f"TABLE {query_table}"
4781
4782        return self.func(
4783            "VECTOR_SEARCH",
4784            this_sql,
4785            expression.args.get("column_to_search"),
4786            query_table,
4787            expression.args.get("query_column_to_search"),
4788            expression.args.get("top_k"),
4789            expression.args.get("distance_type"),
4790            expression.args.get("options"),
4791        )
4792
4793    def forin_sql(self, expression: exp.ForIn) -> str:
4794        this = self.sql(expression, "this")
4795        expression_sql = self.sql(expression, "expression")
4796        return f"FOR {this} DO {expression_sql}"
4797
4798    def refresh_sql(self, expression: exp.Refresh) -> str:
4799        this = self.sql(expression, "this")
4800        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
4801        return f"REFRESH {kind}{this}"
4802
4803    def toarray_sql(self, expression: exp.ToArray) -> str:
4804        arg = expression.this
4805        if not arg.type:
4806            import sqlglot.optimizer.annotate_types
4807
4808            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
4809
4810        if arg.is_type(exp.DType.ARRAY):
4811            return self.sql(arg)
4812
4813        cond_for_null = arg.is_(exp.null())
4814        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4815
4816    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4817        this = expression.this
4818        time_format = self.format_time(expression)
4819
4820        if time_format:
4821            return self.sql(
4822                exp.cast(
4823                    exp.StrToTime(this=this, format=expression.args["format"]),
4824                    exp.DType.TIME,
4825                )
4826            )
4827
4828        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
4829            return self.sql(this)
4830
4831        return self.sql(exp.cast(this, exp.DType.TIME))
4832
4833    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4834        this = expression.this
4835        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
4836            return self.sql(this)
4837
4838        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
4839
4840    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4841        this = expression.this
4842        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
4843            return self.sql(this)
4844
4845        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
4846
4847    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4848        this = expression.this
4849        time_format = self.format_time(expression)
4850        safe = expression.args.get("safe")
4851        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4852            return self.sql(
4853                exp.cast(
4854                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
4855                    exp.DType.DATE,
4856                )
4857            )
4858
4859        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
4860            return self.sql(this)
4861
4862        if safe:
4863            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
4864
4865        return self.sql(exp.cast(this, exp.DType.DATE))
4866
4867    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4868        return self.sql(
4869            exp.func(
4870                "DATEDIFF",
4871                expression.this,
4872                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
4873                "day",
4874            )
4875        )
4876
4877    def lastday_sql(self, expression: exp.LastDay) -> str:
4878        if self.LAST_DAY_SUPPORTS_DATE_PART:
4879            return self.function_fallback_sql(expression)
4880
4881        unit = expression.text("unit")
4882        if unit and unit != "MONTH":
4883            self.unsupported("Date parts are not supported in LAST_DAY.")
4884
4885        return self.func("LAST_DAY", expression.this)
4886
4887    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4888        import sqlglot.dialects.dialect
4889
4890        return self.func(
4891            "DATE_ADD",
4892            expression.this,
4893            expression.expression,
4894            sqlglot.dialects.dialect.unit_to_str(expression),
4895        )
4896
4897    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4898        if self.CAN_IMPLEMENT_ARRAY_ANY:
4899            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4900            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4901            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4902            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4903
4904        import sqlglot.dialects.dialect
4905
4906        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4907        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
4908            self.unsupported("ARRAY_ANY is unsupported")
4909
4910        return self.function_fallback_sql(expression)
4911
4912    def struct_sql(self, expression: exp.Struct) -> str:
4913        expression.set(
4914            "expressions",
4915            [
4916                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4917                if isinstance(e, exp.PropertyEQ)
4918                else e
4919                for e in expression.expressions
4920            ],
4921        )
4922
4923        return self.function_fallback_sql(expression)
4924
4925    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4926        low = self.sql(expression, "this")
4927        high = self.sql(expression, "expression")
4928
4929        return f"{low} TO {high}"
4930
4931    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4932        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4933        tables = f" {self.expressions(expression)}"
4934
4935        exists = " IF EXISTS" if expression.args.get("exists") else ""
4936
4937        on_cluster = self.sql(expression, "cluster")
4938        on_cluster = f" {on_cluster}" if on_cluster else ""
4939
4940        identity = self.sql(expression, "identity")
4941        identity = f" {identity} IDENTITY" if identity else ""
4942
4943        option = self.sql(expression, "option")
4944        option = f" {option}" if option else ""
4945
4946        partition = self.sql(expression, "partition")
4947        partition = f" {partition}" if partition else ""
4948
4949        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4950
4951    # This transpiles T-SQL's CONVERT function
4952    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4953    def convert_sql(self, expression: exp.Convert) -> str:
4954        to = expression.this
4955        value = expression.expression
4956        style = expression.args.get("style")
4957        safe = expression.args.get("safe")
4958        strict = expression.args.get("strict")
4959
4960        if not to or not value:
4961            return ""
4962
4963        # Retrieve length of datatype and override to default if not specified
4964        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4965            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4966
4967        transformed: exp.Expr | None = None
4968        cast = exp.Cast if strict else exp.TryCast
4969
4970        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4971        if isinstance(style, exp.Literal) and style.is_int:
4972            import sqlglot.dialects.tsql
4973
4974            style_value = style.name
4975            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4976            if not converted_style:
4977                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4978
4979            fmt = exp.Literal.string(converted_style)
4980
4981            if to.this == exp.DType.DATE:
4982                transformed = exp.StrToDate(this=value, format=fmt)
4983            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
4984                transformed = exp.StrToTime(this=value, format=fmt)
4985            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4986                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4987            elif to.this == exp.DType.TEXT:
4988                transformed = exp.TimeToStr(this=value, format=fmt)
4989
4990        if not transformed:
4991            transformed = cast(this=value, to=to, safe=safe)
4992
4993        return self.sql(transformed)
4994
4995    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4996        this = expression.this
4997        if isinstance(this, exp.JSONPathWildcard):
4998            this = self.json_path_part(this)
4999            return f".{this}" if this else ""
5000
5001        if self.SAFE_JSON_PATH_KEY_RE.match(this):
5002            return f".{this}"
5003
5004        this = self.json_path_part(this)
5005        return (
5006            f"[{this}]"
5007            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5008            else f".{this}"
5009        )
5010
5011    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5012        this = self.json_path_part(expression.this)
5013        return f"[{this}]" if this else ""
5014
5015    def _simplify_unless_literal(self, expression: E) -> E:
5016        if not isinstance(expression, exp.Literal):
5017            import sqlglot.optimizer.simplify
5018
5019            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5020
5021        return expression
5022
5023    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5024        this = expression.this
5025        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5026            self.unsupported(
5027                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5028            )
5029            return self.sql(this)
5030
5031        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
5032            if self.IGNORE_NULLS_BEFORE_ORDER:
5033                # The first modifier here will be the one closest to the AggFunc's arg
5034                mods = sorted(
5035                    expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
5036                    key=lambda x: (
5037                        0
5038                        if isinstance(x, exp.HavingMax)
5039                        else (1 if isinstance(x, exp.Order) else 2)
5040                    ),
5041                )
5042
5043                if mods:
5044                    mod = mods[0]
5045                    this = expression.__class__(this=mod.this.copy())
5046                    this.meta["inline"] = True
5047                    mod.this.replace(this)
5048                    return self.sql(expression.this)
5049
5050            agg_func = expression.find(exp.AggFunc)
5051
5052            if agg_func:
5053                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5054                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5055
5056        return f"{self.sql(expression, 'this')} {text}"
5057
5058    def _replace_line_breaks(self, string: str) -> str:
5059        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5060        if self.pretty:
5061            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5062        return string
5063
5064    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5065        option = self.sql(expression, "this")
5066
5067        if expression.expressions:
5068            upper = option.upper()
5069
5070            # Snowflake FILE_FORMAT options are separated by whitespace
5071            sep = " " if upper == "FILE_FORMAT" else ", "
5072
5073            # Databricks copy/format options do not set their list of values with EQ
5074            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5075            values = self.expressions(expression, flat=True, sep=sep)
5076            return f"{option}{op}({values})"
5077
5078        value = self.sql(expression, "expression")
5079
5080        if not value:
5081            return option
5082
5083        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5084
5085        return f"{option}{op}{value}"
5086
5087    def credentials_sql(self, expression: exp.Credentials) -> str:
5088        cred_expr = expression.args.get("credentials")
5089        if isinstance(cred_expr, exp.Literal):
5090            # Redshift case: CREDENTIALS <string>
5091            credentials = self.sql(expression, "credentials")
5092            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5093        else:
5094            # Snowflake case: CREDENTIALS = (...)
5095            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5096            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5097
5098        storage = self.sql(expression, "storage")
5099        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5100
5101        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5102        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5103
5104        iam_role = self.sql(expression, "iam_role")
5105        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5106
5107        region = self.sql(expression, "region")
5108        region = f" REGION {region}" if region else ""
5109
5110        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5111
5112    def copy_sql(self, expression: exp.Copy) -> str:
5113        this = self.sql(expression, "this")
5114        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5115
5116        credentials = self.sql(expression, "credentials")
5117        credentials = self.seg(credentials) if credentials else ""
5118        files = self.expressions(expression, key="files", flat=True)
5119        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5120
5121        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5122        params = self.expressions(
5123            expression,
5124            key="params",
5125            sep=sep,
5126            new_line=True,
5127            skip_last=True,
5128            skip_first=True,
5129            indent=self.COPY_PARAMS_ARE_WRAPPED,
5130        )
5131
5132        if params:
5133            if self.COPY_PARAMS_ARE_WRAPPED:
5134                params = f" WITH ({params})"
5135            elif not self.pretty and (files or credentials):
5136                params = f" {params}"
5137
5138        return f"COPY{this}{kind} {files}{credentials}{params}"
5139
5140    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5141        return ""
5142
5143    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5144        on_sql = "ON" if expression.args.get("on") else "OFF"
5145        filter_col: str | None = self.sql(expression, "filter_column")
5146        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5147        retention_period: str | None = self.sql(expression, "retention_period")
5148        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5149
5150        if filter_col or retention_period:
5151            on_sql = self.func("ON", filter_col, retention_period)
5152
5153        return f"DATA_DELETION={on_sql}"
5154
5155    def maskingpolicycolumnconstraint_sql(
5156        self, expression: exp.MaskingPolicyColumnConstraint
5157    ) -> str:
5158        this = self.sql(expression, "this")
5159        expressions = self.expressions(expression, flat=True)
5160        expressions = f" USING ({expressions})" if expressions else ""
5161        return f"MASKING POLICY {this}{expressions}"
5162
5163    def gapfill_sql(self, expression: exp.GapFill) -> str:
5164        this = self.sql(expression, "this")
5165        this = f"TABLE {this}"
5166        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5167
5168    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5169        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5170
5171    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5172        this = self.sql(expression, "this")
5173        expr = expression.expression
5174
5175        if isinstance(expr, exp.Func):
5176            # T-SQL's CLR functions are case sensitive
5177            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5178        else:
5179            expr = self.sql(expression, "expression")
5180
5181        return self.scope_resolution(expr, this)
5182
5183    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5184        if self.PARSE_JSON_NAME is None:
5185            return self.sql(expression.this)
5186
5187        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5188
5189    def rand_sql(self, expression: exp.Rand) -> str:
5190        lower = self.sql(expression, "lower")
5191        upper = self.sql(expression, "upper")
5192
5193        if lower and upper:
5194            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5195        return self.func("RAND", expression.this)
5196
5197    def changes_sql(self, expression: exp.Changes) -> str:
5198        information = self.sql(expression, "information")
5199        information = f"INFORMATION => {information}"
5200        at_before = self.sql(expression, "at_before")
5201        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5202        end = self.sql(expression, "end")
5203        end = f"{self.seg('')}{end}" if end else ""
5204
5205        return f"CHANGES ({information}){at_before}{end}"
5206
5207    def pad_sql(self, expression: exp.Pad) -> str:
5208        prefix = "L" if expression.args.get("is_left") else "R"
5209
5210        fill_pattern = self.sql(expression, "fill_pattern") or None
5211        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5212            fill_pattern = "' '"
5213
5214        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5215
5216    def summarize_sql(self, expression: exp.Summarize) -> str:
5217        table = " TABLE" if expression.args.get("table") else ""
5218        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5219
5220    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5221        generate_series = exp.GenerateSeries(**expression.args)
5222
5223        parent = expression.parent
5224        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5225            parent = parent.parent
5226
5227        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5228            return self.sql(exp.Unnest(expressions=[generate_series]))
5229
5230        if isinstance(parent, exp.Select):
5231            self.unsupported("GenerateSeries projection unnesting is not supported.")
5232
5233        return self.sql(generate_series)
5234
5235    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5236        if self.SUPPORTS_CONVERT_TIMEZONE:
5237            return self.function_fallback_sql(expression)
5238
5239        source_tz = expression.args.get("source_tz")
5240        target_tz = expression.args.get("target_tz")
5241        timestamp = expression.args.get("timestamp")
5242
5243        if source_tz and timestamp:
5244            timestamp = exp.AtTimeZone(
5245                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5246            )
5247
5248        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5249
5250        return self.sql(expr)
5251
5252    def json_sql(self, expression: exp.JSON) -> str:
5253        this = self.sql(expression, "this")
5254        this = f" {this}" if this else ""
5255
5256        _with = expression.args.get("with_")
5257
5258        if _with is None:
5259            with_sql = ""
5260        elif not _with:
5261            with_sql = " WITHOUT"
5262        else:
5263            with_sql = " WITH"
5264
5265        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5266
5267        return f"JSON{this}{with_sql}{unique_sql}"
5268
5269    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5270        path = self.sql(expression, "path")
5271        returning = self.sql(expression, "returning")
5272        returning = f" RETURNING {returning}" if returning else ""
5273
5274        on_condition = self.sql(expression, "on_condition")
5275        on_condition = f" {on_condition}" if on_condition else ""
5276
5277        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5278
5279    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5280        regexp = " REGEXP" if expression.args.get("regexp") else ""
5281        return f"SKIP{regexp} {self.sql(expression.expression)}"
5282
5283    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5284        else_ = "ELSE " if expression.args.get("else_") else ""
5285        condition = self.sql(expression, "expression")
5286        condition = f"WHEN {condition} THEN " if condition else else_
5287        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5288        return f"{condition}{insert}"
5289
5290    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5291        kind = self.sql(expression, "kind")
5292        expressions = self.seg(self.expressions(expression, sep=" "))
5293        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
5294        return res
5295
5296    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5297        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5298        empty = expression.args.get("empty")
5299        empty = (
5300            f"DEFAULT {empty} ON EMPTY"
5301            if isinstance(empty, exp.Expr)
5302            else self.sql(expression, "empty")
5303        )
5304
5305        error = expression.args.get("error")
5306        error = (
5307            f"DEFAULT {error} ON ERROR"
5308            if isinstance(error, exp.Expr)
5309            else self.sql(expression, "error")
5310        )
5311
5312        if error and empty:
5313            error = (
5314                f"{empty} {error}"
5315                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5316                else f"{error} {empty}"
5317            )
5318            empty = ""
5319
5320        null = self.sql(expression, "null")
5321
5322        return f"{empty}{error}{null}"
5323
5324    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5325        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5326        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5327
5328    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5329        this = self.sql(expression, "this")
5330        path = self.sql(expression, "path")
5331
5332        passing = self.expressions(expression, "passing")
5333        passing = f" PASSING {passing}" if passing else ""
5334
5335        on_condition = self.sql(expression, "on_condition")
5336        on_condition = f" {on_condition}" if on_condition else ""
5337
5338        path = f"{path}{passing}{on_condition}"
5339
5340        return self.func("JSON_EXISTS", this, path)
5341
5342    def _add_arrayagg_null_filter(
5343        self,
5344        array_agg_sql: str,
5345        array_agg_expr: exp.ArrayAgg,
5346        column_expr: exp.Expr,
5347    ) -> str:
5348        """
5349        Add NULL filter to ARRAY_AGG if dialect requires it.
5350
5351        Args:
5352            array_agg_sql: The generated ARRAY_AGG SQL string
5353            array_agg_expr: The ArrayAgg expression node
5354            column_expr: The column/expression to filter (before ORDER BY wrapping)
5355
5356        Returns:
5357            SQL string with FILTER clause added if needed
5358        """
5359        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5360        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5361        if not (
5362            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5363        ):
5364            return array_agg_sql
5365
5366        parent = array_agg_expr.parent
5367        if isinstance(parent, exp.Filter):
5368            parent_cond = parent.expression.this
5369            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5370        elif column_expr.find(exp.Column):
5371            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5372            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5373            this_sql = (
5374                self.expressions(column_expr)
5375                if isinstance(column_expr, exp.Distinct)
5376                else self.sql(column_expr)
5377            )
5378            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5379
5380        return array_agg_sql
5381
5382    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5383        array_agg = self.function_fallback_sql(expression)
5384        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
5385
5386    def slice_sql(self, expression: exp.Slice) -> str:
5387        step = self.sql(expression, "step")
5388        end = self.sql(expression.expression)
5389        begin = self.sql(expression.this)
5390
5391        sql = f"{end}:{step}" if step else end
5392        return f"{begin}:{sql}" if sql else f"{begin}:"
5393
5394    def apply_sql(self, expression: exp.Apply) -> str:
5395        this = self.sql(expression, "this")
5396        expr = self.sql(expression, "expression")
5397
5398        return f"{this} APPLY({expr})"
5399
5400    def _grant_or_revoke_sql(
5401        self,
5402        expression: exp.Grant | exp.Revoke,
5403        keyword: str,
5404        preposition: str,
5405        grant_option_prefix: str = "",
5406        grant_option_suffix: str = "",
5407    ) -> str:
5408        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5409
5410        kind = self.sql(expression, "kind")
5411        kind = f" {kind}" if kind else ""
5412
5413        securable = self.sql(expression, "securable")
5414        securable = f" {securable}" if securable else ""
5415
5416        principals = self.expressions(expression, key="principals", flat=True)
5417
5418        if not expression.args.get("grant_option"):
5419            grant_option_prefix = grant_option_suffix = ""
5420
5421        # cascade for revoke only
5422        cascade = self.sql(expression, "cascade")
5423        cascade = f" {cascade}" if cascade else ""
5424
5425        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5426
5427    def grant_sql(self, expression: exp.Grant) -> str:
5428        return self._grant_or_revoke_sql(
5429            expression,
5430            keyword="GRANT",
5431            preposition="TO",
5432            grant_option_suffix=" WITH GRANT OPTION",
5433        )
5434
5435    def revoke_sql(self, expression: exp.Revoke) -> str:
5436        return self._grant_or_revoke_sql(
5437            expression,
5438            keyword="REVOKE",
5439            preposition="FROM",
5440            grant_option_prefix="GRANT OPTION FOR ",
5441        )
5442
5443    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5444        this = self.sql(expression, "this")
5445        columns = self.expressions(expression, flat=True)
5446        columns = f"({columns})" if columns else ""
5447
5448        return f"{this}{columns}"
5449
5450    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5451        this = self.sql(expression, "this")
5452
5453        kind = self.sql(expression, "kind")
5454        kind = f"{kind} " if kind else ""
5455
5456        return f"{kind}{this}"
5457
5458    def columns_sql(self, expression: exp.Columns) -> str:
5459        func = self.function_fallback_sql(expression)
5460        if expression.args.get("unpack"):
5461            func = f"*{func}"
5462
5463        return func
5464
5465    def overlay_sql(self, expression: exp.Overlay) -> str:
5466        this = self.sql(expression, "this")
5467        expr = self.sql(expression, "expression")
5468        from_sql = self.sql(expression, "from_")
5469        for_sql = self.sql(expression, "for_")
5470        for_sql = f" FOR {for_sql}" if for_sql else ""
5471
5472        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
5473
5474    @unsupported_args("format")
5475    def todouble_sql(self, expression: exp.ToDouble) -> str:
5476        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5477        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
5478
5479    def string_sql(self, expression: exp.String) -> str:
5480        this = expression.this
5481        zone = expression.args.get("zone")
5482
5483        if zone:
5484            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5485            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5486            # set for source_tz to transpile the time conversion before the STRING cast
5487            this = exp.ConvertTimezone(
5488                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5489            )
5490
5491        return self.sql(exp.cast(this, exp.DType.VARCHAR))
5492
5493    def median_sql(self, expression: exp.Median) -> str:
5494        if not self.SUPPORTS_MEDIAN:
5495            return self.sql(
5496                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5497            )
5498
5499        return self.function_fallback_sql(expression)
5500
5501    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5502        filler = self.sql(expression, "this")
5503        filler = f" {filler}" if filler else ""
5504        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5505        return f"TRUNCATE{filler} {with_count}"
5506
5507    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5508        if self.SUPPORTS_UNIX_SECONDS:
5509            return self.function_fallback_sql(expression)
5510
5511        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
5512
5513        return self.sql(
5514            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5515        )
5516
5517    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5518        dim = expression.expression
5519
5520        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5521        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5522            if not (dim.is_int and dim.name == "1"):
5523                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5524            dim = None
5525
5526        # If dimension is required but not specified, default initialize it
5527        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5528            dim = exp.Literal.number(1)
5529
5530        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5531
5532    def attach_sql(self, expression: exp.Attach) -> str:
5533        this = self.sql(expression, "this")
5534        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5535        expressions = self.expressions(expression)
5536        expressions = f" ({expressions})" if expressions else ""
5537
5538        return f"ATTACH{exists_sql} {this}{expressions}"
5539
5540    def detach_sql(self, expression: exp.Detach) -> str:
5541        kind = self.sql(expression, "kind")
5542        kind = f" {kind}" if kind else ""
5543        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
5544        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5545        exists = " IF EXISTS" if expression.args.get("exists") else ""
5546        if exists:
5547            kind = kind or " DATABASE"
5548
5549        this = self.sql(expression, "this")
5550        this = f" {this}" if this else ""
5551        cluster = self.sql(expression, "cluster")
5552        cluster = f" {cluster}" if cluster else ""
5553        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
5554        sync = " SYNC" if expression.args.get("sync") else ""
5555        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
5556
5557    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5558        this = self.sql(expression, "this")
5559        value = self.sql(expression, "expression")
5560        value = f" {value}" if value else ""
5561        return f"{this}{value}"
5562
5563    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5564        return (
5565            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5566        )
5567
5568    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5569        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5570        encode = f"{encode} {self.sql(expression, 'this')}"
5571
5572        properties = expression.args.get("properties")
5573        if properties:
5574            encode = f"{encode} {self.properties(properties)}"
5575
5576        return encode
5577
5578    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5579        this = self.sql(expression, "this")
5580        include = f"INCLUDE {this}"
5581
5582        column_def = self.sql(expression, "column_def")
5583        if column_def:
5584            include = f"{include} {column_def}"
5585
5586        alias = self.sql(expression, "alias")
5587        if alias:
5588            include = f"{include} AS {alias}"
5589
5590        return include
5591
5592    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5593        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
5594        name = f"{prefix} {self.sql(expression, 'this')}"
5595        return self.func("XMLELEMENT", name, *expression.expressions)
5596
5597    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5598        this = self.sql(expression, "this")
5599        expr = self.sql(expression, "expression")
5600        expr = f"({expr})" if expr else ""
5601        return f"{this}{expr}"
5602
5603    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5604        partitions = self.expressions(expression, "partition_expressions")
5605        create = self.expressions(expression, "create_expressions")
5606        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
5607
5608    def partitionbyrangepropertydynamic_sql(
5609        self, expression: exp.PartitionByRangePropertyDynamic
5610    ) -> str:
5611        start = self.sql(expression, "start")
5612        end = self.sql(expression, "end")
5613
5614        every = expression.args["every"]
5615        if isinstance(every, exp.Interval) and every.this.is_string:
5616            every.this.replace(exp.Literal.number(every.name))
5617
5618        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5619
5620    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5621        name = self.sql(expression, "this")
5622        values = self.expressions(expression, flat=True)
5623
5624        return f"NAME {name} VALUE {values}"
5625
5626    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5627        kind = self.sql(expression, "kind")
5628        sample = self.sql(expression, "sample")
5629        return f"SAMPLE {sample} {kind}"
5630
5631    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5632        kind = self.sql(expression, "kind")
5633        option = self.sql(expression, "option")
5634        option = f" {option}" if option else ""
5635        this = self.sql(expression, "this")
5636        this = f" {this}" if this else ""
5637        columns = self.expressions(expression)
5638        columns = f" {columns}" if columns else ""
5639        return f"{kind}{option} STATISTICS{this}{columns}"
5640
5641    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5642        this = self.sql(expression, "this")
5643        columns = self.expressions(expression)
5644        inner_expression = self.sql(expression, "expression")
5645        inner_expression = f" {inner_expression}" if inner_expression else ""
5646        update_options = self.sql(expression, "update_options")
5647        update_options = f" {update_options} UPDATE" if update_options else ""
5648        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
5649
5650    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5651        kind = self.sql(expression, "kind")
5652        kind = f" {kind}" if kind else ""
5653        return f"DELETE{kind} STATISTICS"
5654
5655    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5656        inner_expression = self.sql(expression, "expression")
5657        return f"LIST CHAINED ROWS{inner_expression}"
5658
5659    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5660        kind = self.sql(expression, "kind")
5661        this = self.sql(expression, "this")
5662        this = f" {this}" if this else ""
5663        inner_expression = self.sql(expression, "expression")
5664        return f"VALIDATE {kind}{this}{inner_expression}"
5665
5666    def analyze_sql(self, expression: exp.Analyze) -> str:
5667        options = self.expressions(expression, key="options", sep=" ")
5668        options = f" {options}" if options else ""
5669        kind = self.sql(expression, "kind")
5670        kind = f" {kind}" if kind else ""
5671        this = self.sql(expression, "this")
5672        this = f" {this}" if this else ""
5673        mode = self.sql(expression, "mode")
5674        mode = f" {mode}" if mode else ""
5675        properties = self.sql(expression, "properties")
5676        properties = f" {properties}" if properties else ""
5677        partition = self.sql(expression, "partition")
5678        partition = f" {partition}" if partition else ""
5679        inner_expression = self.sql(expression, "expression")
5680        inner_expression = f" {inner_expression}" if inner_expression else ""
5681        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5682
5683    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5684        this = self.sql(expression, "this")
5685        namespaces = self.expressions(expression, key="namespaces")
5686        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5687        passing = self.expressions(expression, key="passing")
5688        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5689        columns = self.expressions(expression, key="columns")
5690        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5691        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5692        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5693
5694    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5695        this = self.sql(expression, "this")
5696        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
5697
5698    def export_sql(self, expression: exp.Export) -> str:
5699        this = self.sql(expression, "this")
5700        connection = self.sql(expression, "connection")
5701        connection = f"WITH CONNECTION {connection} " if connection else ""
5702        options = self.sql(expression, "options")
5703        return f"EXPORT DATA {connection}{options} AS {this}"
5704
5705    def declare_sql(self, expression: exp.Declare) -> str:
5706        replace = "OR REPLACE " if expression.args.get("replace") else ""
5707        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
5708
5709    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5710        variables = self.expressions(expression, "this")
5711        default = self.sql(expression, "default")
5712        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
5713
5714        kind = self.sql(expression, "kind")
5715        if isinstance(expression.args.get("kind"), exp.Schema):
5716            kind = f"TABLE {kind}"
5717
5718        kind = f" {kind}" if kind else ""
5719
5720        return f"{variables}{kind}{default}"
5721
5722    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5723        kind = self.sql(expression, "kind")
5724        this = self.sql(expression, "this")
5725        set = self.sql(expression, "expression")
5726        using = self.sql(expression, "using")
5727        using = f" USING {using}" if using else ""
5728
5729        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5730
5731        return f"{kind_sql} {this} SET {set}{using}"
5732
5733    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5734        params = self.expressions(expression, key="params", flat=True)
5735        return self.func(expression.name, *expression.expressions) + f"({params})"
5736
5737    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5738        return self.func(expression.name, *expression.expressions)
5739
5740    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5741        return self.anonymousaggfunc_sql(expression)
5742
5743    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5744        return self.parameterizedagg_sql(expression)
5745
5746    def show_sql(self, expression: exp.Show) -> str:
5747        self.unsupported("Unsupported SHOW statement")
5748        return ""
5749
5750    def install_sql(self, expression: exp.Install) -> str:
5751        self.unsupported("Unsupported INSTALL statement")
5752        return ""
5753
5754    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5755        # Snowflake GET/PUT statements:
5756        #   PUT <file> <internalStage> <properties>
5757        #   GET <internalStage> <file> <properties>
5758        props = expression.args.get("properties")
5759        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5760        this = self.sql(expression, "this")
5761        target = self.sql(expression, "target")
5762
5763        if isinstance(expression, exp.Put):
5764            return f"PUT {this} {target}{props_sql}"
5765        else:
5766            return f"GET {target} {this}{props_sql}"
5767
5768    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
5769        this = self.sql(expression, "this")
5770        expr = self.sql(expression, "expression")
5771        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5772        return f"TRANSLATE({this} USING {expr}{with_error})"
5773
5774    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5775        if self.SUPPORTS_DECODE_CASE:
5776            return self.func("DECODE", *expression.expressions)
5777
5778        decode_expr, *expressions = expression.expressions
5779
5780        ifs = []
5781        for search, result in zip(expressions[::2], expressions[1::2]):
5782            if isinstance(search, exp.Literal):
5783                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
5784            elif isinstance(search, exp.Null):
5785                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
5786            else:
5787                if isinstance(search, exp.Binary):
5788                    search = exp.paren(search)
5789
5790                cond = exp.or_(
5791                    decode_expr.eq(search),
5792                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5793                    copy=False,
5794                )
5795                ifs.append(exp.If(this=cond, true=result))
5796
5797        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5798        return self.sql(case)
5799
5800    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5801        this = self.sql(expression, "this")
5802        this = self.seg(this, sep="")
5803        dimensions = self.expressions(
5804            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5805        )
5806        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5807        metrics = self.expressions(
5808            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5809        )
5810        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5811        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
5812        facts = self.seg(f"FACTS {facts}") if facts else ""
5813        where = self.sql(expression, "where")
5814        where = self.seg(f"WHERE {where}") if where else ""
5815        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
5816        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5817
5818    def getextract_sql(self, expression: exp.GetExtract) -> str:
5819        this = expression.this
5820        expr = expression.expression
5821
5822        if not this.type or not expression.type:
5823            import sqlglot.optimizer.annotate_types
5824
5825            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
5826
5827        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
5828            return self.sql(exp.Bracket(this=this, expressions=[expr]))
5829
5830        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
5831
5832    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
5833        return self.sql(
5834            exp.DateAdd(
5835                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5836                expression=expression.this,
5837                unit=exp.var("DAY"),
5838            )
5839        )
5840
5841    def space_sql(self: Generator, expression: exp.Space) -> str:
5842        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
5843
5844    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
5845        return f"BUILD {self.sql(expression, 'this')}"
5846
5847    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
5848        method = self.sql(expression, "method")
5849        kind = expression.args.get("kind")
5850        if not kind:
5851            return f"REFRESH {method}"
5852
5853        every = self.sql(expression, "every")
5854        unit = self.sql(expression, "unit")
5855        every = f" EVERY {every} {unit}" if every else ""
5856        starts = self.sql(expression, "starts")
5857        starts = f" STARTS {starts}" if starts else ""
5858
5859        return f"REFRESH {method} ON {kind}{every}{starts}"
5860
5861    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
5862        self.unsupported("The model!attribute syntax is not supported")
5863        return ""
5864
5865    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
5866        return self.func("DIRECTORY", expression.this)
5867
5868    def uuid_sql(self, expression: exp.Uuid) -> str:
5869        is_string = expression.args.get("is_string", False)
5870        uuid_func_sql = self.func("UUID")
5871
5872        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
5873            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
5874
5875        return uuid_func_sql
5876
5877    def initcap_sql(self, expression: exp.Initcap) -> str:
5878        delimiters = expression.expression
5879
5880        if delimiters:
5881            # do not generate delimiters arg if we are round-tripping from default delimiters
5882            if (
5883                delimiters.is_string
5884                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
5885            ):
5886                delimiters = None
5887            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
5888                self.unsupported("INITCAP does not support custom delimiters")
5889                delimiters = None
5890
5891        return self.func("INITCAP", expression.this, delimiters)
5892
5893    def localtime_sql(self, expression: exp.Localtime) -> str:
5894        this = expression.this
5895        return self.func("LOCALTIME", this) if this else "LOCALTIME"
5896
5897    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
5898        this = expression.this
5899        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
5900
5901    def weekstart_sql(self, expression: exp.WeekStart) -> str:
5902        this = expression.this.name.upper()
5903        if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
5904            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
5905            return "WEEK"
5906
5907        return self.func("WEEK", expression.this)
5908
5909    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
5910        this = self.expressions(expression)
5911        charset = self.sql(expression, "charset")
5912        using = f" USING {charset}" if charset else ""
5913        return self.func(name, this + using)
5914
5915    def block_sql(self, expression: exp.Block) -> str:
5916        expressions = self.expressions(expression, sep="; ", flat=True)
5917        return f"{expressions}" if expressions else ""
5918
5919    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
5920        self.unsupported("Unsupported Stored Procedure syntax")
5921        return ""
5922
5923    def ifblock_sql(self, expression: exp.IfBlock) -> str:
5924        self.unsupported("Unsupported If block syntax")
5925        return ""
5926
5927    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
5928        self.unsupported("Unsupported While block syntax")
5929        return ""
5930
5931    def execute_sql(self, expression: exp.Execute) -> str:
5932        self.unsupported("Unsupported Execute syntax")
5933        return ""
5934
5935    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
5936        self.unsupported("Unsupported Execute syntax")
5937        return ""
5938
5939    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
5940        props = self.expressions(expression, sep=" ")
5941        return f"MODIFY {props}"
5942
5943    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
5944        kind = expression.args.get("kind")
5945        return f"USING {kind} {self.sql(expression, 'this')}"
5946
5947    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
5948        this = self.sql(expression, "this")
5949        to = self.sql(expression, "to")
5950        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 ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported
 469    SUPPORTS_MODIFY_COLUMN = False
 470
 471    # Whether the LikeProperty needs to be specified inside of the schema clause
 472    LIKE_PROPERTY_INSIDE_SCHEMA = False
 473
 474    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 475    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 476    MULTI_ARG_DISTINCT = True
 477
 478    # Whether the JSON extraction operators expect a value of type JSON
 479    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 480
 481    # Whether bracketed keys like ["foo"] are supported in JSON paths
 482    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 483
 484    # Whether to escape keys using single quotes in JSON paths
 485    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 486
 487    # The JSONPathPart expressions supported by this dialect
 488    SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy()
 489
 490    # Whether any(f(x) for x in array) can be implemented by this dialect
 491    CAN_IMPLEMENT_ARRAY_ANY = False
 492
 493    # Whether the function TO_NUMBER is supported
 494    SUPPORTS_TO_NUMBER = True
 495
 496    # Whether EXCLUDE in window specification is supported
 497    SUPPORTS_WINDOW_EXCLUDE = False
 498
 499    # Whether or not set op modifiers apply to the outer set op or select.
 500    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 501    # True means limit 1 happens after the set op, False means it it happens on y.
 502    SET_OP_MODIFIERS = True
 503
 504    # Whether parameters from COPY statement are wrapped in parentheses
 505    COPY_PARAMS_ARE_WRAPPED = True
 506
 507    # Whether values of params are set with "=" token or empty space
 508    COPY_PARAMS_EQ_REQUIRED = False
 509
 510    # Whether COPY statement has INTO keyword
 511    COPY_HAS_INTO_KEYWORD = True
 512
 513    # Whether the conditional TRY(expression) function is supported
 514    TRY_SUPPORTED = True
 515
 516    # Whether the UESCAPE syntax in unicode strings is supported
 517    SUPPORTS_UESCAPE = True
 518
 519    # Function used to replace escaped unicode codes in unicode strings
 520    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None
 521
 522    # The keyword to use when generating a star projection with excluded columns
 523    STAR_EXCEPT = "EXCEPT"
 524
 525    # The HEX function name
 526    HEX_FUNC = "HEX"
 527
 528    # The keywords to use when prefixing & separating WITH based properties
 529    WITH_PROPERTIES_PREFIX = "WITH"
 530
 531    # Whether to quote the generated expression of exp.JsonPath
 532    QUOTE_JSON_PATH = True
 533
 534    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 535    PAD_FILL_PATTERN_IS_REQUIRED = False
 536
 537    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 538    SUPPORTS_EXPLODING_PROJECTIONS = True
 539
 540    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 541    ARRAY_CONCAT_IS_VAR_LEN = True
 542
 543    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 544    SUPPORTS_CONVERT_TIMEZONE = False
 545
 546    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 547    SUPPORTS_MEDIAN = True
 548
 549    # Whether UNIX_SECONDS(timestamp) is supported
 550    SUPPORTS_UNIX_SECONDS = False
 551
 552    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 553    ALTER_SET_WRAPPED = False
 554
 555    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 556    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 557    # TODO: The normalization should be done by default once we've tested it across all dialects.
 558    NORMALIZE_EXTRACT_DATE_PARTS = False
 559
 560    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 561    PARSE_JSON_NAME: str | None = "PARSE_JSON"
 562
 563    # The function name of the exp.ArraySize expression
 564    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 565
 566    # The syntax to use when altering the type of a column
 567    ALTER_SET_TYPE = "SET DATA TYPE"
 568
 569    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 570    # None -> Doesn't support it at all
 571    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 572    # True (Postgres) -> Explicitly requires it
 573    ARRAY_SIZE_DIM_REQUIRED: bool | None = None
 574
 575    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 576    SUPPORTS_DECODE_CASE = True
 577
 578    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 579    SUPPORTS_BETWEEN_FLAGS = False
 580
 581    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 582    SUPPORTS_LIKE_QUANTIFIERS = True
 583
 584    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 585    MATCH_AGAINST_TABLE_PREFIX: str | None = None
 586
 587    # Whether to include the VARIABLE keyword for SET assignments
 588    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 589
 590    # The keyword to use for default value assignment in DECLARE statements
 591    DECLARE_DEFAULT_ASSIGNMENT = "="
 592
 593    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 594    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 595    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 596    UPDATE_STATEMENT_SUPPORTS_FROM = True
 597
 598    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 599    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 600
 601    # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.:
 602    # - Snowflake: DROP ICEBERG TABLE a.b;
 603    # - DuckDB:    DROP TABLE a.b;
 604    SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True
 605
 606    TYPE_MAPPING: t.ClassVar = {
 607        exp.DType.DATETIME2: "TIMESTAMP",
 608        exp.DType.NCHAR: "CHAR",
 609        exp.DType.NVARCHAR: "VARCHAR",
 610        exp.DType.MEDIUMTEXT: "TEXT",
 611        exp.DType.LONGTEXT: "TEXT",
 612        exp.DType.TINYTEXT: "TEXT",
 613        exp.DType.BLOB: "VARBINARY",
 614        exp.DType.MEDIUMBLOB: "BLOB",
 615        exp.DType.LONGBLOB: "BLOB",
 616        exp.DType.TINYBLOB: "BLOB",
 617        exp.DType.INET: "INET",
 618        exp.DType.ROWVERSION: "VARBINARY",
 619        exp.DType.SMALLDATETIME: "TIMESTAMP",
 620    }
 621
 622    UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set()
 623
 624    TIME_PART_SINGULARS: t.ClassVar = {
 625        "MICROSECONDS": "MICROSECOND",
 626        "SECONDS": "SECOND",
 627        "MINUTES": "MINUTE",
 628        "HOURS": "HOUR",
 629        "DAYS": "DAY",
 630        "WEEKS": "WEEK",
 631        "MONTHS": "MONTH",
 632        "QUARTERS": "QUARTER",
 633        "YEARS": "YEAR",
 634    }
 635
 636    AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = {
 637        "cluster": lambda self, e: self.sql(e, "cluster"),
 638        "distribute": lambda self, e: self.sql(e, "distribute"),
 639        "sort": lambda self, e: self.sql(e, "sort"),
 640        **AFTER_HAVING_MODIFIER_TRANSFORMS,
 641    }
 642
 643    TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {}
 644
 645    STRUCT_DELIMITER: t.ClassVar = ("<", ">")
 646
 647    PARAMETER_TOKEN = "@"
 648    NAMED_PLACEHOLDER_TOKEN = ":"
 649
 650    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set()
 651
 652    PROPERTIES_LOCATION: t.ClassVar = {
 653        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 654        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 655        exp.ApiProperty: exp.Properties.Location.POST_CREATE,
 656        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,
 657        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 658        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 659        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 660        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 661        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,
 662        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 663        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 664        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 665        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,
 666        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 667        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 668        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 669        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 670        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 671        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 672        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,
 673        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 674        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 675        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 676        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 677        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 678        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 679        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 680        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 681        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 682        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 683        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 684        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,
 685        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,
 686        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 687        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 688        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 689        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 690        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 691        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 692        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 693        exp.HybridProperty: exp.Properties.Location.POST_CREATE,
 694        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 695        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 696        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 697        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 698        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 699        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 700        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 701        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 702        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 703        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 704        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 705        exp.LogProperty: exp.Properties.Location.POST_NAME,
 706        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,
 707        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 708        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 709        exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA,
 710        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,
 711        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 712        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 713        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 714        exp.Order: exp.Properties.Location.POST_SCHEMA,
 715        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 716        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 717        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 718        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 719        exp.Property: exp.Properties.Location.POST_WITH,
 720        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 721        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 722        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 723        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 724        exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED,
 725        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 726        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 727        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 728        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 729        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 730        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 731        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,
 732        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 733        exp.Set: exp.Properties.Location.POST_SCHEMA,
 734        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 735        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 736        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 737        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 738        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 739        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 740        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 741        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 742        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,
 743        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 744        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 745        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 746        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 747        exp.Tags: exp.Properties.Location.POST_WITH,
 748        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 749        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 750        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 751        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 752        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 753        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 754        exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
 755        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 756        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 757        exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
 758        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 759        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 760        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 761        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 762        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 763        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 764        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 765    }
 766
 767    # Keywords that can't be used as unquoted identifier names
 768    RESERVED_KEYWORDS: t.ClassVar[set[str]] = set()
 769
 770    # Exprs whose comments are separated from them for better formatting
 771    WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 772        exp.Command,
 773        exp.Create,
 774        exp.Describe,
 775        exp.Delete,
 776        exp.Drop,
 777        exp.From,
 778        exp.Insert,
 779        exp.Join,
 780        exp.MultitableInserts,
 781        exp.Order,
 782        exp.Group,
 783        exp.Having,
 784        exp.Select,
 785        exp.SetOperation,
 786        exp.Update,
 787        exp.Where,
 788        exp.With,
 789    )
 790
 791    # Exprs that should not have their comments generated in maybe_comment
 792    EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 793        exp.Binary,
 794        exp.SetOperation,
 795    )
 796
 797    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL
 798    UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = (
 799        exp.Column,
 800        exp.Literal,
 801        exp.Neg,
 802        exp.Paren,
 803    )
 804
 805    PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = {
 806        exp.DType.NVARCHAR,
 807        exp.DType.VARCHAR,
 808        exp.DType.CHAR,
 809        exp.DType.NCHAR,
 810    }
 811
 812    # Exprs that need to have all CTEs under them bubbled up to them
 813    EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set()
 814
 815    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = ()
 816
 817    SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE
 818
 819    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 820
 821    __slots__ = (
 822        "pretty",
 823        "identify",
 824        "normalize",
 825        "pad",
 826        "_indent",
 827        "normalize_functions",
 828        "unsupported_level",
 829        "max_unsupported",
 830        "leading_comma",
 831        "max_text_width",
 832        "comments",
 833        "dialect",
 834        "unsupported_messages",
 835        "_escaped_quote_end",
 836        "_escaped_byte_quote_end",
 837        "_escaped_identifier_end",
 838        "_next_name",
 839        "_identifier_start",
 840        "_identifier_end",
 841        "_quote_json_path_key_using_brackets",
 842        "_dispatch",
 843    )
 844
 845    def __init__(
 846        self,
 847        pretty: bool | int | None = None,
 848        identify: str | bool = False,
 849        normalize: bool = False,
 850        pad: int = 2,
 851        indent: int = 2,
 852        normalize_functions: str | bool | None = None,
 853        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 854        max_unsupported: int = 3,
 855        leading_comma: bool = False,
 856        max_text_width: int = 80,
 857        comments: bool = True,
 858        dialect: DialectType = None,
 859    ):
 860        import sqlglot
 861        import sqlglot.dialects.dialect
 862
 863        self.pretty = pretty if pretty is not None else sqlglot.pretty
 864        self.identify = identify
 865        self.normalize = normalize
 866        self.pad = pad
 867        self._indent = indent
 868        self.unsupported_level = unsupported_level
 869        self.max_unsupported = max_unsupported
 870        self.leading_comma = leading_comma
 871        self.max_text_width = max_text_width
 872        self.comments = comments
 873        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
 874
 875        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 876        self.normalize_functions = (
 877            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 878        )
 879
 880        self.unsupported_messages: list[str] = []
 881        self._escaped_quote_end: str = (
 882            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 883        )
 884        self._escaped_byte_quote_end: str = (
 885            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 886            if self.dialect.BYTE_END
 887            else ""
 888        )
 889        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 890
 891        self._next_name = name_sequence("_t")
 892
 893        self._identifier_start = self.dialect.IDENTIFIER_START
 894        self._identifier_end = self.dialect.IDENTIFIER_END
 895
 896        self._quote_json_path_key_using_brackets = True
 897
 898        cls = type(self)
 899        dispatch = _DISPATCH_CACHE.get(cls)
 900        if dispatch is None:
 901            dispatch = _build_dispatch(cls)
 902            _DISPATCH_CACHE[cls] = dispatch
 903        self._dispatch = dispatch
 904
 905    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
 906        """
 907        Generates the SQL string corresponding to the given syntax tree.
 908
 909        Args:
 910            expression: The syntax tree.
 911            copy: Whether to copy the expression. The generator performs mutations so
 912                it is safer to copy.
 913
 914        Returns:
 915            The SQL string corresponding to `expression`.
 916        """
 917        if copy:
 918            expression = expression.copy()
 919
 920        expression = self.preprocess(expression)
 921
 922        self.unsupported_messages = []
 923        sql = self.sql(expression).strip()
 924
 925        if self.pretty:
 926            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 927
 928        if self.unsupported_level == ErrorLevel.IGNORE:
 929            return sql
 930
 931        if self.unsupported_level == ErrorLevel.WARN:
 932            for msg in self.unsupported_messages:
 933                logger.warning(msg)
 934        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 935            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 936
 937        return sql
 938
 939    def preprocess(self, expression: exp.Expr) -> exp.Expr:
 940        """Apply generic preprocessing transformations to a given expression."""
 941        expression = self._move_ctes_to_top_level(expression)
 942
 943        if self.ENSURE_BOOLS:
 944            import sqlglot.transforms
 945
 946            expression = sqlglot.transforms.ensure_bools(expression)
 947
 948        return expression
 949
 950    def _move_ctes_to_top_level(self, expression: E) -> E:
 951        if (
 952            not expression.parent
 953            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 954            and any(node.parent is not expression for node in expression.find_all(exp.With))
 955        ):
 956            import sqlglot.transforms
 957
 958            expression = sqlglot.transforms.move_ctes_to_top_level(expression)
 959        return expression
 960
 961    def unsupported(self, message: str) -> None:
 962        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 963            raise UnsupportedError(message)
 964        self.unsupported_messages.append(message)
 965
 966    def sep(self, sep: str = " ") -> str:
 967        return f"{sep.strip()}\n" if self.pretty else sep
 968
 969    def seg(self, sql: str, sep: str = " ") -> str:
 970        return f"{self.sep(sep)}{sql}"
 971
 972    def sanitize_comment(self, comment: str) -> str:
 973        comment = " " + comment if comment[0].strip() else comment
 974        comment = comment + " " if comment[-1].strip() else comment
 975
 976        # Escape block comment markers to prevent premature closure or unintended nesting.
 977        # This is necessary because single-line comments (--) are converted to block comments
 978        # (/* */) on output, and any */ in the original text would close the comment early.
 979        comment = comment.replace("*/", "* /").replace("/*", "/ *")
 980
 981        return comment
 982
 983    def maybe_comment(
 984        self,
 985        sql: str,
 986        expression: exp.Expr | None = None,
 987        comments: list[str] | None = None,
 988        separated: bool = False,
 989    ) -> str:
 990        comments = (
 991            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 992            if self.comments
 993            else None
 994        )
 995
 996        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 997            return sql
 998
 999        comments_sql = " ".join(
1000            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
1001        )
1002
1003        if not comments_sql:
1004            return sql
1005
1006        comments_sql = self._replace_line_breaks(comments_sql)
1007
1008        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1009            return (
1010                f"{self.sep()}{comments_sql}{sql}"
1011                if not sql or sql[0].isspace()
1012                else f"{comments_sql}{self.sep()}{sql}"
1013            )
1014
1015        return f"{sql} {comments_sql}"
1016
1017    def wrap(self, expression: exp.Expr | str) -> str:
1018        this_sql = (
1019            self.sql(expression)
1020            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1021            else self.sql(expression, "this")
1022        )
1023        if not this_sql:
1024            return "()"
1025
1026        this_sql = self.indent(this_sql, level=1, pad=0)
1027        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
1028
1029    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1030        original = self.identify
1031        self.identify = False
1032        result = func(*args, **kwargs)
1033        self.identify = original
1034        return result
1035
1036    def normalize_func(self, name: str) -> str:
1037        if self.normalize_functions == "upper" or self.normalize_functions is True:
1038            return name.upper()
1039        if self.normalize_functions == "lower":
1040            return name.lower()
1041        return name
1042
1043    def indent(
1044        self,
1045        sql: str,
1046        level: int = 0,
1047        pad: int | None = None,
1048        skip_first: bool = False,
1049        skip_last: bool = False,
1050    ) -> str:
1051        if not self.pretty or not sql:
1052            return sql
1053
1054        pad = self.pad if pad is None else pad
1055        lines = sql.split("\n")
1056
1057        return "\n".join(
1058            (
1059                line
1060                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1061                else f"{' ' * (level * self._indent + pad)}{line}"
1062            )
1063            for i, line in enumerate(lines)
1064        )
1065
1066    def sql(
1067        self,
1068        expression: str | exp.Expr | None,
1069        key: str | None = None,
1070        comment: bool = True,
1071    ) -> str:
1072        if not expression:
1073            return ""
1074
1075        if isinstance(expression, str):
1076            return expression
1077
1078        if key:
1079            value = expression.args.get(key)
1080            if value:
1081                return self.sql(value)
1082            return ""
1083
1084        handler = self._dispatch.get(expression.__class__)
1085
1086        if handler:
1087            sql = handler(self, expression)
1088        elif isinstance(expression, exp.Func):
1089            sql = self.function_fallback_sql(expression)
1090        elif isinstance(expression, exp.Property):
1091            sql = self.property_sql(expression)
1092        else:
1093            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1094
1095        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1096
1097    def uncache_sql(self, expression: exp.Uncache) -> str:
1098        table = self.sql(expression, "this")
1099        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1100        return f"UNCACHE TABLE{exists_sql} {table}"
1101
1102    def cache_sql(self, expression: exp.Cache) -> str:
1103        lazy = " LAZY" if expression.args.get("lazy") else ""
1104        table = self.sql(expression, "this")
1105        options = expression.args.get("options")
1106        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1107        sql = self.sql(expression, "expression")
1108        sql = f" AS{self.sep()}{sql}" if sql else ""
1109        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1110        return self.prepend_ctes(expression, sql)
1111
1112    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1113        default = "DEFAULT " if expression.args.get("default") else ""
1114        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1115
1116    def column_parts(self, expression: exp.Column) -> str:
1117        return ".".join(
1118            self.sql(part)
1119            for part in (
1120                expression.args.get("catalog"),
1121                expression.args.get("db"),
1122                expression.args.get("table"),
1123                expression.args.get("this"),
1124            )
1125            if part
1126        )
1127
1128    def column_sql(self, expression: exp.Column) -> str:
1129        join_mark = " (+)" if expression.args.get("join_mark") else ""
1130
1131        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1132            join_mark = ""
1133            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1134
1135        return f"{self.column_parts(expression)}{join_mark}"
1136
1137    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1138        return self.column_sql(expression)
1139
1140    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1141        this = self.sql(expression, "this")
1142        this = f" {this}" if this else ""
1143        position = self.sql(expression, "position")
1144        return f"{position}{this}"
1145
1146    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1147        column = self.sql(expression, "this")
1148        kind = self.sql(expression, "kind")
1149        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1150        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1151        kind = f"{sep}{kind}" if kind else ""
1152        constraints = f" {constraints}" if constraints else ""
1153        position = self.sql(expression, "position")
1154        position = f" {position}" if position else ""
1155
1156        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1157            kind = ""
1158
1159        return f"{exists}{column}{kind}{constraints}{position}"
1160
1161    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1162        this = self.sql(expression, "this")
1163        kind_sql = self.sql(expression, "kind").strip()
1164        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1165
1166    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1167        this = self.sql(expression, "this")
1168        if expression.args.get("not_null"):
1169            persisted = " PERSISTED NOT NULL"
1170        elif expression.args.get("persisted"):
1171            persisted = " PERSISTED"
1172        else:
1173            persisted = ""
1174
1175        return f"AS {this}{persisted}"
1176
1177    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1178        return self.token_sql(TokenType.AUTO_INCREMENT)
1179
1180    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1181        if isinstance(expression.this, list):
1182            this = self.wrap(self.expressions(expression, key="this", flat=True))
1183        else:
1184            this = self.sql(expression, "this")
1185
1186        return f"COMPRESS {this}"
1187
1188    def generatedasidentitycolumnconstraint_sql(
1189        self, expression: exp.GeneratedAsIdentityColumnConstraint
1190    ) -> str:
1191        this = ""
1192        if expression.this is not None:
1193            on_null = " ON NULL" if expression.args.get("on_null") else ""
1194            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1195
1196        start = expression.args.get("start")
1197        start = f"START WITH {start}" if start else ""
1198        increment = expression.args.get("increment")
1199        increment = f" INCREMENT BY {increment}" if increment else ""
1200        minvalue = expression.args.get("minvalue")
1201        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1202        maxvalue = expression.args.get("maxvalue")
1203        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1204        cycle = expression.args.get("cycle")
1205        cycle_sql = ""
1206
1207        if cycle is not None:
1208            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1209            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1210
1211        sequence_opts = ""
1212        if start or increment or cycle_sql:
1213            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1214            sequence_opts = f" ({sequence_opts.strip()})"
1215
1216        expr = self.sql(expression, "expression")
1217        expr = f"({expr})" if expr else "IDENTITY"
1218
1219        return f"GENERATED{this} AS {expr}{sequence_opts}"
1220
1221    def generatedasrowcolumnconstraint_sql(
1222        self, expression: exp.GeneratedAsRowColumnConstraint
1223    ) -> str:
1224        start = "START" if expression.args.get("start") else "END"
1225        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1226        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1227
1228    def periodforsystemtimeconstraint_sql(
1229        self, expression: exp.PeriodForSystemTimeConstraint
1230    ) -> str:
1231        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1232
1233    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1234        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1235
1236    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1237        desc = expression.args.get("desc")
1238        if desc is not None:
1239            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1240        options = self.expressions(expression, key="options", flat=True, sep=" ")
1241        options = f" {options}" if options else ""
1242        return f"PRIMARY KEY{options}"
1243
1244    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1245        this = self.sql(expression, "this")
1246        this = f" {this}" if this else ""
1247        index_type = expression.args.get("index_type")
1248        index_type = f" USING {index_type}" if index_type else ""
1249        on_conflict = self.sql(expression, "on_conflict")
1250        on_conflict = f" {on_conflict}" if on_conflict else ""
1251        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1252        options = self.expressions(expression, key="options", flat=True, sep=" ")
1253        options = f" {options}" if options else ""
1254        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1255
1256    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1257        input_ = expression.args.get("input_")
1258        output = expression.args.get("output")
1259        variadic = expression.args.get("variadic")
1260
1261        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1262        if variadic:
1263            return "VARIADIC"
1264
1265        if input_ and output:
1266            return f"IN{self.INOUT_SEPARATOR}OUT"
1267        if input_:
1268            return "IN"
1269        if output:
1270            return "OUT"
1271
1272        return ""
1273
1274    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1275        return self.sql(expression, "this")
1276
1277    def create_sql(self, expression: exp.Create) -> str:
1278        kind = self.sql(expression, "kind")
1279        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1280
1281        properties = expression.args.get("properties")
1282
1283        if (
1284            kind == "TRIGGER"
1285            and properties
1286            and properties.expressions
1287            and isinstance(properties.expressions[0], exp.TriggerProperties)
1288            and properties.expressions[0].args.get("constraint")
1289        ):
1290            kind = f"CONSTRAINT {kind}"
1291
1292        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1293
1294        this = self.createable_sql(expression, properties_locs)
1295
1296        properties_sql = ""
1297        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1298            exp.Properties.Location.POST_WITH
1299        ):
1300            props_ast = exp.Properties(
1301                expressions=[
1302                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1303                    *properties_locs[exp.Properties.Location.POST_WITH],
1304                ]
1305            )
1306            props_ast.parent = expression
1307            properties_sql = self.sql(props_ast)
1308
1309            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1310                properties_sql = self.sep() + properties_sql
1311            elif not self.pretty:
1312                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1313                properties_sql = f" {properties_sql}"
1314
1315        begin = " BEGIN" if expression.args.get("begin") else ""
1316
1317        expression_sql = self.sql(expression, "expression")
1318        if expression_sql:
1319            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1320
1321            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1322                postalias_props_sql = ""
1323                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1324                    postalias_props_sql = self.properties(
1325                        exp.Properties(
1326                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1327                        ),
1328                        wrapped=False,
1329                    )
1330                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1331                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1332
1333        postindex_props_sql = ""
1334        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1335            postindex_props_sql = self.properties(
1336                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1337                wrapped=False,
1338                prefix=" ",
1339            )
1340
1341        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1342        indexes = f" {indexes}" if indexes else ""
1343        index_sql = indexes + postindex_props_sql
1344
1345        replace = " OR REPLACE" if expression.args.get("replace") else ""
1346        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1347        unique = " UNIQUE" if expression.args.get("unique") else ""
1348
1349        clustered = expression.args.get("clustered")
1350        if clustered is None:
1351            clustered_sql = ""
1352        elif clustered:
1353            clustered_sql = " CLUSTERED COLUMNSTORE"
1354        else:
1355            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1356
1357        postcreate_props_sql = ""
1358        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1359            postcreate_props_sql = self.properties(
1360                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1361                sep=" ",
1362                prefix=" ",
1363                wrapped=False,
1364            )
1365
1366        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1367
1368        postexpression_props_sql = ""
1369        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1370            postexpression_props_sql = self.properties(
1371                exp.Properties(
1372                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1373                ),
1374                sep=" ",
1375                prefix=" ",
1376                wrapped=False,
1377            )
1378
1379        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1380        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1381        no_schema_binding = (
1382            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1383        )
1384
1385        clone = self.sql(expression, "clone")
1386        clone = f" {clone}" if clone else ""
1387
1388        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1389            properties_expression = f"{expression_sql}{properties_sql}"
1390        else:
1391            properties_expression = f"{properties_sql}{expression_sql}"
1392
1393        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1394        return self.prepend_ctes(expression, expression_sql)
1395
1396    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1397        start = self.sql(expression, "start")
1398        start = f"START WITH {start}" if start else ""
1399        increment = self.sql(expression, "increment")
1400        increment = f" INCREMENT BY {increment}" if increment else ""
1401        minvalue = self.sql(expression, "minvalue")
1402        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1403        maxvalue = self.sql(expression, "maxvalue")
1404        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1405        owned = self.sql(expression, "owned")
1406        owned = f" OWNED BY {owned}" if owned else ""
1407
1408        cache = expression.args.get("cache")
1409        if cache is None:
1410            cache_str = ""
1411        elif cache is True:
1412            cache_str = " CACHE"
1413        else:
1414            cache_str = f" CACHE {cache}"
1415
1416        options = self.expressions(expression, key="options", flat=True, sep=" ")
1417        options = f" {options}" if options else ""
1418
1419        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1420
1421    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1422        timing = expression.args.get("timing", "")
1423        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1424        timing_events = f"{timing} {events}".strip() if timing or events else ""
1425
1426        parts = [timing_events, "ON", self.sql(expression, "table")]
1427
1428        if referenced_table := expression.args.get("referenced_table"):
1429            parts.extend(["FROM", self.sql(referenced_table)])
1430
1431        if deferrable := expression.args.get("deferrable"):
1432            parts.append(deferrable)
1433
1434        if initially := expression.args.get("initially"):
1435            parts.append(f"INITIALLY {initially}")
1436
1437        if referencing := expression.args.get("referencing"):
1438            parts.append(self.sql(referencing))
1439
1440        if for_each := expression.args.get("for_each"):
1441            parts.append(f"FOR EACH {for_each}")
1442
1443        if when := expression.args.get("when"):
1444            parts.append(f"WHEN ({self.sql(when)})")
1445
1446        parts.append(self.sql(expression, "execute"))
1447
1448        return self.sep().join(parts)
1449
1450    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1451        parts = []
1452
1453        if old_alias := expression.args.get("old"):
1454            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1455
1456        if new_alias := expression.args.get("new"):
1457            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1458
1459        return f"REFERENCING {' '.join(parts)}"
1460
1461    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1462        columns = expression.args.get("columns")
1463        if columns:
1464            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1465
1466        return self.sql(expression, "this")
1467
1468    def clone_sql(self, expression: exp.Clone) -> str:
1469        this = self.sql(expression, "this")
1470        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1471        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1472        return f"{shallow}{keyword} {this}"
1473
1474    def describe_sql(self, expression: exp.Describe) -> str:
1475        style = expression.args.get("style")
1476        style = f" {style}" if style else ""
1477        partition = self.sql(expression, "partition")
1478        partition = f" {partition}" if partition else ""
1479        format = self.sql(expression, "format")
1480        format = f" {format}" if format else ""
1481        as_json = " AS JSON" if expression.args.get("as_json") else ""
1482
1483        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1484
1485    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1486        tag = self.sql(expression, "tag")
1487        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1488
1489    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1490        with_ = self.sql(expression, "with_")
1491        if with_:
1492            sql = f"{with_}{self.sep()}{sql}"
1493        return sql
1494
1495    def with_sql(self, expression: exp.With) -> str:
1496        sql = self.expressions(expression, flat=True)
1497        recursive = (
1498            "RECURSIVE "
1499            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1500            else ""
1501        )
1502        search = self.sql(expression, "search")
1503        search = f" {search}" if search else ""
1504
1505        return f"WITH {recursive}{sql}{search}"
1506
1507    def cte_sql(self, expression: exp.CTE) -> str:
1508        alias = expression.args.get("alias")
1509        if alias:
1510            alias.add_comments(expression.pop_comments())
1511
1512        alias_sql = self.sql(expression, "alias")
1513
1514        materialized = expression.args.get("materialized")
1515        if materialized is False:
1516            materialized = "NOT MATERIALIZED "
1517        elif materialized:
1518            materialized = "MATERIALIZED "
1519
1520        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1521        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1522
1523        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1524
1525    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1526        alias = self.sql(expression, "this")
1527        columns = self.expressions(expression, key="columns", flat=True)
1528        columns = f"({columns})" if columns else ""
1529
1530        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1531            columns = ""
1532            self.unsupported("Named columns are not supported in table alias.")
1533
1534        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1535            alias = self._next_name()
1536
1537        return f"{alias}{columns}"
1538
1539    def bitstring_sql(self, expression: exp.BitString) -> str:
1540        this = self.sql(expression, "this")
1541        if self.dialect.BIT_START:
1542            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1543        return f"{int(this, 2)}"
1544
1545    def hexstring_sql(
1546        self, expression: exp.HexString, binary_function_repr: str | None = None
1547    ) -> str:
1548        this = self.sql(expression, "this")
1549        is_integer_type = expression.args.get("is_integer")
1550
1551        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1552            not self.dialect.HEX_START and not binary_function_repr
1553        ):
1554            # Integer representation will be returned if:
1555            # - The read dialect treats the hex value as integer literal but not the write
1556            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1557            return f"{int(this, 16)}"
1558
1559        if not is_integer_type:
1560            # Read dialect treats the hex value as BINARY/BLOB
1561            if binary_function_repr:
1562                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1563                return self.func(binary_function_repr, exp.Literal.string(this))
1564            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1565                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1566                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1567
1568        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1569
1570    def bytestring_sql(self, expression: exp.ByteString) -> str:
1571        this = self.sql(expression, "this")
1572        if self.dialect.BYTE_START:
1573            escaped_byte_string = self.escape_str(
1574                this,
1575                escape_backslash=False,
1576                delimiter=self.dialect.BYTE_END,
1577                escaped_delimiter=self._escaped_byte_quote_end,
1578                is_byte_string=True,
1579            )
1580            is_bytes = expression.args.get("is_bytes", False)
1581            delimited_byte_string = (
1582                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1583            )
1584            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1585                return self.sql(
1586                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1587                )
1588            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1589                return self.sql(
1590                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1591                )
1592
1593            return delimited_byte_string
1594        return this
1595
1596    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1597        this = self.sql(expression, "this")
1598        escape = expression.args.get("escape")
1599
1600        if self.dialect.UNICODE_START:
1601            escape_substitute = r"\\\1"
1602            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1603        else:
1604            escape_substitute = r"\\u\1"
1605            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1606
1607        if escape:
1608            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1609            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1610        else:
1611            escape_pattern = ESCAPED_UNICODE_RE
1612            escape_sql = ""
1613
1614        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1615            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1616
1617        return f"{left_quote}{this}{right_quote}{escape_sql}"
1618
1619    def rawstring_sql(self, expression: exp.RawString) -> str:
1620        string = expression.this
1621        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1622            string = string.replace("\\", "\\\\")
1623
1624        string = self.escape_str(string, escape_backslash=False)
1625        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1626
1627    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1628        this = self.sql(expression, "this")
1629        specifier = self.sql(expression, "expression")
1630        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1631        return f"{this}{specifier}"
1632
1633    def datatype_sql(self, expression: exp.DataType) -> str:
1634        nested = ""
1635        values = ""
1636
1637        expr_nested = expression.args.get("nested")
1638        interior = (
1639            self.expressions(
1640                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1641            )
1642            if expr_nested and self.pretty
1643            else self.expressions(expression, flat=True)
1644        )
1645
1646        type_value = expression.this
1647        if type_value in self.UNSUPPORTED_TYPES:
1648            self.unsupported(
1649                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1650            )
1651
1652        type_sql: t.Any = ""
1653        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1654            type_sql = self.sql(expression, "kind")
1655        elif type_value == exp.DType.CHARACTER_SET:
1656            return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}"
1657        else:
1658            type_sql = (
1659                self.TYPE_MAPPING.get(type_value, type_value.value)
1660                if isinstance(type_value, exp.DType)
1661                else type_value
1662            )
1663
1664        if interior:
1665            if expr_nested:
1666                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1667                if expression.args.get("values") is not None:
1668                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1669                    values = self.expressions(expression, key="values", flat=True)
1670                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1671            elif type_value == exp.DType.INTERVAL:
1672                nested = f" {interior}"
1673            else:
1674                nested = f"({interior})"
1675
1676        type_sql = f"{type_sql}{nested}{values}"
1677        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1678            exp.DType.TIMETZ,
1679            exp.DType.TIMESTAMPTZ,
1680        ):
1681            type_sql = f"{type_sql} WITH TIME ZONE"
1682
1683        return type_sql
1684
1685    def directory_sql(self, expression: exp.Directory) -> str:
1686        local = "LOCAL " if expression.args.get("local") else ""
1687        row_format = self.sql(expression, "row_format")
1688        row_format = f" {row_format}" if row_format else ""
1689        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1690
1691    def delete_sql(self, expression: exp.Delete) -> str:
1692        hint = self.sql(expression, "hint")
1693        this = self.sql(expression, "this")
1694        this = f" FROM {this}" if this else ""
1695        using = self.expressions(expression, key="using")
1696        using = f" USING {using}" if using else ""
1697        cluster = self.sql(expression, "cluster")
1698        cluster = f" {cluster}" if cluster else ""
1699        where = self.sql(expression, "where")
1700        returning = self.sql(expression, "returning")
1701        order = self.sql(expression, "order")
1702        limit = self.sql(expression, "limit")
1703        tables = self.expressions(expression, key="tables")
1704        tables = f" {tables}" if tables else ""
1705        if self.RETURNING_END:
1706            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1707        else:
1708            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1709        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1710
1711    def drop_sql(self, expression: exp.Drop) -> str:
1712        this = self.sql(expression, "this")
1713        expressions = self.expressions(expression, flat=True)
1714        expressions = f" ({expressions})" if expressions else ""
1715        kind = expression.args["kind"]
1716        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1717        iceberg = (
1718            " ICEBERG"
1719            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1720            else ""
1721        )
1722        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1723        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1724        on_cluster = self.sql(expression, "cluster")
1725        on_cluster = f" {on_cluster}" if on_cluster else ""
1726        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1727        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1728        cascade = " CASCADE" if expression.args.get("cascade") else ""
1729        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1730        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1731        purge = " PURGE" if expression.args.get("purge") else ""
1732        sync = " SYNC" if expression.args.get("sync") else ""
1733        return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}"
1734
1735    def set_operation(self, expression: exp.SetOperation) -> str:
1736        op_type = type(expression)
1737        op_name = op_type.key.upper()
1738
1739        distinct = expression.args.get("distinct")
1740        if (
1741            distinct is False
1742            and op_type in (exp.Except, exp.Intersect)
1743            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1744        ):
1745            self.unsupported(f"{op_name} ALL is not supported")
1746
1747        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1748
1749        if distinct is None:
1750            distinct = default_distinct
1751            if distinct is None:
1752                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1753
1754        if distinct is default_distinct:
1755            distinct_or_all = ""
1756        else:
1757            distinct_or_all = " DISTINCT" if distinct else " ALL"
1758
1759        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1760        side_kind = f"{side_kind} " if side_kind else ""
1761
1762        by_name = " BY NAME" if expression.args.get("by_name") else ""
1763        on = self.expressions(expression, key="on", flat=True)
1764        on = f" ON ({on})" if on else ""
1765
1766        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1767
1768    def set_operations(self, expression: exp.SetOperation) -> str:
1769        if not self.SET_OP_MODIFIERS:
1770            limit = expression.args.get("limit")
1771            order = expression.args.get("order")
1772
1773            if limit or order:
1774                select = self._move_ctes_to_top_level(
1775                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1776                )
1777
1778                if limit:
1779                    select = select.limit(limit.pop(), copy=False)
1780                if order:
1781                    select = select.order_by(order.pop(), copy=False)
1782                return self.sql(select)
1783
1784        sqls: list[str] = []
1785        stack: list[str | exp.Expr] = [expression]
1786
1787        while stack:
1788            node = stack.pop()
1789
1790            if isinstance(node, exp.SetOperation):
1791                stack.append(node.expression)
1792                stack.append(
1793                    self.maybe_comment(
1794                        self.set_operation(node), comments=node.comments, separated=True
1795                    )
1796                )
1797                stack.append(node.this)
1798            else:
1799                sqls.append(self.sql(node))
1800
1801        this = self.sep().join(sqls)
1802        this = self.query_modifiers(expression, this)
1803        return self.prepend_ctes(expression, this)
1804
1805    def fetch_sql(self, expression: exp.Fetch) -> str:
1806        direction = expression.args.get("direction")
1807        direction = f" {direction}" if direction else ""
1808        count = self.sql(expression, "count")
1809        count = f" {count}" if count else ""
1810        limit_options = self.sql(expression, "limit_options")
1811        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1812        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1813
1814    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1815        percent = " PERCENT" if expression.args.get("percent") else ""
1816        rows = " ROWS" if expression.args.get("rows") else ""
1817        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1818        if not with_ties and rows:
1819            with_ties = " ONLY"
1820        return f"{percent}{rows}{with_ties}"
1821
1822    def filter_sql(self, expression: exp.Filter) -> str:
1823        if self.AGGREGATE_FILTER_SUPPORTED:
1824            this = self.sql(expression, "this")
1825            where = self.sql(expression, "expression").strip()
1826            return f"{this} FILTER({where})"
1827
1828        agg = expression.this
1829        agg_arg = agg.this
1830        cond = expression.expression.this
1831        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1832        return self.sql(agg)
1833
1834    def hint_sql(self, expression: exp.Hint) -> str:
1835        if not self.QUERY_HINTS:
1836            self.unsupported("Hints are not supported")
1837            return ""
1838
1839        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1840
1841    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1842        using = self.sql(expression, "using")
1843        using = f" USING {using}" if using else ""
1844        columns = self.expressions(expression, key="columns", flat=True)
1845        columns = f"({columns})" if columns else ""
1846        partition_by = self.expressions(expression, key="partition_by", flat=True)
1847        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1848        where = self.sql(expression, "where")
1849        include = self.expressions(expression, key="include", flat=True)
1850        if include:
1851            include = f" INCLUDE ({include})"
1852        with_storage = self.expressions(expression, key="with_storage", flat=True)
1853        with_storage = f" WITH ({with_storage})" if with_storage else ""
1854        tablespace = self.sql(expression, "tablespace")
1855        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1856        on = self.sql(expression, "on")
1857        on = f" ON {on}" if on else ""
1858
1859        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1860
1861    def index_sql(self, expression: exp.Index) -> str:
1862        unique = "UNIQUE " if expression.args.get("unique") else ""
1863        primary = "PRIMARY " if expression.args.get("primary") else ""
1864        amp = "AMP " if expression.args.get("amp") else ""
1865        name = self.sql(expression, "this")
1866        name = f"{name} " if name else ""
1867        table = self.sql(expression, "table")
1868        table = f"{self.INDEX_ON} {table}" if table else ""
1869
1870        index = "INDEX " if not table else ""
1871
1872        params = self.sql(expression, "params")
1873        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1874
1875    def identifier_sql(self, expression: exp.Identifier) -> str:
1876        text = expression.name
1877        lower = text.lower()
1878        quoted = expression.quoted
1879        text = lower if self.normalize and not quoted else text
1880        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1881        if (
1882            quoted
1883            or self.dialect.can_quote(expression, self.identify)
1884            or lower in self.RESERVED_KEYWORDS
1885            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1886        ):
1887            text = (
1888                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
1889            )
1890        return text
1891
1892    def hex_sql(self, expression: exp.Hex) -> str:
1893        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1894        if self.dialect.HEX_LOWERCASE:
1895            text = self.func("LOWER", text)
1896
1897        return text
1898
1899    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1900        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1901        if not self.dialect.HEX_LOWERCASE:
1902            text = self.func("LOWER", text)
1903        return text
1904
1905    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1906        input_format = self.sql(expression, "input_format")
1907        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1908        output_format = self.sql(expression, "output_format")
1909        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1910        return self.sep().join((input_format, output_format))
1911
1912    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1913        string = self.sql(exp.Literal.string(expression.name))
1914        return f"{prefix}{string}"
1915
1916    def partition_sql(self, expression: exp.Partition) -> str:
1917        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1918        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1919
1920    def properties_sql(self, expression: exp.Properties) -> str:
1921        root_properties = []
1922        with_properties = []
1923
1924        for p in expression.expressions:
1925            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1926            if p_loc == exp.Properties.Location.POST_WITH:
1927                with_properties.append(p)
1928            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1929                root_properties.append(p)
1930
1931        root_props_ast = exp.Properties(expressions=root_properties)
1932        root_props_ast.parent = expression.parent
1933
1934        with_props_ast = exp.Properties(expressions=with_properties)
1935        with_props_ast.parent = expression.parent
1936
1937        root_props = self.root_properties(root_props_ast)
1938        with_props = self.with_properties(with_props_ast)
1939
1940        if root_props and with_props and not self.pretty:
1941            with_props = " " + with_props
1942
1943        return root_props + with_props
1944
1945    def root_properties(self, properties: exp.Properties) -> str:
1946        if properties.expressions:
1947            return self.expressions(properties, indent=False, sep=" ")
1948        return ""
1949
1950    def properties(
1951        self,
1952        properties: exp.Properties,
1953        prefix: str = "",
1954        sep: str = ", ",
1955        suffix: str = "",
1956        wrapped: bool = True,
1957    ) -> str:
1958        if properties.expressions:
1959            expressions = self.expressions(properties, sep=sep, indent=False)
1960            if expressions:
1961                expressions = self.wrap(expressions) if wrapped else expressions
1962                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1963        return ""
1964
1965    def with_properties(self, properties: exp.Properties) -> str:
1966        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1967
1968    def locate_properties(self, properties: exp.Properties) -> defaultdict:
1969        properties_locs = defaultdict(list)
1970        for p in properties.expressions:
1971            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1972            if p_loc != exp.Properties.Location.UNSUPPORTED:
1973                properties_locs[p_loc].append(p)
1974            else:
1975                self.unsupported(f"Unsupported property {p.key}")
1976
1977        return properties_locs
1978
1979    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1980        if isinstance(expression.this, exp.Dot):
1981            return self.sql(expression, "this")
1982        return f"'{expression.name}'" if string_key else expression.name
1983
1984    def property_sql(self, expression: exp.Property) -> str:
1985        property_cls = expression.__class__
1986        if property_cls == exp.Property:
1987            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1988
1989        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1990        if not property_name:
1991            self.unsupported(f"Unsupported property {expression.key}")
1992
1993        return f"{property_name}={self.sql(expression, 'this')}"
1994
1995    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
1996        return f"UUID {self.sql(expression, 'this')}"
1997
1998    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1999        if self.SUPPORTS_CREATE_TABLE_LIKE:
2000            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2001            options = f" {options}" if options else ""
2002
2003            like = f"LIKE {self.sql(expression, 'this')}{options}"
2004            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2005                like = f"({like})"
2006
2007            return like
2008
2009        if expression.expressions:
2010            self.unsupported("Transpilation of LIKE property options is unsupported")
2011
2012        select = exp.select("*").from_(expression.this).limit(0)
2013        return f"AS {self.sql(select)}"
2014
2015    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2016        no = "NO " if expression.args.get("no") else ""
2017        protection = " PROTECTION" if expression.args.get("protection") else ""
2018        return f"{no}FALLBACK{protection}"
2019
2020    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2021        no = "NO " if expression.args.get("no") else ""
2022        local = expression.args.get("local")
2023        local = f"{local} " if local else ""
2024        dual = "DUAL " if expression.args.get("dual") else ""
2025        before = "BEFORE " if expression.args.get("before") else ""
2026        after = "AFTER " if expression.args.get("after") else ""
2027        return f"{no}{local}{dual}{before}{after}JOURNAL"
2028
2029    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2030        freespace = self.sql(expression, "this")
2031        percent = " PERCENT" if expression.args.get("percent") else ""
2032        return f"FREESPACE={freespace}{percent}"
2033
2034    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2035        if expression.args.get("default"):
2036            property = "DEFAULT"
2037        elif expression.args.get("on"):
2038            property = "ON"
2039        else:
2040            property = "OFF"
2041        return f"CHECKSUM={property}"
2042
2043    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2044        if expression.args.get("no"):
2045            return "NO MERGEBLOCKRATIO"
2046        if expression.args.get("default"):
2047            return "DEFAULT MERGEBLOCKRATIO"
2048
2049        percent = " PERCENT" if expression.args.get("percent") else ""
2050        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
2051
2052    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2053        expressions = self.expressions(expression, flat=True)
2054        expressions = f"({expressions})" if expressions else ""
2055        return f"USING {self.sql(expression, 'this')}{expressions}"
2056
2057    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2058        default = expression.args.get("default")
2059        minimum = expression.args.get("minimum")
2060        maximum = expression.args.get("maximum")
2061        if default or minimum or maximum:
2062            if default:
2063                prop = "DEFAULT"
2064            elif minimum:
2065                prop = "MINIMUM"
2066            else:
2067                prop = "MAXIMUM"
2068            return f"{prop} DATABLOCKSIZE"
2069        units = expression.args.get("units")
2070        units = f" {units}" if units else ""
2071        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
2072
2073    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2074        autotemp = expression.args.get("autotemp")
2075        always = expression.args.get("always")
2076        default = expression.args.get("default")
2077        manual = expression.args.get("manual")
2078        never = expression.args.get("never")
2079
2080        if autotemp is not None:
2081            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2082        elif always:
2083            prop = "ALWAYS"
2084        elif default:
2085            prop = "DEFAULT"
2086        elif manual:
2087            prop = "MANUAL"
2088        elif never:
2089            prop = "NEVER"
2090        return f"BLOCKCOMPRESSION={prop}"
2091
2092    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2093        no = expression.args.get("no")
2094        no = " NO" if no else ""
2095        concurrent = expression.args.get("concurrent")
2096        concurrent = " CONCURRENT" if concurrent else ""
2097        target = self.sql(expression, "target")
2098        target = f" {target}" if target else ""
2099        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2100
2101    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2102        if isinstance(expression.this, list):
2103            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2104        if expression.this:
2105            modulus = self.sql(expression, "this")
2106            remainder = self.sql(expression, "expression")
2107            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2108
2109        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2110        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2111        return f"FROM ({from_expressions}) TO ({to_expressions})"
2112
2113    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2114        this = self.sql(expression, "this")
2115
2116        for_values_or_default = expression.expression
2117        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2118            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2119        else:
2120            for_values_or_default = " DEFAULT"
2121
2122        return f"PARTITION OF {this}{for_values_or_default}"
2123
2124    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2125        kind = expression.args.get("kind")
2126        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2127        for_or_in = expression.args.get("for_or_in")
2128        for_or_in = f" {for_or_in}" if for_or_in else ""
2129        lock_type = expression.args.get("lock_type")
2130        override = " OVERRIDE" if expression.args.get("override") else ""
2131        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2132
2133    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2134        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2135        statistics = expression.args.get("statistics")
2136        statistics_sql = ""
2137        if statistics is not None:
2138            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2139        return f"{data_sql}{statistics_sql}"
2140
2141    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2142        this = self.sql(expression, "this")
2143        this = f"HISTORY_TABLE={this}" if this else ""
2144        data_consistency: str | None = self.sql(expression, "data_consistency")
2145        data_consistency = (
2146            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2147        )
2148        retention_period: str | None = self.sql(expression, "retention_period")
2149        retention_period = (
2150            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2151        )
2152
2153        if this:
2154            on_sql = self.func("ON", this, data_consistency, retention_period)
2155        else:
2156            on_sql = "ON" if expression.args.get("on") else "OFF"
2157
2158        sql = f"SYSTEM_VERSIONING={on_sql}"
2159
2160        return f"WITH({sql})" if expression.args.get("with_") else sql
2161
2162    def insert_sql(self, expression: exp.Insert) -> str:
2163        hint = self.sql(expression, "hint")
2164        overwrite = expression.args.get("overwrite")
2165
2166        if isinstance(expression.this, exp.Directory):
2167            this = " OVERWRITE" if overwrite else " INTO"
2168        else:
2169            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2170
2171        stored = self.sql(expression, "stored")
2172        stored = f" {stored}" if stored else ""
2173        alternative = expression.args.get("alternative")
2174        alternative = f" OR {alternative}" if alternative else ""
2175        ignore = " IGNORE" if expression.args.get("ignore") else ""
2176        is_function = expression.args.get("is_function")
2177        if is_function:
2178            this = f"{this} FUNCTION"
2179        this = f"{this} {self.sql(expression, 'this')}"
2180
2181        exists = " IF EXISTS" if expression.args.get("exists") else ""
2182        where = self.sql(expression, "where")
2183        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2184        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2185        on_conflict = self.sql(expression, "conflict")
2186        on_conflict = f" {on_conflict}" if on_conflict else ""
2187        by_name = " BY NAME" if expression.args.get("by_name") else ""
2188        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2189        returning = self.sql(expression, "returning")
2190
2191        if self.RETURNING_END:
2192            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2193        else:
2194            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2195
2196        partition_by = self.sql(expression, "partition")
2197        partition_by = f" {partition_by}" if partition_by else ""
2198        settings = self.sql(expression, "settings")
2199        settings = f" {settings}" if settings else ""
2200
2201        source = self.sql(expression, "source")
2202        source = f"TABLE {source}" if source else ""
2203
2204        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2205        return self.prepend_ctes(expression, sql)
2206
2207    def introducer_sql(self, expression: exp.Introducer) -> str:
2208        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2209
2210    def kill_sql(self, expression: exp.Kill) -> str:
2211        kind = self.sql(expression, "kind")
2212        kind = f" {kind}" if kind else ""
2213        this = self.sql(expression, "this")
2214        this = f" {this}" if this else ""
2215        return f"KILL{kind}{this}"
2216
2217    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2218        return expression.name
2219
2220    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2221        return expression.name
2222
2223    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2224        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2225
2226        constraint = self.sql(expression, "constraint")
2227        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2228
2229        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2230        if conflict_keys:
2231            conflict_keys = f"({conflict_keys})"
2232
2233        index_predicate = self.sql(expression, "index_predicate")
2234        conflict_keys = f"{conflict_keys}{index_predicate} "
2235
2236        action = self.sql(expression, "action")
2237
2238        expressions = self.expressions(expression, flat=True)
2239        if expressions:
2240            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2241            expressions = f" {set_keyword}{expressions}"
2242
2243        where = self.sql(expression, "where")
2244        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2245
2246    def returning_sql(self, expression: exp.Returning) -> str:
2247        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2248
2249    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2250        fields = self.sql(expression, "fields")
2251        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2252        escaped = self.sql(expression, "escaped")
2253        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2254        items = self.sql(expression, "collection_items")
2255        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2256        keys = self.sql(expression, "map_keys")
2257        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2258        lines = self.sql(expression, "lines")
2259        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2260        null = self.sql(expression, "null")
2261        null = f" NULL DEFINED AS {null}" if null else ""
2262        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2263
2264    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2265        return f"WITH ({self.expressions(expression, flat=True)})"
2266
2267    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2268        this = f"{self.sql(expression, 'this')} INDEX"
2269        target = self.sql(expression, "target")
2270        target = f" FOR {target}" if target else ""
2271        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2272
2273    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2274        this = self.sql(expression, "this")
2275        kind = self.sql(expression, "kind")
2276        expr = self.sql(expression, "expression")
2277        return f"{this} ({kind} => {expr})"
2278
2279    def table_parts(self, expression: exp.Table) -> str:
2280        return ".".join(
2281            self.sql(part)
2282            for part in (
2283                expression.args.get("catalog"),
2284                expression.args.get("db"),
2285                expression.args.get("this"),
2286            )
2287            if part is not None
2288        )
2289
2290    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2291        table = self.table_parts(expression)
2292        only = "ONLY " if expression.args.get("only") else ""
2293        partition = self.sql(expression, "partition")
2294        partition = f" {partition}" if partition else ""
2295        version = self.sql(expression, "version")
2296        version = f" {version}" if version else ""
2297        alias = self.sql(expression, "alias")
2298        alias = f"{sep}{alias}" if alias else ""
2299
2300        sample = self.sql(expression, "sample")
2301        post_alias = ""
2302        pre_alias = ""
2303
2304        if self.dialect.ALIAS_POST_TABLESAMPLE:
2305            pre_alias = sample
2306        else:
2307            post_alias = sample
2308
2309        if self.dialect.ALIAS_POST_VERSION:
2310            pre_alias = f"{pre_alias}{version}"
2311        else:
2312            post_alias = f"{post_alias}{version}"
2313
2314        hints = self.expressions(expression, key="hints", sep=" ")
2315        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2316        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2317        joins = self.indent(
2318            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2319        )
2320        laterals = self.expressions(expression, key="laterals", sep="")
2321
2322        file_format = self.sql(expression, "format")
2323        if file_format:
2324            pattern = self.sql(expression, "pattern")
2325            pattern = f", PATTERN => {pattern}" if pattern else ""
2326            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2327
2328        ordinality = expression.args.get("ordinality") or ""
2329        if ordinality:
2330            ordinality = f" WITH ORDINALITY{alias}"
2331            alias = ""
2332
2333        when = self.sql(expression, "when")
2334        if when:
2335            table = f"{table} {when}"
2336
2337        changes = self.sql(expression, "changes")
2338        changes = f" {changes}" if changes else ""
2339
2340        rows_from = self.expressions(expression, key="rows_from")
2341        if rows_from:
2342            table = f"ROWS FROM {self.wrap(rows_from)}"
2343
2344        indexed = expression.args.get("indexed")
2345        if indexed is not None:
2346            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2347        else:
2348            indexed = ""
2349
2350        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2351
2352    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2353        table = self.func("TABLE", expression.this)
2354        alias = self.sql(expression, "alias")
2355        alias = f" AS {alias}" if alias else ""
2356        sample = self.sql(expression, "sample")
2357        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2358        joins = self.indent(
2359            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2360        )
2361        return f"{table}{alias}{pivots}{sample}{joins}"
2362
2363    def tablesample_sql(
2364        self,
2365        expression: exp.TableSample,
2366        tablesample_keyword: str | None = None,
2367    ) -> str:
2368        method = self.sql(expression, "method")
2369        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2370        numerator = self.sql(expression, "bucket_numerator")
2371        denominator = self.sql(expression, "bucket_denominator")
2372        field = self.sql(expression, "bucket_field")
2373        field = f" ON {field}" if field else ""
2374        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2375        seed = self.sql(expression, "seed")
2376        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2377
2378        size = self.sql(expression, "size")
2379        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2380            size = f"{size} ROWS"
2381
2382        percent = self.sql(expression, "percent")
2383        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2384            percent = f"{percent} PERCENT"
2385
2386        expr = f"{bucket}{percent}{size}"
2387        if self.TABLESAMPLE_REQUIRES_PARENS:
2388            expr = f"({expr})"
2389
2390        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2391
2392    def pivot_sql(self, expression: exp.Pivot) -> str:
2393        expressions = self.expressions(expression, flat=True)
2394        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2395
2396        group = self.sql(expression, "group")
2397
2398        if expression.this:
2399            this = self.sql(expression, "this")
2400            if not expressions:
2401                sql = f"UNPIVOT {this}"
2402            else:
2403                on = f"{self.seg('ON')} {expressions}"
2404                into = self.sql(expression, "into")
2405                into = f"{self.seg('INTO')} {into}" if into else ""
2406                using = self.expressions(expression, key="using", flat=True)
2407                using = f"{self.seg('USING')} {using}" if using else ""
2408                sql = f"{direction} {this}{on}{into}{using}{group}"
2409            return self.prepend_ctes(expression, sql)
2410
2411        alias = self.sql(expression, "alias")
2412        alias = f" AS {alias}" if alias else ""
2413
2414        fields = self.expressions(
2415            expression,
2416            "fields",
2417            sep=" ",
2418            dynamic=True,
2419            new_line=True,
2420            skip_first=True,
2421            skip_last=True,
2422        )
2423
2424        include_nulls = expression.args.get("include_nulls")
2425        if include_nulls is not None:
2426            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2427        else:
2428            nulls = ""
2429
2430        default_on_null = self.sql(expression, "default_on_null")
2431        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2432        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2433        return self.prepend_ctes(expression, sql)
2434
2435    def version_sql(self, expression: exp.Version) -> str:
2436        this = f"FOR {expression.name}"
2437        kind = expression.text("kind")
2438        expr = self.sql(expression, "expression")
2439        return f"{this} {kind} {expr}"
2440
2441    def tuple_sql(self, expression: exp.Tuple) -> str:
2442        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2443
2444    def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]:
2445        """
2446        Returns (join_sql, from_sql) for UPDATE statements.
2447        - join_sql: placed after UPDATE table, before SET
2448        - from_sql: placed after SET clause (standard position)
2449        Dialects like MySQL need to convert FROM to JOIN syntax.
2450        """
2451        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2452            return ("", self.sql(expression, "from_"))
2453
2454        # Qualify unqualified columns in SET clause with the target table
2455        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2456        target_table = expression.this
2457        if isinstance(target_table, exp.Table):
2458            target_name = exp.to_identifier(target_table.alias_or_name)
2459            for eq in expression.expressions:
2460                col = eq.this
2461                if isinstance(col, exp.Column) and not col.table:
2462                    col.set("table", target_name)
2463
2464        table = from_expr.this
2465        if nested_joins := table.args.get("joins", []):
2466            table.set("joins", None)
2467
2468        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2469        for nested in nested_joins:
2470            if not nested.args.get("on") and not nested.args.get("using"):
2471                nested.set("on", exp.true())
2472            join_sql += self.sql(nested)
2473
2474        return (join_sql, "")
2475
2476    def update_sql(self, expression: exp.Update) -> str:
2477        hint = self.sql(expression, "hint")
2478        this = self.sql(expression, "this")
2479        join_sql, from_sql = self._update_from_joins_sql(expression)
2480        set_sql = self.expressions(expression, flat=True)
2481        where_sql = self.sql(expression, "where")
2482        returning = self.sql(expression, "returning")
2483        order = self.sql(expression, "order")
2484        limit = self.sql(expression, "limit")
2485        if self.RETURNING_END:
2486            expression_sql = f"{from_sql}{where_sql}{returning}"
2487        else:
2488            expression_sql = f"{returning}{from_sql}{where_sql}"
2489        options = self.expressions(expression, key="options")
2490        options = f" OPTION({options})" if options else ""
2491        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2492        return self.prepend_ctes(expression, sql)
2493
2494    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2495        values_as_table = values_as_table and self.VALUES_AS_TABLE
2496
2497        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2498        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2499            args = self.expressions(expression)
2500            alias = self.sql(expression, "alias")
2501            values = f"VALUES{self.seg('')}{args}"
2502            values = (
2503                f"({values})"
2504                if self.WRAP_DERIVED_VALUES
2505                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2506                else values
2507            )
2508            values = self.query_modifiers(expression, values)
2509            return f"{values} AS {alias}" if alias else values
2510
2511        # Converts `VALUES...` expression into a series of select unions.
2512        alias_node = expression.args.get("alias")
2513        column_names = alias_node and alias_node.columns
2514
2515        selects: list[exp.Query] = []
2516
2517        for i, tup in enumerate(expression.expressions):
2518            row = tup.expressions
2519
2520            if i == 0 and column_names:
2521                row = [
2522                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2523                ]
2524
2525            selects.append(exp.Select(expressions=row))
2526
2527        if self.pretty:
2528            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2529            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2530            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2531            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2532            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2533
2534        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2535        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2536        return f"({unions}){alias}"
2537
2538    def var_sql(self, expression: exp.Var) -> str:
2539        return self.sql(expression, "this")
2540
2541    @unsupported_args("expressions")
2542    def into_sql(self, expression: exp.Into) -> str:
2543        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2544        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2545        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2546
2547    def from_sql(self, expression: exp.From) -> str:
2548        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2549
2550    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2551        grouping_sets = self.expressions(expression, indent=False)
2552        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2553
2554    def rollup_sql(self, expression: exp.Rollup) -> str:
2555        expressions = self.expressions(expression, indent=False)
2556        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2557
2558    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2559        this = self.sql(expression, "this")
2560
2561        columns = self.expressions(expression, flat=True)
2562
2563        from_sql = self.sql(expression, "from_index")
2564        from_sql = f" FROM {from_sql}" if from_sql else ""
2565
2566        properties = expression.args.get("properties")
2567        properties_sql = (
2568            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2569        )
2570
2571        return f"{this}({columns}){from_sql}{properties_sql}"
2572
2573    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2574        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2575
2576    def cube_sql(self, expression: exp.Cube) -> str:
2577        expressions = self.expressions(expression, indent=False)
2578        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2579
2580    def group_sql(self, expression: exp.Group) -> str:
2581        group_by_all = expression.args.get("all")
2582        if group_by_all is True:
2583            modifier = " ALL"
2584        elif group_by_all is False:
2585            modifier = " DISTINCT"
2586        else:
2587            modifier = ""
2588
2589        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2590
2591        grouping_sets = self.expressions(expression, key="grouping_sets")
2592        cube = self.expressions(expression, key="cube")
2593        rollup = self.expressions(expression, key="rollup")
2594
2595        groupings = csv(
2596            self.seg(grouping_sets) if grouping_sets else "",
2597            self.seg(cube) if cube else "",
2598            self.seg(rollup) if rollup else "",
2599            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2600            sep=self.GROUPINGS_SEP,
2601        )
2602
2603        if (
2604            expression.expressions
2605            and groupings
2606            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2607        ):
2608            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2609
2610        return f"{group_by}{groupings}"
2611
2612    def having_sql(self, expression: exp.Having) -> str:
2613        this = self.indent(self.sql(expression, "this"))
2614        return f"{self.seg('HAVING')}{self.sep()}{this}"
2615
2616    def connect_sql(self, expression: exp.Connect) -> str:
2617        start = self.sql(expression, "start")
2618        start = self.seg(f"START WITH {start}") if start else ""
2619        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2620        connect = self.sql(expression, "connect")
2621        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2622        return start + connect
2623
2624    def prior_sql(self, expression: exp.Prior) -> str:
2625        return f"PRIOR {self.sql(expression, 'this')}"
2626
2627    def join_sql(self, expression: exp.Join) -> str:
2628        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2629            side = None
2630        else:
2631            side = expression.side
2632
2633        op_sql = " ".join(
2634            op
2635            for op in (
2636                expression.method,
2637                "GLOBAL" if expression.args.get("global_") else None,
2638                side,
2639                expression.kind,
2640                expression.hint if self.JOIN_HINTS else None,
2641                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2642            )
2643            if op
2644        )
2645        match_cond = self.sql(expression, "match_condition")
2646        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2647        on_sql = self.sql(expression, "on")
2648        using = expression.args.get("using")
2649
2650        if not on_sql and using:
2651            on_sql = csv(*(self.sql(column) for column in using))
2652
2653        this = expression.this
2654        this_sql = self.sql(this)
2655
2656        exprs = self.expressions(expression)
2657        if exprs:
2658            this_sql = f"{this_sql},{self.seg(exprs)}"
2659
2660        if on_sql:
2661            on_sql = self.indent(on_sql, skip_first=True)
2662            space = self.seg(" " * self.pad) if self.pretty else " "
2663            if using:
2664                on_sql = f"{space}USING ({on_sql})"
2665            else:
2666                on_sql = f"{space}ON {on_sql}"
2667        elif not op_sql:
2668            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2669                return f" {this_sql}"
2670
2671            return f", {this_sql}"
2672
2673        if op_sql != "STRAIGHT_JOIN":
2674            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2675
2676        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2677        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2678
2679    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2680        args = self.expressions(expression, flat=True)
2681        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2682        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2683
2684    def lateral_op(self, expression: exp.Lateral) -> str:
2685        cross_apply = expression.args.get("cross_apply")
2686
2687        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2688        if cross_apply is True:
2689            op = "INNER JOIN "
2690        elif cross_apply is False:
2691            op = "LEFT JOIN "
2692        else:
2693            op = ""
2694
2695        return f"{op}LATERAL"
2696
2697    def lateral_sql(self, expression: exp.Lateral) -> str:
2698        this = self.sql(expression, "this")
2699
2700        if expression.args.get("view"):
2701            alias = expression.args["alias"]
2702            columns = self.expressions(alias, key="columns", flat=True)
2703            table = f" {alias.name}" if alias.name else ""
2704            columns = f" AS {columns}" if columns else ""
2705            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2706            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2707
2708        alias = self.sql(expression, "alias")
2709        alias = f" AS {alias}" if alias else ""
2710
2711        ordinality = expression.args.get("ordinality") or ""
2712        if ordinality:
2713            ordinality = f" WITH ORDINALITY{alias}"
2714            alias = ""
2715
2716        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2717
2718    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2719        this = self.sql(expression, "this")
2720
2721        args = [
2722            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2723            for e in (expression.args.get(k) for k in ("offset", "expression"))
2724            if e
2725        ]
2726
2727        args_sql = ", ".join(self.sql(e) for e in args)
2728        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2729        expressions = self.expressions(expression, flat=True)
2730        limit_options = self.sql(expression, "limit_options")
2731        expressions = f" BY {expressions}" if expressions else ""
2732
2733        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2734
2735    def offset_sql(self, expression: exp.Offset) -> str:
2736        this = self.sql(expression, "this")
2737        value = expression.expression
2738        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2739        expressions = self.expressions(expression, flat=True)
2740        expressions = f" BY {expressions}" if expressions else ""
2741        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2742
2743    def setitem_sql(self, expression: exp.SetItem) -> str:
2744        kind = self.sql(expression, "kind")
2745        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2746            kind = ""
2747        else:
2748            kind = f"{kind} " if kind else ""
2749        this = self.sql(expression, "this")
2750        expressions = self.expressions(expression)
2751        collate = self.sql(expression, "collate")
2752        collate = f" COLLATE {collate}" if collate else ""
2753        global_ = "GLOBAL " if expression.args.get("global_") else ""
2754        return f"{global_}{kind}{this}{expressions}{collate}"
2755
2756    def set_sql(self, expression: exp.Set) -> str:
2757        expressions = f" {self.expressions(expression, flat=True)}"
2758        tag = " TAG" if expression.args.get("tag") else ""
2759        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2760
2761    def queryband_sql(self, expression: exp.QueryBand) -> str:
2762        this = self.sql(expression, "this")
2763        update = " UPDATE" if expression.args.get("update") else ""
2764        scope = self.sql(expression, "scope")
2765        scope = f" FOR {scope}" if scope else ""
2766
2767        return f"QUERY_BAND = {this}{update}{scope}"
2768
2769    def pragma_sql(self, expression: exp.Pragma) -> str:
2770        return f"PRAGMA {self.sql(expression, 'this')}"
2771
2772    def lock_sql(self, expression: exp.Lock) -> str:
2773        if not self.LOCKING_READS_SUPPORTED:
2774            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2775            return ""
2776
2777        update = expression.args["update"]
2778        key = expression.args.get("key")
2779        if update:
2780            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2781        else:
2782            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2783        expressions = self.expressions(expression, flat=True)
2784        expressions = f" OF {expressions}" if expressions else ""
2785        wait = expression.args.get("wait")
2786
2787        if wait is not None:
2788            if isinstance(wait, exp.Literal):
2789                wait = f" WAIT {self.sql(wait)}"
2790            else:
2791                wait = " NOWAIT" if wait else " SKIP LOCKED"
2792
2793        return f"{lock_type}{expressions}{wait or ''}"
2794
2795    def literal_sql(self, expression: exp.Literal) -> str:
2796        text = expression.this or ""
2797        if expression.is_string:
2798            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2799        return text
2800
2801    def escape_str(
2802        self,
2803        text: str,
2804        escape_backslash: bool = True,
2805        delimiter: str | None = None,
2806        escaped_delimiter: str | None = None,
2807        is_byte_string: bool = False,
2808    ) -> str:
2809        if is_byte_string:
2810            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
2811        else:
2812            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
2813
2814        if supports_escape_sequences:
2815            text = "".join(
2816                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
2817                for ch in text
2818            )
2819
2820        delimiter = delimiter or self.dialect.QUOTE_END
2821        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2822
2823        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2824
2825    def loaddata_sql(self, expression: exp.LoadData) -> str:
2826        is_overwrite = expression.args.get("overwrite")
2827        overwrite = " OVERWRITE" if is_overwrite else ""
2828        this = self.sql(expression, "this")
2829
2830        files = expression.args.get("files")
2831        if files:
2832            files_sql = self.expressions(files, flat=True)
2833            files_sql = f"FILES{self.wrap(files_sql)}"
2834            this = f" {this}" if is_overwrite else f" INTO TABLE {this}"
2835            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
2836
2837        local = " LOCAL" if expression.args.get("local") else ""
2838        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2839        this = f" INTO TABLE {this}"
2840        partition = self.sql(expression, "partition")
2841        partition = f" {partition}" if partition else ""
2842        input_format = self.sql(expression, "input_format")
2843        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2844        serde = self.sql(expression, "serde")
2845        serde = f" SERDE {serde}" if serde else ""
2846        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2847
2848    def null_sql(self, *_) -> str:
2849        return "NULL"
2850
2851    def boolean_sql(self, expression: exp.Boolean) -> str:
2852        return "TRUE" if expression.this else "FALSE"
2853
2854    def booland_sql(self, expression: exp.Booland) -> str:
2855        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
2856
2857    def boolor_sql(self, expression: exp.Boolor) -> str:
2858        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
2859
2860    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2861        this = self.sql(expression, "this")
2862        this = f"{this} " if this else this
2863        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2864        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
2865
2866    def withfill_sql(self, expression: exp.WithFill) -> str:
2867        from_sql = self.sql(expression, "from_")
2868        from_sql = f" FROM {from_sql}" if from_sql else ""
2869        to_sql = self.sql(expression, "to")
2870        to_sql = f" TO {to_sql}" if to_sql else ""
2871        step_sql = self.sql(expression, "step")
2872        step_sql = f" STEP {step_sql}" if step_sql else ""
2873        interpolated_values = [
2874            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2875            if isinstance(e, exp.Alias)
2876            else self.sql(e, "this")
2877            for e in expression.args.get("interpolate") or []
2878        ]
2879        interpolate = (
2880            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2881        )
2882        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2883
2884    def cluster_sql(self, expression: exp.Cluster) -> str:
2885        return self.op_expressions("CLUSTER BY", expression)
2886
2887    def distribute_sql(self, expression: exp.Distribute) -> str:
2888        return self.op_expressions("DISTRIBUTE BY", expression)
2889
2890    def sort_sql(self, expression: exp.Sort) -> str:
2891        return self.op_expressions("SORT BY", expression)
2892
2893    def ordered_sql(self, expression: exp.Ordered) -> str:
2894        desc = expression.args.get("desc")
2895        asc = not desc
2896
2897        nulls_first = expression.args.get("nulls_first")
2898        nulls_last = not nulls_first
2899        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2900        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2901        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2902
2903        this = self.sql(expression, "this")
2904
2905        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2906        nulls_sort_change = ""
2907        if nulls_first and (
2908            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2909        ):
2910            nulls_sort_change = " NULLS FIRST"
2911        elif (
2912            nulls_last
2913            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2914            and not nulls_are_last
2915        ):
2916            nulls_sort_change = " NULLS LAST"
2917
2918        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2919        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2920            window = expression.find_ancestor(exp.Window, exp.Select)
2921
2922            if isinstance(window, exp.Window):
2923                window_this = window.this
2924                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
2925                    window_this = window_this.this
2926                spec = window.args.get("spec")
2927            else:
2928                window_this = None
2929                spec = None
2930
2931            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
2932            # without a spec or with a ROWS spec, but not with RANGE
2933            if not (
2934                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
2935                and (not spec or spec.text("kind").upper() == "ROWS")
2936            ):
2937                if window_this and spec:
2938                    self.unsupported(
2939                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
2940                    )
2941                    nulls_sort_change = ""
2942                elif self.NULL_ORDERING_SUPPORTED is False and (
2943                    (asc and nulls_sort_change == " NULLS LAST")
2944                    or (desc and nulls_sort_change == " NULLS FIRST")
2945                ):
2946                    # BigQuery does not allow these ordering/nulls combinations when used under
2947                    # an aggregation func or under a window containing one
2948                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2949
2950                    if isinstance(ancestor, exp.Window):
2951                        ancestor = ancestor.this
2952                    if isinstance(ancestor, exp.AggFunc):
2953                        self.unsupported(
2954                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
2955                        )
2956                        nulls_sort_change = ""
2957                elif self.NULL_ORDERING_SUPPORTED is None:
2958                    if expression.this.is_int:
2959                        self.unsupported(
2960                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2961                        )
2962                    elif not isinstance(expression.this, exp.Rand):
2963                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2964                        this = (
2965                            f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2966                        )
2967                    nulls_sort_change = ""
2968
2969        with_fill = self.sql(expression, "with_fill")
2970        with_fill = f" {with_fill}" if with_fill else ""
2971
2972        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2973
2974    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2975        window_frame = self.sql(expression, "window_frame")
2976        window_frame = f"{window_frame} " if window_frame else ""
2977
2978        this = self.sql(expression, "this")
2979
2980        return f"{window_frame}{this}"
2981
2982    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2983        partition = self.partition_by_sql(expression)
2984        order = self.sql(expression, "order")
2985        measures = self.expressions(expression, key="measures")
2986        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2987        rows = self.sql(expression, "rows")
2988        rows = self.seg(rows) if rows else ""
2989        after = self.sql(expression, "after")
2990        after = self.seg(after) if after else ""
2991        pattern = self.sql(expression, "pattern")
2992        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2993        definition_sqls = [
2994            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2995            for definition in expression.args.get("define", [])
2996        ]
2997        definitions = self.expressions(sqls=definition_sqls)
2998        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2999        body = "".join(
3000            (
3001                partition,
3002                order,
3003                measures,
3004                rows,
3005                after,
3006                pattern,
3007                define,
3008            )
3009        )
3010        alias = self.sql(expression, "alias")
3011        alias = f" {alias}" if alias else ""
3012        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3013
3014    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3015        limit = expression.args.get("limit")
3016
3017        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3018            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
3019        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
3020            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3021
3022        return csv(
3023            *sqls,
3024            *[self.sql(join) for join in expression.args.get("joins") or []],
3025            self.sql(expression, "match"),
3026            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3027            self.sql(expression, "prewhere"),
3028            self.sql(expression, "where"),
3029            self.sql(expression, "connect"),
3030            self.sql(expression, "group"),
3031            self.sql(expression, "having"),
3032            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3033            self.sql(expression, "order"),
3034            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3035            *self.after_limit_modifiers(expression),
3036            self.options_modifier(expression),
3037            self.for_modifiers(expression),
3038            sep="",
3039        )
3040
3041    def options_modifier(self, expression: exp.Expr) -> str:
3042        options = self.expressions(expression, key="options")
3043        return f" {options}" if options else ""
3044
3045    def for_modifiers(self, expression: exp.Expr) -> str:
3046        for_modifiers = self.expressions(expression, key="for_")
3047        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
3048
3049    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3050        self.unsupported("Unsupported query option.")
3051        return ""
3052
3053    def offset_limit_modifiers(
3054        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3055    ) -> list[str]:
3056        return [
3057            self.sql(expression, "offset") if fetch else self.sql(limit),
3058            self.sql(limit) if fetch else self.sql(expression, "offset"),
3059        ]
3060
3061    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3062        locks = self.expressions(expression, key="locks", sep=" ")
3063        locks = f" {locks}" if locks else ""
3064        return [locks, self.sql(expression, "sample")]
3065
3066    def select_sql(self, expression: exp.Select) -> str:
3067        into = expression.args.get("into")
3068        if not self.SUPPORTS_SELECT_INTO and into:
3069            into.pop()
3070
3071        hint = self.sql(expression, "hint")
3072        distinct = self.sql(expression, "distinct")
3073        distinct = f" {distinct}" if distinct else ""
3074        kind = self.sql(expression, "kind")
3075
3076        limit = expression.args.get("limit")
3077        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3078            top = self.limit_sql(limit, top=True)
3079            limit.pop()
3080        else:
3081            top = ""
3082
3083        expressions = self.expressions(expression)
3084
3085        if kind:
3086            if kind in self.SELECT_KINDS:
3087                kind = f" AS {kind}"
3088            else:
3089                if kind == "STRUCT":
3090                    expressions = self.expressions(
3091                        sqls=[
3092                            self.sql(
3093                                exp.Struct(
3094                                    expressions=[
3095                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3096                                        if isinstance(e, exp.Alias)
3097                                        else e
3098                                        for e in expression.expressions
3099                                    ]
3100                                )
3101                            )
3102                        ]
3103                    )
3104                kind = ""
3105
3106        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3107        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3108
3109        exclude = expression.args.get("exclude")
3110
3111        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3112            exclude_sql = self.expressions(sqls=exclude, flat=True)
3113            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3114
3115        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3116        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3117        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3118        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3119        sql = self.query_modifiers(
3120            expression,
3121            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3122            self.sql(expression, "into", comment=False),
3123            self.sql(expression, "from_", comment=False),
3124        )
3125
3126        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3127        if expression.args.get("with_"):
3128            sql = self.maybe_comment(sql, expression)
3129            expression.pop_comments()
3130
3131        sql = self.prepend_ctes(expression, sql)
3132
3133        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3134            expression.set("exclude", None)
3135            subquery = expression.subquery(copy=False)
3136            star = exp.Star(except_=exclude)
3137            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3138
3139        if not self.SUPPORTS_SELECT_INTO and into:
3140            if into.args.get("temporary"):
3141                table_kind = " TEMPORARY"
3142            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3143                table_kind = " UNLOGGED"
3144            else:
3145                table_kind = ""
3146            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3147
3148        return sql
3149
3150    def schema_sql(self, expression: exp.Schema) -> str:
3151        this = self.sql(expression, "this")
3152        sql = self.schema_columns_sql(expression)
3153        return f"{this} {sql}" if this and sql else this or sql
3154
3155    def schema_columns_sql(self, expression: exp.Expr) -> str:
3156        if expression.expressions:
3157            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3158        return ""
3159
3160    def star_sql(self, expression: exp.Star) -> str:
3161        except_ = self.expressions(expression, key="except_", flat=True)
3162        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3163        replace = self.expressions(expression, key="replace", flat=True)
3164        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3165        rename = self.expressions(expression, key="rename", flat=True)
3166        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3167        return f"*{except_}{replace}{rename}"
3168
3169    def parameter_sql(self, expression: exp.Parameter) -> str:
3170        this = self.sql(expression, "this")
3171        return f"{self.PARAMETER_TOKEN}{this}"
3172
3173    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3174        this = self.sql(expression, "this")
3175        kind = expression.text("kind")
3176        if kind:
3177            kind = f"{kind}."
3178        return f"@@{kind}{this}"
3179
3180    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3181        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3182
3183    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3184        alias = self.sql(expression, "alias")
3185        alias = f"{sep}{alias}" if alias else ""
3186        sample = self.sql(expression, "sample")
3187        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3188            alias = f"{sample}{alias}"
3189
3190            # Set to None so it's not generated again by self.query_modifiers()
3191            expression.set("sample", None)
3192
3193        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3194        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3195        return self.prepend_ctes(expression, sql)
3196
3197    def qualify_sql(self, expression: exp.Qualify) -> str:
3198        this = self.indent(self.sql(expression, "this"))
3199        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3200
3201    def unnest_sql(self, expression: exp.Unnest) -> str:
3202        args = self.expressions(expression, flat=True)
3203
3204        alias = expression.args.get("alias")
3205        offset = expression.args.get("offset")
3206
3207        if self.UNNEST_WITH_ORDINALITY:
3208            if alias and isinstance(offset, exp.Expr):
3209                alias.append("columns", offset)
3210                expression.set("offset", None)
3211
3212        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3213            columns = alias.columns
3214            alias = self.sql(columns[0]) if columns else ""
3215        else:
3216            alias = self.sql(alias)
3217
3218        alias = f" AS {alias}" if alias else alias
3219        if self.UNNEST_WITH_ORDINALITY:
3220            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3221        else:
3222            if isinstance(offset, exp.Expr):
3223                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3224            elif offset:
3225                suffix = f"{alias} WITH OFFSET"
3226            else:
3227                suffix = alias
3228
3229        return f"UNNEST({args}){suffix}"
3230
3231    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3232        return ""
3233
3234    def where_sql(self, expression: exp.Where) -> str:
3235        this = self.indent(self.sql(expression, "this"))
3236        return f"{self.seg('WHERE')}{self.sep()}{this}"
3237
3238    def window_sql(self, expression: exp.Window) -> str:
3239        this = self.sql(expression, "this")
3240        partition = self.partition_by_sql(expression)
3241        order = expression.args.get("order")
3242        order = self.order_sql(order, flat=True) if order else ""
3243        spec = self.sql(expression, "spec")
3244        alias = self.sql(expression, "alias")
3245        over = self.sql(expression, "over") or "OVER"
3246
3247        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3248
3249        first = expression.args.get("first")
3250        if first is None:
3251            first = ""
3252        else:
3253            first = "FIRST" if first else "LAST"
3254
3255        if not partition and not order and not spec and alias:
3256            return f"{this} {alias}"
3257
3258        args = self.format_args(
3259            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3260        )
3261        return f"{this} ({args})"
3262
3263    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3264        partition = self.expressions(expression, key="partition_by", flat=True)
3265        return f"PARTITION BY {partition}" if partition else ""
3266
3267    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3268        kind = self.sql(expression, "kind")
3269        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3270        end = (
3271            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3272            or "CURRENT ROW"
3273        )
3274
3275        window_spec = f"{kind} BETWEEN {start} AND {end}"
3276
3277        exclude = self.sql(expression, "exclude")
3278        if exclude:
3279            if self.SUPPORTS_WINDOW_EXCLUDE:
3280                window_spec += f" EXCLUDE {exclude}"
3281            else:
3282                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3283
3284        return window_spec
3285
3286    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3287        this = self.sql(expression, "this")
3288        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3289        return f"{this} WITHIN GROUP ({expression_sql})"
3290
3291    def between_sql(self, expression: exp.Between) -> str:
3292        this = self.sql(expression, "this")
3293        low = self.sql(expression, "low")
3294        high = self.sql(expression, "high")
3295        symmetric = expression.args.get("symmetric")
3296
3297        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3298            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3299
3300        flag = (
3301            " SYMMETRIC"
3302            if symmetric
3303            else " ASYMMETRIC"
3304            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3305            else ""  # silently drop ASYMMETRIC – semantics identical
3306        )
3307        return f"{this} BETWEEN{flag} {low} AND {high}"
3308
3309    def bracket_offset_expressions(
3310        self, expression: exp.Bracket, index_offset: int | None = None
3311    ) -> list[exp.Expr]:
3312        if expression.args.get("json_access"):
3313            return expression.expressions
3314
3315        return apply_index_offset(
3316            expression.this,
3317            expression.expressions,
3318            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3319            dialect=self.dialect,
3320        )
3321
3322    def bracket_sql(self, expression: exp.Bracket) -> str:
3323        expressions = self.bracket_offset_expressions(expression)
3324        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3325        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3326
3327    def all_sql(self, expression: exp.All) -> str:
3328        this = self.sql(expression, "this")
3329        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3330            this = self.wrap(this)
3331        return f"ALL {this}"
3332
3333    def any_sql(self, expression: exp.Any) -> str:
3334        this = self.sql(expression, "this")
3335        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3336            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3337                this = self.wrap(this)
3338            return f"ANY{this}"
3339        return f"ANY {this}"
3340
3341    def exists_sql(self, expression: exp.Exists) -> str:
3342        return f"EXISTS{self.wrap(expression)}"
3343
3344    def case_sql(self, expression: exp.Case) -> str:
3345        this = self.sql(expression, "this")
3346        statements = [f"CASE {this}" if this else "CASE"]
3347
3348        for e in expression.args["ifs"]:
3349            statements.append(f"WHEN {self.sql(e, 'this')}")
3350            statements.append(f"THEN {self.sql(e, 'true')}")
3351
3352        default = self.sql(expression, "default")
3353
3354        if default:
3355            statements.append(f"ELSE {default}")
3356
3357        statements.append("END")
3358
3359        if self.pretty and self.too_wide(statements):
3360            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3361
3362        return " ".join(statements)
3363
3364    def constraint_sql(self, expression: exp.Constraint) -> str:
3365        this = self.sql(expression, "this")
3366        expressions = self.expressions(expression, flat=True)
3367        return f"CONSTRAINT {this} {expressions}"
3368
3369    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3370        order = expression.args.get("order")
3371        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3372        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3373
3374    def extract_sql(self, expression: exp.Extract) -> str:
3375        import sqlglot.dialects.dialect
3376
3377        this = (
3378            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3379            if self.NORMALIZE_EXTRACT_DATE_PARTS
3380            else expression.this
3381        )
3382        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3383        expression_sql = self.sql(expression, "expression")
3384
3385        return f"EXTRACT({this_sql} FROM {expression_sql})"
3386
3387    def trim_sql(self, expression: exp.Trim) -> str:
3388        trim_type = self.sql(expression, "position")
3389
3390        if trim_type == "LEADING":
3391            func_name = "LTRIM"
3392        elif trim_type == "TRAILING":
3393            func_name = "RTRIM"
3394        else:
3395            func_name = "TRIM"
3396
3397        return self.func(func_name, expression.this, expression.expression)
3398
3399    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3400        args = expression.expressions
3401        if isinstance(expression, exp.ConcatWs):
3402            args = args[1:]  # Skip the delimiter
3403
3404        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3405            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3406
3407        concat_coalesce = (
3408            self.dialect.CONCAT_WS_COALESCE
3409            if isinstance(expression, exp.ConcatWs)
3410            else self.dialect.CONCAT_COALESCE
3411        )
3412
3413        if not concat_coalesce and expression.args.get("coalesce"):
3414
3415            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3416                if not e.type:
3417                    import sqlglot.optimizer.annotate_types
3418
3419                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3420
3421                if e.is_string or e.is_type(exp.DType.ARRAY):
3422                    return e
3423
3424                return exp.func("coalesce", e, exp.Literal.string(""))
3425
3426            args = [_wrap_with_coalesce(e) for e in args]
3427
3428        return args
3429
3430    def concat_sql(self, expression: exp.Concat) -> str:
3431        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3432            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3433            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3434            # instead of coalescing them to empty string.
3435            import sqlglot.dialects.dialect
3436
3437            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3438
3439        expressions = self.convert_concat_args(expression)
3440
3441        # Some dialects don't allow a single-argument CONCAT call
3442        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3443            return self.sql(expressions[0])
3444
3445        return self.func("CONCAT", *expressions)
3446
3447    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3448        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3449            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3450            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3451            all_args = expression.expressions
3452            expression.set("coalesce", True)
3453            return self.sql(
3454                exp.case()
3455                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3456                .else_(expression)
3457            )
3458
3459        return self.func(
3460            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3461        )
3462
3463    def check_sql(self, expression: exp.Check) -> str:
3464        this = self.sql(expression, key="this")
3465        return f"CHECK ({this})"
3466
3467    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3468        expressions = self.expressions(expression, flat=True)
3469        expressions = f" ({expressions})" if expressions else ""
3470        reference = self.sql(expression, "reference")
3471        reference = f" {reference}" if reference else ""
3472        delete = self.sql(expression, "delete")
3473        delete = f" ON DELETE {delete}" if delete else ""
3474        update = self.sql(expression, "update")
3475        update = f" ON UPDATE {update}" if update else ""
3476        options = self.expressions(expression, key="options", flat=True, sep=" ")
3477        options = f" {options}" if options else ""
3478        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3479
3480    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3481        this = self.sql(expression, "this")
3482        this = f" {this}" if this else ""
3483        expressions = self.expressions(expression, flat=True)
3484        include = self.sql(expression, "include")
3485        options = self.expressions(expression, key="options", flat=True, sep=" ")
3486        options = f" {options}" if options else ""
3487        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3488
3489    def if_sql(self, expression: exp.If) -> str:
3490        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3491
3492    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3493        if self.MATCH_AGAINST_TABLE_PREFIX:
3494            expressions = []
3495            for expr in expression.expressions:
3496                if isinstance(expr, exp.Table):
3497                    expressions.append(f"TABLE {self.sql(expr)}")
3498                else:
3499                    expressions.append(expr)
3500        else:
3501            expressions = expression.expressions
3502
3503        modifier = expression.args.get("modifier")
3504        modifier = f" {modifier}" if modifier else ""
3505        return (
3506            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3507        )
3508
3509    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3510        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3511
3512    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3513        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3514
3515        if expression.args.get("escape"):
3516            path = self.escape_str(path)
3517
3518        if self.QUOTE_JSON_PATH:
3519            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3520
3521        return path
3522
3523    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3524        if isinstance(expression, exp.JSONPathPart):
3525            transform = self.TRANSFORMS.get(expression.__class__)
3526            if not callable(transform):
3527                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3528                return ""
3529
3530            return transform(self, expression)
3531
3532        if isinstance(expression, int):
3533            return str(expression)
3534
3535        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3536            escaped = expression.replace("'", "\\'")
3537            escaped = f"\\'{expression}\\'"
3538        else:
3539            escaped = expression.replace('"', '\\"')
3540            escaped = f'"{escaped}"'
3541
3542        return escaped
3543
3544    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3545        return f"{self.sql(expression, 'this')} FORMAT JSON"
3546
3547    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3548        # Output the Teradata column FORMAT override.
3549        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3550        this = self.sql(expression, "this")
3551        fmt = self.sql(expression, "format")
3552        return f"{this} (FORMAT {fmt})"
3553
3554    def _jsonobject_sql(
3555        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = ""
3556    ) -> str:
3557        null_handling = expression.args.get("null_handling")
3558        null_handling = f" {null_handling}" if null_handling else ""
3559
3560        unique_keys = expression.args.get("unique_keys")
3561        if unique_keys is not None:
3562            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3563        else:
3564            unique_keys = ""
3565
3566        return_type = self.sql(expression, "return_type")
3567        return_type = f" RETURNING {return_type}" if return_type else ""
3568        encoding = self.sql(expression, "encoding")
3569        encoding = f" ENCODING {encoding}" if encoding else ""
3570
3571        if not name:
3572            name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG"
3573
3574        return self.func(
3575            name,
3576            *expression.expressions,
3577            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3578        )
3579
3580    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
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_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3588        )
3589
3590    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3591        this = self.sql(expression, "this")
3592        order = self.sql(expression, "order")
3593        null_handling = expression.args.get("null_handling")
3594        null_handling = f" {null_handling}" if null_handling else ""
3595        return_type = self.sql(expression, "return_type")
3596        return_type = f" RETURNING {return_type}" if return_type else ""
3597        strict = " STRICT" if expression.args.get("strict") else ""
3598        return self.func(
3599            "JSON_ARRAYAGG",
3600            this,
3601            suffix=f"{order}{null_handling}{return_type}{strict})",
3602        )
3603
3604    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3605        path = self.sql(expression, "path")
3606        path = f" PATH {path}" if path else ""
3607        nested_schema = self.sql(expression, "nested_schema")
3608
3609        if nested_schema:
3610            return f"NESTED{path} {nested_schema}"
3611
3612        this = self.sql(expression, "this")
3613        kind = self.sql(expression, "kind")
3614        kind = f" {kind}" if kind else ""
3615        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3616
3617        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3618        return f"{this}{kind}{format_json}{path}{ordinality}"
3619
3620    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3621        return self.func("COLUMNS", *expression.expressions)
3622
3623    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3624        this = self.sql(expression, "this")
3625        path = self.sql(expression, "path")
3626        path = f", {path}" if path else ""
3627        error_handling = expression.args.get("error_handling")
3628        error_handling = f" {error_handling}" if error_handling else ""
3629        empty_handling = expression.args.get("empty_handling")
3630        empty_handling = f" {empty_handling}" if empty_handling else ""
3631        schema = self.sql(expression, "schema")
3632        return self.func(
3633            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3634        )
3635
3636    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3637        this = self.sql(expression, "this")
3638        kind = self.sql(expression, "kind")
3639        path = self.sql(expression, "path")
3640        path = f" {path}" if path else ""
3641        as_json = " AS JSON" if expression.args.get("as_json") else ""
3642        return f"{this} {kind}{path}{as_json}"
3643
3644    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3645        this = self.sql(expression, "this")
3646        path = self.sql(expression, "path")
3647        path = f", {path}" if path else ""
3648        expressions = self.expressions(expression)
3649        with_ = (
3650            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3651            if expressions
3652            else ""
3653        )
3654        return f"OPENJSON({this}{path}){with_}"
3655
3656    def in_sql(self, expression: exp.In) -> str:
3657        query = expression.args.get("query")
3658        unnest = expression.args.get("unnest")
3659        field = expression.args.get("field")
3660        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3661
3662        if query:
3663            in_sql = self.sql(query)
3664        elif unnest:
3665            in_sql = self.in_unnest_op(unnest)
3666        elif field:
3667            in_sql = self.sql(field)
3668        else:
3669            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3670
3671        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3672
3673    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3674        return f"(SELECT {self.sql(unnest)})"
3675
3676    def interval_sql(self, expression: exp.Interval) -> str:
3677        unit_expression = expression.args.get("unit")
3678        unit = self.sql(unit_expression) if unit_expression else ""
3679        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3680            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3681        unit = f" {unit}" if unit else ""
3682
3683        if self.SINGLE_STRING_INTERVAL:
3684            this = expression.this.name if expression.this else ""
3685            if this:
3686                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3687                    return f"INTERVAL '{this}'{unit}"
3688                return f"INTERVAL '{this}{unit}'"
3689            return f"INTERVAL{unit}"
3690
3691        this = self.sql(expression, "this")
3692        if this:
3693            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3694            this = f" {this}" if unwrapped else f" ({this})"
3695
3696        return f"INTERVAL{this}{unit}"
3697
3698    def return_sql(self, expression: exp.Return) -> str:
3699        return f"RETURN {self.sql(expression, 'this')}"
3700
3701    def reference_sql(self, expression: exp.Reference) -> str:
3702        this = self.sql(expression, "this")
3703        expressions = self.expressions(expression, flat=True)
3704        expressions = f"({expressions})" if expressions else ""
3705        options = self.expressions(expression, key="options", flat=True, sep=" ")
3706        options = f" {options}" if options else ""
3707        return f"REFERENCES {this}{expressions}{options}"
3708
3709    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3710        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3711        parent = expression.parent
3712        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3713
3714        return self.func(
3715            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3716        )
3717
3718    def paren_sql(self, expression: exp.Paren) -> str:
3719        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3720        return f"({sql}{self.seg(')', sep='')}"
3721
3722    def neg_sql(self, expression: exp.Neg) -> str:
3723        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3724        this_sql = self.sql(expression, "this")
3725        sep = " " if this_sql[0] == "-" else ""
3726        return f"-{sep}{this_sql}"
3727
3728    def not_sql(self, expression: exp.Not) -> str:
3729        return f"NOT {self.sql(expression, 'this')}"
3730
3731    def alias_sql(self, expression: exp.Alias) -> str:
3732        alias = self.sql(expression, "alias")
3733        alias = f" AS {alias}" if alias else ""
3734        return f"{self.sql(expression, 'this')}{alias}"
3735
3736    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3737        alias = expression.args["alias"]
3738
3739        parent = expression.parent
3740        pivot = parent and parent.parent
3741
3742        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3743            identifier_alias = isinstance(alias, exp.Identifier)
3744            literal_alias = isinstance(alias, exp.Literal)
3745
3746            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3747                alias.replace(exp.Literal.string(alias.output_name))
3748            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3749                alias.replace(exp.to_identifier(alias.output_name))
3750
3751        return self.alias_sql(expression)
3752
3753    def aliases_sql(self, expression: exp.Aliases) -> str:
3754        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3755
3756    def atindex_sql(self, expression: exp.AtIndex) -> str:
3757        this = self.sql(expression, "this")
3758        index = self.sql(expression, "expression")
3759        return f"{this} AT {index}"
3760
3761    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3762        this = self.sql(expression, "this")
3763        zone = self.sql(expression, "zone")
3764        return f"{this} AT TIME ZONE {zone}"
3765
3766    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3767        this = self.sql(expression, "this")
3768        zone = self.sql(expression, "zone")
3769        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3770
3771    def add_sql(self, expression: exp.Add) -> str:
3772        return self.binary(expression, "+")
3773
3774    def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str:
3775        return self.connector_sql(expression, "AND", stack)
3776
3777    def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str:
3778        return self.connector_sql(expression, "OR", stack)
3779
3780    def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str:
3781        return self.connector_sql(expression, "XOR", stack)
3782
3783    def connector_sql(
3784        self,
3785        expression: exp.Connector,
3786        op: str,
3787        stack: list[str | exp.Expr] | None = None,
3788    ) -> str:
3789        if stack is not None:
3790            if expression.expressions:
3791                stack.append(self.expressions(expression, sep=f" {op} "))
3792            else:
3793                stack.append(expression.right)
3794                if expression.comments and self.comments:
3795                    for comment in expression.comments:
3796                        if comment:
3797                            op += f" /*{self.sanitize_comment(comment)}*/"
3798                stack.extend((op, expression.left))
3799            return op
3800
3801        stack = [expression]
3802        sqls: list[str] = []
3803        ops = set()
3804
3805        while stack:
3806            node = stack.pop()
3807            if isinstance(node, exp.Connector):
3808                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3809            else:
3810                sql = self.sql(node)
3811                if sqls and sqls[-1] in ops:
3812                    sqls[-1] += f" {sql}"
3813                else:
3814                    sqls.append(sql)
3815
3816        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3817        return sep.join(sqls)
3818
3819    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3820        return self.binary(expression, "&")
3821
3822    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3823        return self.binary(expression, "<<")
3824
3825    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3826        return f"~{self.sql(expression, 'this')}"
3827
3828    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3829        return self.binary(expression, "|")
3830
3831    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3832        return self.binary(expression, ">>")
3833
3834    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3835        return self.binary(expression, "^")
3836
3837    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
3838        format_sql = self.sql(expression, "format")
3839        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3840        to_sql = self.sql(expression, "to")
3841        to_sql = f" {to_sql}" if to_sql else ""
3842        action = self.sql(expression, "action")
3843        action = f" {action}" if action else ""
3844        default = self.sql(expression, "default")
3845        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3846        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3847
3848    # Base implementation that excludes safe, zone, and target_type metadata args
3849    def strtotime_sql(self, expression: exp.StrToTime) -> str:
3850        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
3851
3852    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3853        zone = self.sql(expression, "this")
3854        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3855
3856    def collate_sql(self, expression: exp.Collate) -> str:
3857        if self.COLLATE_IS_FUNC:
3858            return self.function_fallback_sql(expression)
3859        return self.binary(expression, "COLLATE")
3860
3861    def command_sql(self, expression: exp.Command) -> str:
3862        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3863
3864    def comment_sql(self, expression: exp.Comment) -> str:
3865        this = self.sql(expression, "this")
3866        kind = expression.args["kind"]
3867        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3868        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3869        expression_sql = self.sql(expression, "expression")
3870        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3871
3872    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3873        this = self.sql(expression, "this")
3874        delete = " DELETE" if expression.args.get("delete") else ""
3875        recompress = self.sql(expression, "recompress")
3876        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3877        to_disk = self.sql(expression, "to_disk")
3878        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3879        to_volume = self.sql(expression, "to_volume")
3880        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3881        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3882
3883    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3884        where = self.sql(expression, "where")
3885        group = self.sql(expression, "group")
3886        aggregates = self.expressions(expression, key="aggregates")
3887        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3888
3889        if not (where or group or aggregates) and len(expression.expressions) == 1:
3890            return f"TTL {self.expressions(expression, flat=True)}"
3891
3892        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3893
3894    def transaction_sql(self, expression: exp.Transaction) -> str:
3895        modes = self.expressions(expression, key="modes")
3896        modes = f" {modes}" if modes else ""
3897        return f"BEGIN{modes}"
3898
3899    def commit_sql(self, expression: exp.Commit) -> str:
3900        chain = expression.args.get("chain")
3901        if chain is not None:
3902            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3903
3904        return f"COMMIT{chain or ''}"
3905
3906    def rollback_sql(self, expression: exp.Rollback) -> str:
3907        savepoint = expression.args.get("savepoint")
3908        savepoint = f" TO {savepoint}" if savepoint else ""
3909        return f"ROLLBACK{savepoint}"
3910
3911    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3912        this = self.sql(expression, "this")
3913
3914        dtype = self.sql(expression, "dtype")
3915        if dtype:
3916            collate = self.sql(expression, "collate")
3917            collate = f" COLLATE {collate}" if collate else ""
3918            using = self.sql(expression, "using")
3919            using = f" USING {using}" if using else ""
3920            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3921            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3922
3923        default = self.sql(expression, "default")
3924        if default:
3925            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3926
3927        comment = self.sql(expression, "comment")
3928        if comment:
3929            return f"ALTER COLUMN {this} COMMENT {comment}"
3930
3931        visible = expression.args.get("visible")
3932        if visible:
3933            return f"ALTER COLUMN {this} SET {visible}"
3934
3935        allow_null = expression.args.get("allow_null")
3936        drop = expression.args.get("drop")
3937
3938        if not drop and not allow_null:
3939            self.unsupported("Unsupported ALTER COLUMN syntax")
3940
3941        if allow_null is not None:
3942            keyword = "DROP" if drop else "SET"
3943            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3944
3945        return f"ALTER COLUMN {this} DROP DEFAULT"
3946
3947    def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str:
3948        if not self.SUPPORTS_MODIFY_COLUMN:
3949            self.unsupported("MODIFY COLUMN is not supported in this dialect")
3950        return f"MODIFY COLUMN {self.sql(expression, 'this')}"
3951
3952    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3953        this = self.sql(expression, "this")
3954
3955        visible = expression.args.get("visible")
3956        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3957
3958        return f"ALTER INDEX {this} {visible_sql}"
3959
3960    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3961        this = self.sql(expression, "this")
3962        if not isinstance(expression.this, exp.Var):
3963            this = f"KEY DISTKEY {this}"
3964        return f"ALTER DISTSTYLE {this}"
3965
3966    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3967        compound = " COMPOUND" if expression.args.get("compound") else ""
3968        this = self.sql(expression, "this")
3969        expressions = self.expressions(expression, flat=True)
3970        expressions = f"({expressions})" if expressions else ""
3971        return f"ALTER{compound} SORTKEY {this or expressions}"
3972
3973    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
3974        if not self.RENAME_TABLE_WITH_DB:
3975            # Remove db from tables
3976            expression = expression.transform(
3977                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3978            ).assert_is(exp.AlterRename)
3979        this = self.sql(expression, "this")
3980        to_kw = " TO" if include_to else ""
3981        return f"RENAME{to_kw} {this}"
3982
3983    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3984        exists = " IF EXISTS" if expression.args.get("exists") else ""
3985        old_column = self.sql(expression, "this")
3986        new_column = self.sql(expression, "to")
3987        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3988
3989    def alterset_sql(self, expression: exp.AlterSet) -> str:
3990        exprs = self.expressions(expression, flat=True)
3991        if self.ALTER_SET_WRAPPED:
3992            exprs = f"({exprs})"
3993
3994        return f"SET {exprs}"
3995
3996    def alter_sql(self, expression: exp.Alter) -> str:
3997        actions = expression.args["actions"]
3998
3999        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
4000            actions[0], exp.ColumnDef
4001        ):
4002            actions_sql = self.expressions(expression, key="actions", flat=True)
4003            actions_sql = f"ADD {actions_sql}"
4004        else:
4005            actions_list = []
4006            for action in actions:
4007                if isinstance(action, (exp.ColumnDef, exp.Schema)):
4008                    action_sql = self.add_column_sql(action)
4009                else:
4010                    action_sql = self.sql(action)
4011                    if isinstance(action, exp.Query):
4012                        action_sql = f"AS {action_sql}"
4013
4014                actions_list.append(action_sql)
4015
4016            actions_sql = self.format_args(*actions_list).lstrip("\n")
4017
4018        iceberg = (
4019            "ICEBERG "
4020            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
4021            else ""
4022        )
4023        exists = " IF EXISTS" if expression.args.get("exists") else ""
4024        on_cluster = self.sql(expression, "cluster")
4025        on_cluster = f" {on_cluster}" if on_cluster else ""
4026        only = " ONLY" if expression.args.get("only") else ""
4027        options = self.expressions(expression, key="options")
4028        options = f", {options}" if options else ""
4029        kind = self.sql(expression, "kind")
4030        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
4031        check = " WITH CHECK" if expression.args.get("check") else ""
4032        cascade = (
4033            " CASCADE"
4034            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
4035            else ""
4036        )
4037        this = self.sql(expression, "this")
4038        this = f" {this}" if this else ""
4039
4040        return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4041
4042    def altersession_sql(self, expression: exp.AlterSession) -> str:
4043        items_sql = self.expressions(expression, flat=True)
4044        keyword = "UNSET" if expression.args.get("unset") else "SET"
4045        return f"{keyword} {items_sql}"
4046
4047    def add_column_sql(self, expression: exp.Expr) -> str:
4048        sql = self.sql(expression)
4049        if isinstance(expression, exp.Schema):
4050            column_text = " COLUMNS"
4051        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
4052            column_text = " COLUMN"
4053        else:
4054            column_text = ""
4055
4056        return f"ADD{column_text} {sql}"
4057
4058    def droppartition_sql(self, expression: exp.DropPartition) -> str:
4059        expressions = self.expressions(expression)
4060        exists = " IF EXISTS " if expression.args.get("exists") else " "
4061        return f"DROP{exists}{expressions}"
4062
4063    def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str:
4064        return "DROP PRIMARY KEY"
4065
4066    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
4067        return f"ADD {self.expressions(expression, indent=False)}"
4068
4069    def addpartition_sql(self, expression: exp.AddPartition) -> str:
4070        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
4071        location = self.sql(expression, "location")
4072        location = f" {location}" if location else ""
4073        return f"ADD {exists}{self.sql(expression.this)}{location}"
4074
4075    def distinct_sql(self, expression: exp.Distinct) -> str:
4076        this = self.expressions(expression, flat=True)
4077
4078        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
4079            case = exp.case()
4080            for arg in expression.expressions:
4081                case = case.when(arg.is_(exp.null()), exp.null())
4082            this = self.sql(case.else_(f"({this})"))
4083
4084        this = f" {this}" if this else ""
4085
4086        on = self.sql(expression, "on")
4087        on = f" ON {on}" if on else ""
4088        return f"DISTINCT{this}{on}"
4089
4090    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
4091        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
4092
4093    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
4094        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
4095
4096    def havingmax_sql(self, expression: exp.HavingMax) -> str:
4097        this_sql = self.sql(expression, "this")
4098        expression_sql = self.sql(expression, "expression")
4099        kind = "MAX" if expression.args.get("max") else "MIN"
4100        return f"{this_sql} HAVING {kind} {expression_sql}"
4101
4102    def intdiv_sql(self, expression: exp.IntDiv) -> str:
4103        return self.sql(
4104            exp.Cast(
4105                this=exp.Div(this=expression.this, expression=expression.expression),
4106                to=exp.DataType(this=exp.DType.INT),
4107            )
4108        )
4109
4110    def dpipe_sql(self, expression: exp.DPipe) -> str:
4111        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
4112            return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))
4113        return self.binary(expression, "||")
4114
4115    def div_sql(self, expression: exp.Div) -> str:
4116        l, r = expression.left, expression.right
4117
4118        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
4119            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
4120
4121        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
4122            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
4123                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))
4124
4125        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
4126            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
4127                return self.sql(
4128                    exp.cast(
4129                        l / r,
4130                        to=exp.DType.BIGINT,
4131                    )
4132                )
4133
4134        return self.binary(expression, "/")
4135
4136    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
4137        n = exp._wrap(expression.this, exp.Binary)
4138        d = exp._wrap(expression.expression, exp.Binary)
4139        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
4140
4141    def overlaps_sql(self, expression: exp.Overlaps) -> str:
4142        return self.binary(expression, "OVERLAPS")
4143
4144    def distance_sql(self, expression: exp.Distance) -> str:
4145        return self.binary(expression, "<->")
4146
4147    def dot_sql(self, expression: exp.Dot) -> str:
4148        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
4149
4150    def eq_sql(self, expression: exp.EQ) -> str:
4151        return self.binary(expression, "=")
4152
4153    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4154        return self.binary(expression, ":=")
4155
4156    def escape_sql(self, expression: exp.Escape) -> str:
4157        this = expression.this
4158        if (
4159            isinstance(this, (exp.Like, exp.ILike))
4160            and isinstance(this.expression, (exp.All, exp.Any))
4161            and not self.SUPPORTS_LIKE_QUANTIFIERS
4162        ):
4163            return self._like_sql(this, escape=expression)
4164        return self.binary(expression, "ESCAPE")
4165
4166    def glob_sql(self, expression: exp.Glob) -> str:
4167        return self.binary(expression, "GLOB")
4168
4169    def gt_sql(self, expression: exp.GT) -> str:
4170        return self.binary(expression, ">")
4171
4172    def gte_sql(self, expression: exp.GTE) -> str:
4173        return self.binary(expression, ">=")
4174
4175    def is_sql(self, expression: exp.Is) -> str:
4176        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4177            return self.sql(
4178                expression.this if expression.expression.this else exp.not_(expression.this)
4179            )
4180        return self.binary(expression, "IS")
4181
4182    def _like_sql(
4183        self,
4184        expression: exp.Like | exp.ILike,
4185        escape: exp.Escape | None = None,
4186    ) -> str:
4187        this = expression.this
4188        rhs = expression.expression
4189
4190        if isinstance(expression, exp.Like):
4191            exp_class: type[exp.Like | exp.ILike] = exp.Like
4192            op = "LIKE"
4193        else:
4194            exp_class = exp.ILike
4195            op = "ILIKE"
4196
4197        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4198            exprs = rhs.this.unnest()
4199
4200            if isinstance(exprs, exp.Tuple):
4201                exprs = exprs.expressions
4202            else:
4203                exprs = [exprs]
4204
4205            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4206
4207            def _make_like(expr: exp.Expression) -> exp.Expression:
4208                like: exp.Expression = exp_class(this=this, expression=expr)
4209                if escape:
4210                    like = exp.Escape(this=like, expression=escape.expression.copy())
4211                return like
4212
4213            like_expr: exp.Expr = _make_like(exprs[0])
4214            for expr in exprs[1:]:
4215                like_expr = connective(like_expr, _make_like(expr), copy=False)
4216
4217            parent = escape.parent if escape else expression.parent
4218            if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance(
4219                parent, exp.Condition
4220            ):
4221                like_expr = exp.paren(like_expr, copy=False)
4222
4223            return self.sql(like_expr)
4224
4225        return self.binary(expression, op)
4226
4227    def like_sql(self, expression: exp.Like) -> str:
4228        return self._like_sql(expression)
4229
4230    def ilike_sql(self, expression: exp.ILike) -> str:
4231        return self._like_sql(expression)
4232
4233    def match_sql(self, expression: exp.Match) -> str:
4234        return self.binary(expression, "MATCH")
4235
4236    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4237        return self.binary(expression, "SIMILAR TO")
4238
4239    def lt_sql(self, expression: exp.LT) -> str:
4240        return self.binary(expression, "<")
4241
4242    def lte_sql(self, expression: exp.LTE) -> str:
4243        return self.binary(expression, "<=")
4244
4245    def mod_sql(self, expression: exp.Mod) -> str:
4246        return self.binary(expression, "%")
4247
4248    def mul_sql(self, expression: exp.Mul) -> str:
4249        return self.binary(expression, "*")
4250
4251    def neq_sql(self, expression: exp.NEQ) -> str:
4252        return self.binary(expression, "<>")
4253
4254    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4255        return self.binary(expression, "IS NOT DISTINCT FROM")
4256
4257    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4258        return self.binary(expression, "IS DISTINCT FROM")
4259
4260    def sub_sql(self, expression: exp.Sub) -> str:
4261        return self.binary(expression, "-")
4262
4263    def trycast_sql(self, expression: exp.TryCast) -> str:
4264        return self.cast_sql(expression, safe_prefix="TRY_")
4265
4266    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4267        return self.cast_sql(expression)
4268
4269    def try_sql(self, expression: exp.Try) -> str:
4270        if not self.TRY_SUPPORTED:
4271            self.unsupported("Unsupported TRY function")
4272            return self.sql(expression, "this")
4273
4274        return self.func("TRY", expression.this)
4275
4276    def log_sql(self, expression: exp.Log) -> str:
4277        this = expression.this
4278        expr = expression.expression
4279
4280        if self.dialect.LOG_BASE_FIRST is False:
4281            this, expr = expr, this
4282        elif self.dialect.LOG_BASE_FIRST is None and expr:
4283            if this.name in ("2", "10"):
4284                return self.func(f"LOG{this.name}", expr)
4285
4286            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4287
4288        return self.func("LOG", this, expr)
4289
4290    def use_sql(self, expression: exp.Use) -> str:
4291        kind = self.sql(expression, "kind")
4292        kind = f" {kind}" if kind else ""
4293        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4294        this = f" {this}" if this else ""
4295        return f"USE{kind}{this}"
4296
4297    def binary(self, expression: exp.Binary, op: str) -> str:
4298        sqls: list[str] = []
4299        stack: list[None | str | exp.Expr] = [expression]
4300        binary_type = type(expression)
4301
4302        while stack:
4303            node = stack.pop()
4304
4305            if type(node) is binary_type:
4306                op_func = node.args.get("operator")
4307                if op_func:
4308                    op = f"OPERATOR({self.sql(op_func)})"
4309
4310                stack.append(node.args.get("expression"))
4311                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4312                stack.append(node.args.get("this"))
4313            else:
4314                sqls.append(self.sql(node))
4315
4316        return "".join(sqls)
4317
4318    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4319        to_clause = self.sql(expression, "to")
4320        if to_clause:
4321            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4322
4323        return self.function_fallback_sql(expression)
4324
4325    def function_fallback_sql(self, expression: exp.Func) -> str:
4326        args = []
4327
4328        for key in expression.arg_types:
4329            arg_value = expression.args.get(key)
4330
4331            if isinstance(arg_value, list):
4332                for value in arg_value:
4333                    args.append(value)
4334            elif arg_value is not None:
4335                args.append(arg_value)
4336
4337        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4338            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
4339        else:
4340            name = expression.sql_name()
4341
4342        return self.func(name, *args)
4343
4344    def func(
4345        self,
4346        name: str,
4347        *args: t.Any,
4348        prefix: str = "(",
4349        suffix: str = ")",
4350        normalize: bool = True,
4351    ) -> str:
4352        name = self.normalize_func(name) if normalize else name
4353        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4354
4355    def format_args(self, *args: t.Any, sep: str = ", ") -> str:
4356        arg_sqls = tuple(
4357            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4358        )
4359        if self.pretty and self.too_wide(arg_sqls):
4360            return self.indent(
4361                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4362            )
4363        return sep.join(arg_sqls)
4364
4365    def too_wide(self, args: t.Iterable) -> bool:
4366        return sum(len(arg) for arg in args) > self.max_text_width
4367
4368    def format_time(
4369        self,
4370        expression: exp.Expr,
4371        inverse_time_mapping: dict[str, str] | None = None,
4372        inverse_time_trie: dict | None = None,
4373    ) -> str | None:
4374        return format_time(
4375            self.sql(expression, "format"),
4376            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4377            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4378        )
4379
4380    def expressions(
4381        self,
4382        expression: exp.Expr | None = None,
4383        key: str | None = None,
4384        sqls: t.Collection[str | exp.Expr] | None = None,
4385        flat: bool = False,
4386        indent: bool = True,
4387        skip_first: bool = False,
4388        skip_last: bool = False,
4389        sep: str = ", ",
4390        prefix: str = "",
4391        dynamic: bool = False,
4392        new_line: bool = False,
4393    ) -> str:
4394        expressions = expression.args.get(key or "expressions") if expression else sqls
4395
4396        if not expressions:
4397            return ""
4398
4399        if flat:
4400            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4401
4402        num_sqls = len(expressions)
4403        result_sqls = []
4404
4405        for i, e in enumerate(expressions):
4406            sql = self.sql(e, comment=False)
4407            if not sql:
4408                continue
4409
4410            comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else ""
4411
4412            if self.pretty:
4413                if self.leading_comma:
4414                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4415                else:
4416                    result_sqls.append(
4417                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4418                    )
4419            else:
4420                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4421
4422        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4423            if new_line:
4424                result_sqls.insert(0, "")
4425                result_sqls.append("")
4426            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4427        else:
4428            result_sql = "".join(result_sqls)
4429
4430        return (
4431            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4432            if indent
4433            else result_sql
4434        )
4435
4436    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:
4437        flat = flat or isinstance(expression.parent, exp.Properties)
4438        expressions_sql = self.expressions(expression, flat=flat)
4439        if flat:
4440            return f"{op} {expressions_sql}"
4441        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4442
4443    def naked_property(self, expression: exp.Property) -> str:
4444        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4445        if not property_name:
4446            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4447        return f"{property_name} {self.sql(expression, 'this')}"
4448
4449    def tag_sql(self, expression: exp.Tag) -> str:
4450        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4451
4452    def token_sql(self, token_type: TokenType) -> str:
4453        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4454
4455    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4456        this = self.sql(expression, "this")
4457        expressions = self.no_identify(self.expressions, expression)
4458        expressions = (
4459            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4460        )
4461        return f"{this}{expressions}" if expressions.strip() != "" else this
4462
4463    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4464        this = self.sql(expression, "this")
4465        expressions = self.expressions(expression, flat=True)
4466        return f"{this}({expressions})"
4467
4468    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4469        return self.binary(expression, "=>")
4470
4471    def when_sql(self, expression: exp.When) -> str:
4472        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4473        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4474        condition = self.sql(expression, "condition")
4475        condition = f" AND {condition}" if condition else ""
4476
4477        then_expression = expression.args.get("then")
4478        if isinstance(then_expression, exp.Insert):
4479            this = self.sql(then_expression, "this")
4480            this = f"INSERT {this}" if this else "INSERT"
4481            then = self.sql(then_expression, "expression")
4482            then = f"{this} VALUES {then}" if then else this
4483        elif isinstance(then_expression, exp.Update):
4484            if isinstance(then_expression.args.get("expressions"), exp.Star):
4485                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4486            else:
4487                expressions_sql = self.expressions(then_expression)
4488                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4489        else:
4490            then = self.sql(then_expression)
4491
4492        if isinstance(then_expression, (exp.Insert, exp.Update)):
4493            where = self.sql(then_expression, "where")
4494            if where and not self.SUPPORTS_MERGE_WHERE:
4495                kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE"
4496                self.unsupported(f"WHERE clause in MERGE {kind} is not supported")
4497                where = ""
4498            then = f"{then}{where}"
4499        return f"WHEN {matched}{source}{condition} THEN {then}"
4500
4501    def whens_sql(self, expression: exp.Whens) -> str:
4502        return self.expressions(expression, sep=" ", indent=False)
4503
4504    def merge_sql(self, expression: exp.Merge) -> str:
4505        table = expression.this
4506        table_alias = ""
4507
4508        hints = table.args.get("hints")
4509        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4510            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4511            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4512
4513        this = self.sql(table)
4514        using = f"USING {self.sql(expression, 'using')}"
4515        whens = self.sql(expression, "whens")
4516
4517        on = self.sql(expression, "on")
4518        on = f"ON {on}" if on else ""
4519
4520        if not on:
4521            on = self.expressions(expression, key="using_cond")
4522            on = f"USING ({on})" if on else ""
4523
4524        returning = self.sql(expression, "returning")
4525        if returning:
4526            whens = f"{whens}{returning}"
4527
4528        sep = self.sep()
4529
4530        return self.prepend_ctes(
4531            expression,
4532            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4533        )
4534
4535    @unsupported_args("format")
4536    def tochar_sql(self, expression: exp.ToChar) -> str:
4537        return self.sql(exp.cast(expression.this, exp.DType.TEXT))
4538
4539    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4540        if not self.SUPPORTS_TO_NUMBER:
4541            self.unsupported("Unsupported TO_NUMBER function")
4542            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4543
4544        fmt = expression.args.get("format")
4545        if not fmt:
4546            self.unsupported("Conversion format is required for TO_NUMBER")
4547            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))
4548
4549        return self.func("TO_NUMBER", expression.this, fmt)
4550
4551    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4552        this = self.sql(expression, "this")
4553        kind = self.sql(expression, "kind")
4554        settings_sql = self.expressions(expression, key="settings", sep=" ")
4555        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4556        return f"{this}({kind}{args})"
4557
4558    def dictrange_sql(self, expression: exp.DictRange) -> str:
4559        this = self.sql(expression, "this")
4560        max = self.sql(expression, "max")
4561        min = self.sql(expression, "min")
4562        return f"{this}(MIN {min} MAX {max})"
4563
4564    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4565        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4566
4567    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4568        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4569
4570    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4571    def uniquekeyproperty_sql(
4572        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4573    ) -> str:
4574        return f"{prefix} ({self.expressions(expression, flat=True)})"
4575
4576    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4577    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4578        expressions = self.expressions(expression, flat=True)
4579        expressions = f" {self.wrap(expressions)}" if expressions else ""
4580        buckets = self.sql(expression, "buckets")
4581        kind = self.sql(expression, "kind")
4582        buckets = f" BUCKETS {buckets}" if buckets else ""
4583        order = self.sql(expression, "order")
4584        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4585
4586    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4587        return ""
4588
4589    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4590        expressions = self.expressions(expression, key="expressions", flat=True)
4591        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4592        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4593        buckets = self.sql(expression, "buckets")
4594        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4595
4596    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4597        this = self.sql(expression, "this")
4598        having = self.sql(expression, "having")
4599
4600        if having:
4601            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4602
4603        return self.func("ANY_VALUE", this)
4604
4605    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4606        transform = self.func("TRANSFORM", *expression.expressions)
4607        row_format_before = self.sql(expression, "row_format_before")
4608        row_format_before = f" {row_format_before}" if row_format_before else ""
4609        record_writer = self.sql(expression, "record_writer")
4610        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4611        using = f" USING {self.sql(expression, 'command_script')}"
4612        schema = self.sql(expression, "schema")
4613        schema = f" AS {schema}" if schema else ""
4614        row_format_after = self.sql(expression, "row_format_after")
4615        row_format_after = f" {row_format_after}" if row_format_after else ""
4616        record_reader = self.sql(expression, "record_reader")
4617        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4618        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4619
4620    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4621        key_block_size = self.sql(expression, "key_block_size")
4622        if key_block_size:
4623            return f"KEY_BLOCK_SIZE = {key_block_size}"
4624
4625        using = self.sql(expression, "using")
4626        if using:
4627            return f"USING {using}"
4628
4629        parser = self.sql(expression, "parser")
4630        if parser:
4631            return f"WITH PARSER {parser}"
4632
4633        comment = self.sql(expression, "comment")
4634        if comment:
4635            return f"COMMENT {comment}"
4636
4637        visible = expression.args.get("visible")
4638        if visible is not None:
4639            return "VISIBLE" if visible else "INVISIBLE"
4640
4641        engine_attr = self.sql(expression, "engine_attr")
4642        if engine_attr:
4643            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4644
4645        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4646        if secondary_engine_attr:
4647            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4648
4649        self.unsupported("Unsupported index constraint option.")
4650        return ""
4651
4652    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4653        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4654        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4655
4656    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4657        kind = self.sql(expression, "kind")
4658        kind = f"{kind} INDEX" if kind else "INDEX"
4659        this = self.sql(expression, "this")
4660        this = f" {this}" if this else ""
4661        index_type = self.sql(expression, "index_type")
4662        index_type = f" USING {index_type}" if index_type else ""
4663        expressions = self.expressions(expression, flat=True)
4664        expressions = f" ({expressions})" if expressions else ""
4665        options = self.expressions(expression, key="options", sep=" ")
4666        options = f" {options}" if options else ""
4667        return f"{kind}{this}{index_type}{expressions}{options}"
4668
4669    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4670        if self.NVL2_SUPPORTED:
4671            return self.function_fallback_sql(expression)
4672
4673        case = exp.Case().when(
4674            expression.this.is_(exp.null()).not_(copy=False),
4675            expression.args["true"],
4676            copy=False,
4677        )
4678        else_cond = expression.args.get("false")
4679        if else_cond:
4680            case.else_(else_cond, copy=False)
4681
4682        return self.sql(case)
4683
4684    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4685        this = self.sql(expression, "this")
4686        expr = self.sql(expression, "expression")
4687        position = self.sql(expression, "position")
4688        position = f", {position}" if position else ""
4689        iterator = self.sql(expression, "iterator")
4690        condition = self.sql(expression, "condition")
4691        condition = f" IF {condition}" if condition else ""
4692        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
4693
4694    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4695        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4696
4697    def opclass_sql(self, expression: exp.Opclass) -> str:
4698        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4699
4700    def _ml_sql(self, expression: exp.Func, name: str) -> str:
4701        model = self.sql(expression, "this")
4702        model = f"MODEL {model}"
4703        expr = expression.expression
4704        if expr:
4705            expr_sql = self.sql(expression, "expression")
4706            expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql
4707        else:
4708            expr_sql = None
4709
4710        parameters = self.sql(expression, "params_struct") or None
4711
4712        return self.func(name, model, expr_sql, parameters)
4713
4714    def predict_sql(self, expression: exp.Predict) -> str:
4715        return self._ml_sql(expression, "PREDICT")
4716
4717    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4718        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4719        return self._ml_sql(expression, name)
4720
4721    def generatetext_sql(self, expression: exp.GenerateText) -> str:
4722        return self._ml_sql(expression, "GENERATE_TEXT")
4723
4724    def generatetable_sql(self, expression: exp.GenerateTable) -> str:
4725        return self._ml_sql(expression, "GENERATE_TABLE")
4726
4727    def generatebool_sql(self, expression: exp.GenerateBool) -> str:
4728        return self._ml_sql(expression, "GENERATE_BOOL")
4729
4730    def generateint_sql(self, expression: exp.GenerateInt) -> str:
4731        return self._ml_sql(expression, "GENERATE_INT")
4732
4733    def generatedouble_sql(self, expression: exp.GenerateDouble) -> str:
4734        return self._ml_sql(expression, "GENERATE_DOUBLE")
4735
4736    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
4737        return self._ml_sql(expression, "TRANSLATE")
4738
4739    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
4740        return self._ml_sql(expression, "FORECAST")
4741
4742    def aiforecast_sql(self, expression: exp.AIForecast) -> str:
4743        this_sql = self.sql(expression, "this")
4744        if isinstance(expression.this, exp.Table):
4745            this_sql = f"TABLE {this_sql}"
4746
4747        return self.func(
4748            "FORECAST",
4749            this_sql,
4750            expression.args.get("data_col"),
4751            expression.args.get("timestamp_col"),
4752            expression.args.get("model"),
4753            expression.args.get("id_cols"),
4754            expression.args.get("horizon"),
4755            expression.args.get("forecast_end_timestamp"),
4756            expression.args.get("confidence_level"),
4757            expression.args.get("output_historical_time_series"),
4758            expression.args.get("context_window"),
4759        )
4760
4761    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4762        this_sql = self.sql(expression, "this")
4763        if isinstance(expression.this, exp.Table):
4764            this_sql = f"TABLE {this_sql}"
4765
4766        return self.func(
4767            "FEATURES_AT_TIME",
4768            this_sql,
4769            expression.args.get("time"),
4770            expression.args.get("num_rows"),
4771            expression.args.get("ignore_feature_nulls"),
4772        )
4773
4774    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
4775        this_sql = self.sql(expression, "this")
4776        if isinstance(expression.this, exp.Table):
4777            this_sql = f"TABLE {this_sql}"
4778
4779        query_table = self.sql(expression, "query_table")
4780        if isinstance(expression.args["query_table"], exp.Table):
4781            query_table = f"TABLE {query_table}"
4782
4783        return self.func(
4784            "VECTOR_SEARCH",
4785            this_sql,
4786            expression.args.get("column_to_search"),
4787            query_table,
4788            expression.args.get("query_column_to_search"),
4789            expression.args.get("top_k"),
4790            expression.args.get("distance_type"),
4791            expression.args.get("options"),
4792        )
4793
4794    def forin_sql(self, expression: exp.ForIn) -> str:
4795        this = self.sql(expression, "this")
4796        expression_sql = self.sql(expression, "expression")
4797        return f"FOR {this} DO {expression_sql}"
4798
4799    def refresh_sql(self, expression: exp.Refresh) -> str:
4800        this = self.sql(expression, "this")
4801        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
4802        return f"REFRESH {kind}{this}"
4803
4804    def toarray_sql(self, expression: exp.ToArray) -> str:
4805        arg = expression.this
4806        if not arg.type:
4807            import sqlglot.optimizer.annotate_types
4808
4809            arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect)
4810
4811        if arg.is_type(exp.DType.ARRAY):
4812            return self.sql(arg)
4813
4814        cond_for_null = arg.is_(exp.null())
4815        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4816
4817    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4818        this = expression.this
4819        time_format = self.format_time(expression)
4820
4821        if time_format:
4822            return self.sql(
4823                exp.cast(
4824                    exp.StrToTime(this=this, format=expression.args["format"]),
4825                    exp.DType.TIME,
4826                )
4827            )
4828
4829        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):
4830            return self.sql(this)
4831
4832        return self.sql(exp.cast(this, exp.DType.TIME))
4833
4834    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4835        this = expression.this
4836        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):
4837            return self.sql(this)
4838
4839        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
4840
4841    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4842        this = expression.this
4843        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):
4844            return self.sql(this)
4845
4846        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
4847
4848    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4849        this = expression.this
4850        time_format = self.format_time(expression)
4851        safe = expression.args.get("safe")
4852        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4853            return self.sql(
4854                exp.cast(
4855                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
4856                    exp.DType.DATE,
4857                )
4858            )
4859
4860        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):
4861            return self.sql(this)
4862
4863        if safe:
4864            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))
4865
4866        return self.sql(exp.cast(this, exp.DType.DATE))
4867
4868    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4869        return self.sql(
4870            exp.func(
4871                "DATEDIFF",
4872                expression.this,
4873                exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
4874                "day",
4875            )
4876        )
4877
4878    def lastday_sql(self, expression: exp.LastDay) -> str:
4879        if self.LAST_DAY_SUPPORTS_DATE_PART:
4880            return self.function_fallback_sql(expression)
4881
4882        unit = expression.text("unit")
4883        if unit and unit != "MONTH":
4884            self.unsupported("Date parts are not supported in LAST_DAY.")
4885
4886        return self.func("LAST_DAY", expression.this)
4887
4888    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4889        import sqlglot.dialects.dialect
4890
4891        return self.func(
4892            "DATE_ADD",
4893            expression.this,
4894            expression.expression,
4895            sqlglot.dialects.dialect.unit_to_str(expression),
4896        )
4897
4898    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4899        if self.CAN_IMPLEMENT_ARRAY_ANY:
4900            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4901            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4902            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4903            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4904
4905        import sqlglot.dialects.dialect
4906
4907        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4908        if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect:
4909            self.unsupported("ARRAY_ANY is unsupported")
4910
4911        return self.function_fallback_sql(expression)
4912
4913    def struct_sql(self, expression: exp.Struct) -> str:
4914        expression.set(
4915            "expressions",
4916            [
4917                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4918                if isinstance(e, exp.PropertyEQ)
4919                else e
4920                for e in expression.expressions
4921            ],
4922        )
4923
4924        return self.function_fallback_sql(expression)
4925
4926    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4927        low = self.sql(expression, "this")
4928        high = self.sql(expression, "expression")
4929
4930        return f"{low} TO {high}"
4931
4932    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4933        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4934        tables = f" {self.expressions(expression)}"
4935
4936        exists = " IF EXISTS" if expression.args.get("exists") else ""
4937
4938        on_cluster = self.sql(expression, "cluster")
4939        on_cluster = f" {on_cluster}" if on_cluster else ""
4940
4941        identity = self.sql(expression, "identity")
4942        identity = f" {identity} IDENTITY" if identity else ""
4943
4944        option = self.sql(expression, "option")
4945        option = f" {option}" if option else ""
4946
4947        partition = self.sql(expression, "partition")
4948        partition = f" {partition}" if partition else ""
4949
4950        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4951
4952    # This transpiles T-SQL's CONVERT function
4953    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4954    def convert_sql(self, expression: exp.Convert) -> str:
4955        to = expression.this
4956        value = expression.expression
4957        style = expression.args.get("style")
4958        safe = expression.args.get("safe")
4959        strict = expression.args.get("strict")
4960
4961        if not to or not value:
4962            return ""
4963
4964        # Retrieve length of datatype and override to default if not specified
4965        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4966            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4967
4968        transformed: exp.Expr | None = None
4969        cast = exp.Cast if strict else exp.TryCast
4970
4971        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4972        if isinstance(style, exp.Literal) and style.is_int:
4973            import sqlglot.dialects.tsql
4974
4975            style_value = style.name
4976            converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4977            if not converted_style:
4978                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4979
4980            fmt = exp.Literal.string(converted_style)
4981
4982            if to.this == exp.DType.DATE:
4983                transformed = exp.StrToDate(this=value, format=fmt)
4984            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):
4985                transformed = exp.StrToTime(this=value, format=fmt)
4986            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4987                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4988            elif to.this == exp.DType.TEXT:
4989                transformed = exp.TimeToStr(this=value, format=fmt)
4990
4991        if not transformed:
4992            transformed = cast(this=value, to=to, safe=safe)
4993
4994        return self.sql(transformed)
4995
4996    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4997        this = expression.this
4998        if isinstance(this, exp.JSONPathWildcard):
4999            this = self.json_path_part(this)
5000            return f".{this}" if this else ""
5001
5002        if self.SAFE_JSON_PATH_KEY_RE.match(this):
5003            return f".{this}"
5004
5005        this = self.json_path_part(this)
5006        return (
5007            f"[{this}]"
5008            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
5009            else f".{this}"
5010        )
5011
5012    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
5013        this = self.json_path_part(expression.this)
5014        return f"[{this}]" if this else ""
5015
5016    def _simplify_unless_literal(self, expression: E) -> E:
5017        if not isinstance(expression, exp.Literal):
5018            import sqlglot.optimizer.simplify
5019
5020            expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect)
5021
5022        return expression
5023
5024    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
5025        this = expression.this
5026        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
5027            self.unsupported(
5028                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
5029            )
5030            return self.sql(this)
5031
5032        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
5033            if self.IGNORE_NULLS_BEFORE_ORDER:
5034                # The first modifier here will be the one closest to the AggFunc's arg
5035                mods = sorted(
5036                    expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
5037                    key=lambda x: (
5038                        0
5039                        if isinstance(x, exp.HavingMax)
5040                        else (1 if isinstance(x, exp.Order) else 2)
5041                    ),
5042                )
5043
5044                if mods:
5045                    mod = mods[0]
5046                    this = expression.__class__(this=mod.this.copy())
5047                    this.meta["inline"] = True
5048                    mod.this.replace(this)
5049                    return self.sql(expression.this)
5050
5051            agg_func = expression.find(exp.AggFunc)
5052
5053            if agg_func:
5054                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
5055                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
5056
5057        return f"{self.sql(expression, 'this')} {text}"
5058
5059    def _replace_line_breaks(self, string: str) -> str:
5060        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
5061        if self.pretty:
5062            return string.replace("\n", self.SENTINEL_LINE_BREAK)
5063        return string
5064
5065    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
5066        option = self.sql(expression, "this")
5067
5068        if expression.expressions:
5069            upper = option.upper()
5070
5071            # Snowflake FILE_FORMAT options are separated by whitespace
5072            sep = " " if upper == "FILE_FORMAT" else ", "
5073
5074            # Databricks copy/format options do not set their list of values with EQ
5075            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
5076            values = self.expressions(expression, flat=True, sep=sep)
5077            return f"{option}{op}({values})"
5078
5079        value = self.sql(expression, "expression")
5080
5081        if not value:
5082            return option
5083
5084        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
5085
5086        return f"{option}{op}{value}"
5087
5088    def credentials_sql(self, expression: exp.Credentials) -> str:
5089        cred_expr = expression.args.get("credentials")
5090        if isinstance(cred_expr, exp.Literal):
5091            # Redshift case: CREDENTIALS <string>
5092            credentials = self.sql(expression, "credentials")
5093            credentials = f"CREDENTIALS {credentials}" if credentials else ""
5094        else:
5095            # Snowflake case: CREDENTIALS = (...)
5096            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
5097            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
5098
5099        storage = self.sql(expression, "storage")
5100        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
5101
5102        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
5103        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
5104
5105        iam_role = self.sql(expression, "iam_role")
5106        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
5107
5108        region = self.sql(expression, "region")
5109        region = f" REGION {region}" if region else ""
5110
5111        return f"{credentials}{storage}{encryption}{iam_role}{region}"
5112
5113    def copy_sql(self, expression: exp.Copy) -> str:
5114        this = self.sql(expression, "this")
5115        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
5116
5117        credentials = self.sql(expression, "credentials")
5118        credentials = self.seg(credentials) if credentials else ""
5119        files = self.expressions(expression, key="files", flat=True)
5120        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
5121
5122        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
5123        params = self.expressions(
5124            expression,
5125            key="params",
5126            sep=sep,
5127            new_line=True,
5128            skip_last=True,
5129            skip_first=True,
5130            indent=self.COPY_PARAMS_ARE_WRAPPED,
5131        )
5132
5133        if params:
5134            if self.COPY_PARAMS_ARE_WRAPPED:
5135                params = f" WITH ({params})"
5136            elif not self.pretty and (files or credentials):
5137                params = f" {params}"
5138
5139        return f"COPY{this}{kind} {files}{credentials}{params}"
5140
5141    def semicolon_sql(self, expression: exp.Semicolon) -> str:
5142        return ""
5143
5144    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
5145        on_sql = "ON" if expression.args.get("on") else "OFF"
5146        filter_col: str | None = self.sql(expression, "filter_column")
5147        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
5148        retention_period: str | None = self.sql(expression, "retention_period")
5149        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
5150
5151        if filter_col or retention_period:
5152            on_sql = self.func("ON", filter_col, retention_period)
5153
5154        return f"DATA_DELETION={on_sql}"
5155
5156    def maskingpolicycolumnconstraint_sql(
5157        self, expression: exp.MaskingPolicyColumnConstraint
5158    ) -> str:
5159        this = self.sql(expression, "this")
5160        expressions = self.expressions(expression, flat=True)
5161        expressions = f" USING ({expressions})" if expressions else ""
5162        return f"MASKING POLICY {this}{expressions}"
5163
5164    def gapfill_sql(self, expression: exp.GapFill) -> str:
5165        this = self.sql(expression, "this")
5166        this = f"TABLE {this}"
5167        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
5168
5169    def scope_resolution(self, rhs: str, scope_name: str) -> str:
5170        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
5171
5172    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
5173        this = self.sql(expression, "this")
5174        expr = expression.expression
5175
5176        if isinstance(expr, exp.Func):
5177            # T-SQL's CLR functions are case sensitive
5178            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
5179        else:
5180            expr = self.sql(expression, "expression")
5181
5182        return self.scope_resolution(expr, this)
5183
5184    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
5185        if self.PARSE_JSON_NAME is None:
5186            return self.sql(expression.this)
5187
5188        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
5189
5190    def rand_sql(self, expression: exp.Rand) -> str:
5191        lower = self.sql(expression, "lower")
5192        upper = self.sql(expression, "upper")
5193
5194        if lower and upper:
5195            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
5196        return self.func("RAND", expression.this)
5197
5198    def changes_sql(self, expression: exp.Changes) -> str:
5199        information = self.sql(expression, "information")
5200        information = f"INFORMATION => {information}"
5201        at_before = self.sql(expression, "at_before")
5202        at_before = f"{self.seg('')}{at_before}" if at_before else ""
5203        end = self.sql(expression, "end")
5204        end = f"{self.seg('')}{end}" if end else ""
5205
5206        return f"CHANGES ({information}){at_before}{end}"
5207
5208    def pad_sql(self, expression: exp.Pad) -> str:
5209        prefix = "L" if expression.args.get("is_left") else "R"
5210
5211        fill_pattern = self.sql(expression, "fill_pattern") or None
5212        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
5213            fill_pattern = "' '"
5214
5215        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
5216
5217    def summarize_sql(self, expression: exp.Summarize) -> str:
5218        table = " TABLE" if expression.args.get("table") else ""
5219        return f"SUMMARIZE{table} {self.sql(expression.this)}"
5220
5221    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5222        generate_series = exp.GenerateSeries(**expression.args)
5223
5224        parent = expression.parent
5225        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5226            parent = parent.parent
5227
5228        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5229            return self.sql(exp.Unnest(expressions=[generate_series]))
5230
5231        if isinstance(parent, exp.Select):
5232            self.unsupported("GenerateSeries projection unnesting is not supported.")
5233
5234        return self.sql(generate_series)
5235
5236    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5237        if self.SUPPORTS_CONVERT_TIMEZONE:
5238            return self.function_fallback_sql(expression)
5239
5240        source_tz = expression.args.get("source_tz")
5241        target_tz = expression.args.get("target_tz")
5242        timestamp = expression.args.get("timestamp")
5243
5244        if source_tz and timestamp:
5245            timestamp = exp.AtTimeZone(
5246                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz
5247            )
5248
5249        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5250
5251        return self.sql(expr)
5252
5253    def json_sql(self, expression: exp.JSON) -> str:
5254        this = self.sql(expression, "this")
5255        this = f" {this}" if this else ""
5256
5257        _with = expression.args.get("with_")
5258
5259        if _with is None:
5260            with_sql = ""
5261        elif not _with:
5262            with_sql = " WITHOUT"
5263        else:
5264            with_sql = " WITH"
5265
5266        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5267
5268        return f"JSON{this}{with_sql}{unique_sql}"
5269
5270    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5271        path = self.sql(expression, "path")
5272        returning = self.sql(expression, "returning")
5273        returning = f" RETURNING {returning}" if returning else ""
5274
5275        on_condition = self.sql(expression, "on_condition")
5276        on_condition = f" {on_condition}" if on_condition else ""
5277
5278        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5279
5280    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:
5281        regexp = " REGEXP" if expression.args.get("regexp") else ""
5282        return f"SKIP{regexp} {self.sql(expression.expression)}"
5283
5284    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5285        else_ = "ELSE " if expression.args.get("else_") else ""
5286        condition = self.sql(expression, "expression")
5287        condition = f"WHEN {condition} THEN " if condition else else_
5288        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5289        return f"{condition}{insert}"
5290
5291    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5292        kind = self.sql(expression, "kind")
5293        expressions = self.seg(self.expressions(expression, sep=" "))
5294        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
5295        return res
5296
5297    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5298        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5299        empty = expression.args.get("empty")
5300        empty = (
5301            f"DEFAULT {empty} ON EMPTY"
5302            if isinstance(empty, exp.Expr)
5303            else self.sql(expression, "empty")
5304        )
5305
5306        error = expression.args.get("error")
5307        error = (
5308            f"DEFAULT {error} ON ERROR"
5309            if isinstance(error, exp.Expr)
5310            else self.sql(expression, "error")
5311        )
5312
5313        if error and empty:
5314            error = (
5315                f"{empty} {error}"
5316                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5317                else f"{error} {empty}"
5318            )
5319            empty = ""
5320
5321        null = self.sql(expression, "null")
5322
5323        return f"{empty}{error}{null}"
5324
5325    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5326        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5327        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5328
5329    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5330        this = self.sql(expression, "this")
5331        path = self.sql(expression, "path")
5332
5333        passing = self.expressions(expression, "passing")
5334        passing = f" PASSING {passing}" if passing else ""
5335
5336        on_condition = self.sql(expression, "on_condition")
5337        on_condition = f" {on_condition}" if on_condition else ""
5338
5339        path = f"{path}{passing}{on_condition}"
5340
5341        return self.func("JSON_EXISTS", this, path)
5342
5343    def _add_arrayagg_null_filter(
5344        self,
5345        array_agg_sql: str,
5346        array_agg_expr: exp.ArrayAgg,
5347        column_expr: exp.Expr,
5348    ) -> str:
5349        """
5350        Add NULL filter to ARRAY_AGG if dialect requires it.
5351
5352        Args:
5353            array_agg_sql: The generated ARRAY_AGG SQL string
5354            array_agg_expr: The ArrayAgg expression node
5355            column_expr: The column/expression to filter (before ORDER BY wrapping)
5356
5357        Returns:
5358            SQL string with FILTER clause added if needed
5359        """
5360        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5361        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5362        if not (
5363            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5364        ):
5365            return array_agg_sql
5366
5367        parent = array_agg_expr.parent
5368        if isinstance(parent, exp.Filter):
5369            parent_cond = parent.expression.this
5370            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5371        elif column_expr.find(exp.Column):
5372            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5373            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5374            this_sql = (
5375                self.expressions(column_expr)
5376                if isinstance(column_expr, exp.Distinct)
5377                else self.sql(column_expr)
5378            )
5379            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5380
5381        return array_agg_sql
5382
5383    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5384        array_agg = self.function_fallback_sql(expression)
5385        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
5386
5387    def slice_sql(self, expression: exp.Slice) -> str:
5388        step = self.sql(expression, "step")
5389        end = self.sql(expression.expression)
5390        begin = self.sql(expression.this)
5391
5392        sql = f"{end}:{step}" if step else end
5393        return f"{begin}:{sql}" if sql else f"{begin}:"
5394
5395    def apply_sql(self, expression: exp.Apply) -> str:
5396        this = self.sql(expression, "this")
5397        expr = self.sql(expression, "expression")
5398
5399        return f"{this} APPLY({expr})"
5400
5401    def _grant_or_revoke_sql(
5402        self,
5403        expression: exp.Grant | exp.Revoke,
5404        keyword: str,
5405        preposition: str,
5406        grant_option_prefix: str = "",
5407        grant_option_suffix: str = "",
5408    ) -> str:
5409        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5410
5411        kind = self.sql(expression, "kind")
5412        kind = f" {kind}" if kind else ""
5413
5414        securable = self.sql(expression, "securable")
5415        securable = f" {securable}" if securable else ""
5416
5417        principals = self.expressions(expression, key="principals", flat=True)
5418
5419        if not expression.args.get("grant_option"):
5420            grant_option_prefix = grant_option_suffix = ""
5421
5422        # cascade for revoke only
5423        cascade = self.sql(expression, "cascade")
5424        cascade = f" {cascade}" if cascade else ""
5425
5426        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5427
5428    def grant_sql(self, expression: exp.Grant) -> str:
5429        return self._grant_or_revoke_sql(
5430            expression,
5431            keyword="GRANT",
5432            preposition="TO",
5433            grant_option_suffix=" WITH GRANT OPTION",
5434        )
5435
5436    def revoke_sql(self, expression: exp.Revoke) -> str:
5437        return self._grant_or_revoke_sql(
5438            expression,
5439            keyword="REVOKE",
5440            preposition="FROM",
5441            grant_option_prefix="GRANT OPTION FOR ",
5442        )
5443
5444    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5445        this = self.sql(expression, "this")
5446        columns = self.expressions(expression, flat=True)
5447        columns = f"({columns})" if columns else ""
5448
5449        return f"{this}{columns}"
5450
5451    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5452        this = self.sql(expression, "this")
5453
5454        kind = self.sql(expression, "kind")
5455        kind = f"{kind} " if kind else ""
5456
5457        return f"{kind}{this}"
5458
5459    def columns_sql(self, expression: exp.Columns) -> str:
5460        func = self.function_fallback_sql(expression)
5461        if expression.args.get("unpack"):
5462            func = f"*{func}"
5463
5464        return func
5465
5466    def overlay_sql(self, expression: exp.Overlay) -> str:
5467        this = self.sql(expression, "this")
5468        expr = self.sql(expression, "expression")
5469        from_sql = self.sql(expression, "from_")
5470        for_sql = self.sql(expression, "for_")
5471        for_sql = f" FOR {for_sql}" if for_sql else ""
5472
5473        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
5474
5475    @unsupported_args("format")
5476    def todouble_sql(self, expression: exp.ToDouble) -> str:
5477        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5478        return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr()))
5479
5480    def string_sql(self, expression: exp.String) -> str:
5481        this = expression.this
5482        zone = expression.args.get("zone")
5483
5484        if zone:
5485            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5486            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5487            # set for source_tz to transpile the time conversion before the STRING cast
5488            this = exp.ConvertTimezone(
5489                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5490            )
5491
5492        return self.sql(exp.cast(this, exp.DType.VARCHAR))
5493
5494    def median_sql(self, expression: exp.Median) -> str:
5495        if not self.SUPPORTS_MEDIAN:
5496            return self.sql(
5497                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5498            )
5499
5500        return self.function_fallback_sql(expression)
5501
5502    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5503        filler = self.sql(expression, "this")
5504        filler = f" {filler}" if filler else ""
5505        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5506        return f"TRUNCATE{filler} {with_count}"
5507
5508    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5509        if self.SUPPORTS_UNIX_SECONDS:
5510            return self.function_fallback_sql(expression)
5511
5512        start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ)
5513
5514        return self.sql(
5515            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5516        )
5517
5518    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5519        dim = expression.expression
5520
5521        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5522        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5523            if not (dim.is_int and dim.name == "1"):
5524                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5525            dim = None
5526
5527        # If dimension is required but not specified, default initialize it
5528        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5529            dim = exp.Literal.number(1)
5530
5531        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5532
5533    def attach_sql(self, expression: exp.Attach) -> str:
5534        this = self.sql(expression, "this")
5535        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5536        expressions = self.expressions(expression)
5537        expressions = f" ({expressions})" if expressions else ""
5538
5539        return f"ATTACH{exists_sql} {this}{expressions}"
5540
5541    def detach_sql(self, expression: exp.Detach) -> str:
5542        kind = self.sql(expression, "kind")
5543        kind = f" {kind}" if kind else ""
5544        # the DATABASE keyword is required if IF EXISTS is set for DuckDB
5545        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5546        exists = " IF EXISTS" if expression.args.get("exists") else ""
5547        if exists:
5548            kind = kind or " DATABASE"
5549
5550        this = self.sql(expression, "this")
5551        this = f" {this}" if this else ""
5552        cluster = self.sql(expression, "cluster")
5553        cluster = f" {cluster}" if cluster else ""
5554        permanent = " PERMANENTLY" if expression.args.get("permanent") else ""
5555        sync = " SYNC" if expression.args.get("sync") else ""
5556        return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
5557
5558    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5559        this = self.sql(expression, "this")
5560        value = self.sql(expression, "expression")
5561        value = f" {value}" if value else ""
5562        return f"{this}{value}"
5563
5564    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5565        return (
5566            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5567        )
5568
5569    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5570        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5571        encode = f"{encode} {self.sql(expression, 'this')}"
5572
5573        properties = expression.args.get("properties")
5574        if properties:
5575            encode = f"{encode} {self.properties(properties)}"
5576
5577        return encode
5578
5579    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5580        this = self.sql(expression, "this")
5581        include = f"INCLUDE {this}"
5582
5583        column_def = self.sql(expression, "column_def")
5584        if column_def:
5585            include = f"{include} {column_def}"
5586
5587        alias = self.sql(expression, "alias")
5588        if alias:
5589            include = f"{include} AS {alias}"
5590
5591        return include
5592
5593    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5594        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
5595        name = f"{prefix} {self.sql(expression, 'this')}"
5596        return self.func("XMLELEMENT", name, *expression.expressions)
5597
5598    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5599        this = self.sql(expression, "this")
5600        expr = self.sql(expression, "expression")
5601        expr = f"({expr})" if expr else ""
5602        return f"{this}{expr}"
5603
5604    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5605        partitions = self.expressions(expression, "partition_expressions")
5606        create = self.expressions(expression, "create_expressions")
5607        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
5608
5609    def partitionbyrangepropertydynamic_sql(
5610        self, expression: exp.PartitionByRangePropertyDynamic
5611    ) -> str:
5612        start = self.sql(expression, "start")
5613        end = self.sql(expression, "end")
5614
5615        every = expression.args["every"]
5616        if isinstance(every, exp.Interval) and every.this.is_string:
5617            every.this.replace(exp.Literal.number(every.name))
5618
5619        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5620
5621    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5622        name = self.sql(expression, "this")
5623        values = self.expressions(expression, flat=True)
5624
5625        return f"NAME {name} VALUE {values}"
5626
5627    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5628        kind = self.sql(expression, "kind")
5629        sample = self.sql(expression, "sample")
5630        return f"SAMPLE {sample} {kind}"
5631
5632    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5633        kind = self.sql(expression, "kind")
5634        option = self.sql(expression, "option")
5635        option = f" {option}" if option else ""
5636        this = self.sql(expression, "this")
5637        this = f" {this}" if this else ""
5638        columns = self.expressions(expression)
5639        columns = f" {columns}" if columns else ""
5640        return f"{kind}{option} STATISTICS{this}{columns}"
5641
5642    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5643        this = self.sql(expression, "this")
5644        columns = self.expressions(expression)
5645        inner_expression = self.sql(expression, "expression")
5646        inner_expression = f" {inner_expression}" if inner_expression else ""
5647        update_options = self.sql(expression, "update_options")
5648        update_options = f" {update_options} UPDATE" if update_options else ""
5649        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
5650
5651    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5652        kind = self.sql(expression, "kind")
5653        kind = f" {kind}" if kind else ""
5654        return f"DELETE{kind} STATISTICS"
5655
5656    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5657        inner_expression = self.sql(expression, "expression")
5658        return f"LIST CHAINED ROWS{inner_expression}"
5659
5660    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5661        kind = self.sql(expression, "kind")
5662        this = self.sql(expression, "this")
5663        this = f" {this}" if this else ""
5664        inner_expression = self.sql(expression, "expression")
5665        return f"VALIDATE {kind}{this}{inner_expression}"
5666
5667    def analyze_sql(self, expression: exp.Analyze) -> str:
5668        options = self.expressions(expression, key="options", sep=" ")
5669        options = f" {options}" if options else ""
5670        kind = self.sql(expression, "kind")
5671        kind = f" {kind}" if kind else ""
5672        this = self.sql(expression, "this")
5673        this = f" {this}" if this else ""
5674        mode = self.sql(expression, "mode")
5675        mode = f" {mode}" if mode else ""
5676        properties = self.sql(expression, "properties")
5677        properties = f" {properties}" if properties else ""
5678        partition = self.sql(expression, "partition")
5679        partition = f" {partition}" if partition else ""
5680        inner_expression = self.sql(expression, "expression")
5681        inner_expression = f" {inner_expression}" if inner_expression else ""
5682        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5683
5684    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5685        this = self.sql(expression, "this")
5686        namespaces = self.expressions(expression, key="namespaces")
5687        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5688        passing = self.expressions(expression, key="passing")
5689        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5690        columns = self.expressions(expression, key="columns")
5691        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5692        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5693        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5694
5695    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5696        this = self.sql(expression, "this")
5697        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
5698
5699    def export_sql(self, expression: exp.Export) -> str:
5700        this = self.sql(expression, "this")
5701        connection = self.sql(expression, "connection")
5702        connection = f"WITH CONNECTION {connection} " if connection else ""
5703        options = self.sql(expression, "options")
5704        return f"EXPORT DATA {connection}{options} AS {this}"
5705
5706    def declare_sql(self, expression: exp.Declare) -> str:
5707        replace = "OR REPLACE " if expression.args.get("replace") else ""
5708        return f"DECLARE {replace}{self.expressions(expression, flat=True)}"
5709
5710    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5711        variables = self.expressions(expression, "this")
5712        default = self.sql(expression, "default")
5713        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
5714
5715        kind = self.sql(expression, "kind")
5716        if isinstance(expression.args.get("kind"), exp.Schema):
5717            kind = f"TABLE {kind}"
5718
5719        kind = f" {kind}" if kind else ""
5720
5721        return f"{variables}{kind}{default}"
5722
5723    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5724        kind = self.sql(expression, "kind")
5725        this = self.sql(expression, "this")
5726        set = self.sql(expression, "expression")
5727        using = self.sql(expression, "using")
5728        using = f" USING {using}" if using else ""
5729
5730        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5731
5732        return f"{kind_sql} {this} SET {set}{using}"
5733
5734    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5735        params = self.expressions(expression, key="params", flat=True)
5736        return self.func(expression.name, *expression.expressions) + f"({params})"
5737
5738    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5739        return self.func(expression.name, *expression.expressions)
5740
5741    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5742        return self.anonymousaggfunc_sql(expression)
5743
5744    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5745        return self.parameterizedagg_sql(expression)
5746
5747    def show_sql(self, expression: exp.Show) -> str:
5748        self.unsupported("Unsupported SHOW statement")
5749        return ""
5750
5751    def install_sql(self, expression: exp.Install) -> str:
5752        self.unsupported("Unsupported INSTALL statement")
5753        return ""
5754
5755    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5756        # Snowflake GET/PUT statements:
5757        #   PUT <file> <internalStage> <properties>
5758        #   GET <internalStage> <file> <properties>
5759        props = expression.args.get("properties")
5760        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5761        this = self.sql(expression, "this")
5762        target = self.sql(expression, "target")
5763
5764        if isinstance(expression, exp.Put):
5765            return f"PUT {this} {target}{props_sql}"
5766        else:
5767            return f"GET {target} {this}{props_sql}"
5768
5769    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
5770        this = self.sql(expression, "this")
5771        expr = self.sql(expression, "expression")
5772        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5773        return f"TRANSLATE({this} USING {expr}{with_error})"
5774
5775    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5776        if self.SUPPORTS_DECODE_CASE:
5777            return self.func("DECODE", *expression.expressions)
5778
5779        decode_expr, *expressions = expression.expressions
5780
5781        ifs = []
5782        for search, result in zip(expressions[::2], expressions[1::2]):
5783            if isinstance(search, exp.Literal):
5784                ifs.append(exp.If(this=decode_expr.eq(search), true=result))
5785            elif isinstance(search, exp.Null):
5786                ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result))
5787            else:
5788                if isinstance(search, exp.Binary):
5789                    search = exp.paren(search)
5790
5791                cond = exp.or_(
5792                    decode_expr.eq(search),
5793                    exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5794                    copy=False,
5795                )
5796                ifs.append(exp.If(this=cond, true=result))
5797
5798        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5799        return self.sql(case)
5800
5801    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5802        this = self.sql(expression, "this")
5803        this = self.seg(this, sep="")
5804        dimensions = self.expressions(
5805            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5806        )
5807        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5808        metrics = self.expressions(
5809            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5810        )
5811        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5812        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
5813        facts = self.seg(f"FACTS {facts}") if facts else ""
5814        where = self.sql(expression, "where")
5815        where = self.seg(f"WHERE {where}") if where else ""
5816        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
5817        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5818
5819    def getextract_sql(self, expression: exp.GetExtract) -> str:
5820        this = expression.this
5821        expr = expression.expression
5822
5823        if not this.type or not expression.type:
5824            import sqlglot.optimizer.annotate_types
5825
5826            this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect)
5827
5828        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):
5829            return self.sql(exp.Bracket(this=this, expressions=[expr]))
5830
5831        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
5832
5833    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
5834        return self.sql(
5835            exp.DateAdd(
5836                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE),
5837                expression=expression.this,
5838                unit=exp.var("DAY"),
5839            )
5840        )
5841
5842    def space_sql(self: Generator, expression: exp.Space) -> str:
5843        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
5844
5845    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
5846        return f"BUILD {self.sql(expression, 'this')}"
5847
5848    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
5849        method = self.sql(expression, "method")
5850        kind = expression.args.get("kind")
5851        if not kind:
5852            return f"REFRESH {method}"
5853
5854        every = self.sql(expression, "every")
5855        unit = self.sql(expression, "unit")
5856        every = f" EVERY {every} {unit}" if every else ""
5857        starts = self.sql(expression, "starts")
5858        starts = f" STARTS {starts}" if starts else ""
5859
5860        return f"REFRESH {method} ON {kind}{every}{starts}"
5861
5862    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
5863        self.unsupported("The model!attribute syntax is not supported")
5864        return ""
5865
5866    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
5867        return self.func("DIRECTORY", expression.this)
5868
5869    def uuid_sql(self, expression: exp.Uuid) -> str:
5870        is_string = expression.args.get("is_string", False)
5871        uuid_func_sql = self.func("UUID")
5872
5873        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
5874            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))
5875
5876        return uuid_func_sql
5877
5878    def initcap_sql(self, expression: exp.Initcap) -> str:
5879        delimiters = expression.expression
5880
5881        if delimiters:
5882            # do not generate delimiters arg if we are round-tripping from default delimiters
5883            if (
5884                delimiters.is_string
5885                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
5886            ):
5887                delimiters = None
5888            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
5889                self.unsupported("INITCAP does not support custom delimiters")
5890                delimiters = None
5891
5892        return self.func("INITCAP", expression.this, delimiters)
5893
5894    def localtime_sql(self, expression: exp.Localtime) -> str:
5895        this = expression.this
5896        return self.func("LOCALTIME", this) if this else "LOCALTIME"
5897
5898    def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str:
5899        this = expression.this
5900        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
5901
5902    def weekstart_sql(self, expression: exp.WeekStart) -> str:
5903        this = expression.this.name.upper()
5904        if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
5905            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
5906            return "WEEK"
5907
5908        return self.func("WEEK", expression.this)
5909
5910    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
5911        this = self.expressions(expression)
5912        charset = self.sql(expression, "charset")
5913        using = f" USING {charset}" if charset else ""
5914        return self.func(name, this + using)
5915
5916    def block_sql(self, expression: exp.Block) -> str:
5917        expressions = self.expressions(expression, sep="; ", flat=True)
5918        return f"{expressions}" if expressions else ""
5919
5920    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
5921        self.unsupported("Unsupported Stored Procedure syntax")
5922        return ""
5923
5924    def ifblock_sql(self, expression: exp.IfBlock) -> str:
5925        self.unsupported("Unsupported If block syntax")
5926        return ""
5927
5928    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
5929        self.unsupported("Unsupported While block syntax")
5930        return ""
5931
5932    def execute_sql(self, expression: exp.Execute) -> str:
5933        self.unsupported("Unsupported Execute syntax")
5934        return ""
5935
5936    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
5937        self.unsupported("Unsupported Execute syntax")
5938        return ""
5939
5940    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:
5941        props = self.expressions(expression, sep=" ")
5942        return f"MODIFY {props}"
5943
5944    def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
5945        kind = expression.args.get("kind")
5946        return f"USING {kind} {self.sql(expression, 'this')}"
5947
5948    def renameindex_sql(self, expression: exp.RenameIndex) -> str:
5949        this = self.sql(expression, "this")
5950        to = self.sql(expression, "to")
5951        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)
845    def __init__(
846        self,
847        pretty: bool | int | None = None,
848        identify: str | bool = False,
849        normalize: bool = False,
850        pad: int = 2,
851        indent: int = 2,
852        normalize_functions: str | bool | None = None,
853        unsupported_level: ErrorLevel = ErrorLevel.WARN,
854        max_unsupported: int = 3,
855        leading_comma: bool = False,
856        max_text_width: int = 80,
857        comments: bool = True,
858        dialect: DialectType = None,
859    ):
860        import sqlglot
861        import sqlglot.dialects.dialect
862
863        self.pretty = pretty if pretty is not None else sqlglot.pretty
864        self.identify = identify
865        self.normalize = normalize
866        self.pad = pad
867        self._indent = indent
868        self.unsupported_level = unsupported_level
869        self.max_unsupported = max_unsupported
870        self.leading_comma = leading_comma
871        self.max_text_width = max_text_width
872        self.comments = comments
873        self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect)
874
875        # This is both a Dialect property and a Generator argument, so we prioritize the latter
876        self.normalize_functions = (
877            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
878        )
879
880        self.unsupported_messages: list[str] = []
881        self._escaped_quote_end: str = (
882            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
883        )
884        self._escaped_byte_quote_end: str = (
885            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
886            if self.dialect.BYTE_END
887            else ""
888        )
889        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
890
891        self._next_name = name_sequence("_t")
892
893        self._identifier_start = self.dialect.IDENTIFIER_START
894        self._identifier_end = self.dialect.IDENTIFIER_END
895
896        self._quote_json_path_key_using_brackets = True
897
898        cls = type(self)
899        dispatch = _DISPATCH_CACHE.get(cls)
900        if dispatch is None:
901            dispatch = _build_dispatch(cls)
902            _DISPATCH_CACHE[cls] = dispatch
903        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
SUPPORTS_MODIFY_COLUMN = False
LIKE_PROPERTY_INSIDE_SCHEMA = False
MULTI_ARG_DISTINCT = True
JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
JSON_PATH_BRACKETED_KEY_SUPPORTED = True
JSON_PATH_SINGLE_QUOTE_ESCAPE = False
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.NVARCHAR: 'NVARCHAR'>, <DType.CHAR: 'CHAR'>, <DType.NCHAR: 'NCHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: ClassVar[set[type[sqlglot.expressions.core.Expr]]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] = ()
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:
905    def generate(self, expression: exp.Expr, copy: bool = True) -> str:
906        """
907        Generates the SQL string corresponding to the given syntax tree.
908
909        Args:
910            expression: The syntax tree.
911            copy: Whether to copy the expression. The generator performs mutations so
912                it is safer to copy.
913
914        Returns:
915            The SQL string corresponding to `expression`.
916        """
917        if copy:
918            expression = expression.copy()
919
920        expression = self.preprocess(expression)
921
922        self.unsupported_messages = []
923        sql = self.sql(expression).strip()
924
925        if self.pretty:
926            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
927
928        if self.unsupported_level == ErrorLevel.IGNORE:
929            return sql
930
931        if self.unsupported_level == ErrorLevel.WARN:
932            for msg in self.unsupported_messages:
933                logger.warning(msg)
934        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
935            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
936
937        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:
939    def preprocess(self, expression: exp.Expr) -> exp.Expr:
940        """Apply generic preprocessing transformations to a given expression."""
941        expression = self._move_ctes_to_top_level(expression)
942
943        if self.ENSURE_BOOLS:
944            import sqlglot.transforms
945
946            expression = sqlglot.transforms.ensure_bools(expression)
947
948        return expression

Apply generic preprocessing transformations to a given expression.

def unsupported(self, message: str) -> None:
961    def unsupported(self, message: str) -> None:
962        if self.unsupported_level == ErrorLevel.IMMEDIATE:
963            raise UnsupportedError(message)
964        self.unsupported_messages.append(message)
def sep(self, sep: str = ' ') -> str:
966    def sep(self, sep: str = " ") -> str:
967        return f"{sep.strip()}\n" if self.pretty else sep
def seg(self, sql: str, sep: str = ' ') -> str:
969    def seg(self, sql: str, sep: str = " ") -> str:
970        return f"{self.sep(sep)}{sql}"
def sanitize_comment(self, comment: str) -> str:
972    def sanitize_comment(self, comment: str) -> str:
973        comment = " " + comment if comment[0].strip() else comment
974        comment = comment + " " if comment[-1].strip() else comment
975
976        # Escape block comment markers to prevent premature closure or unintended nesting.
977        # This is necessary because single-line comments (--) are converted to block comments
978        # (/* */) on output, and any */ in the original text would close the comment early.
979        comment = comment.replace("*/", "* /").replace("/*", "/ *")
980
981        return comment
def maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
 983    def maybe_comment(
 984        self,
 985        sql: str,
 986        expression: exp.Expr | None = None,
 987        comments: list[str] | None = None,
 988        separated: bool = False,
 989    ) -> str:
 990        comments = (
 991            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 992            if self.comments
 993            else None
 994        )
 995
 996        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 997            return sql
 998
 999        comments_sql = " ".join(
1000            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
1001        )
1002
1003        if not comments_sql:
1004            return sql
1005
1006        comments_sql = self._replace_line_breaks(comments_sql)
1007
1008        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
1009            return (
1010                f"{self.sep()}{comments_sql}{sql}"
1011                if not sql or sql[0].isspace()
1012                else f"{comments_sql}{self.sep()}{sql}"
1013            )
1014
1015        return f"{sql} {comments_sql}"
def wrap(self, expression: sqlglot.expressions.core.Expr | str) -> str:
1017    def wrap(self, expression: exp.Expr | str) -> str:
1018        this_sql = (
1019            self.sql(expression)
1020            if isinstance(expression, exp.UNWRAPPED_QUERIES)
1021            else self.sql(expression, "this")
1022        )
1023        if not this_sql:
1024            return "()"
1025
1026        this_sql = self.indent(this_sql, level=1, pad=0)
1027        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def no_identify(self, func: Callable[..., str], *args, **kwargs) -> str:
1029    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
1030        original = self.identify
1031        self.identify = False
1032        result = func(*args, **kwargs)
1033        self.identify = original
1034        return result
def normalize_func(self, name: str) -> str:
1036    def normalize_func(self, name: str) -> str:
1037        if self.normalize_functions == "upper" or self.normalize_functions is True:
1038            return name.upper()
1039        if self.normalize_functions == "lower":
1040            return name.lower()
1041        return name
def indent( self, sql: str, level: int = 0, pad: int | None = None, skip_first: bool = False, skip_last: bool = False) -> str:
1043    def indent(
1044        self,
1045        sql: str,
1046        level: int = 0,
1047        pad: int | None = None,
1048        skip_first: bool = False,
1049        skip_last: bool = False,
1050    ) -> str:
1051        if not self.pretty or not sql:
1052            return sql
1053
1054        pad = self.pad if pad is None else pad
1055        lines = sql.split("\n")
1056
1057        return "\n".join(
1058            (
1059                line
1060                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
1061                else f"{' ' * (level * self._indent + pad)}{line}"
1062            )
1063            for i, line in enumerate(lines)
1064        )
def sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1066    def sql(
1067        self,
1068        expression: str | exp.Expr | None,
1069        key: str | None = None,
1070        comment: bool = True,
1071    ) -> str:
1072        if not expression:
1073            return ""
1074
1075        if isinstance(expression, str):
1076            return expression
1077
1078        if key:
1079            value = expression.args.get(key)
1080            if value:
1081                return self.sql(value)
1082            return ""
1083
1084        handler = self._dispatch.get(expression.__class__)
1085
1086        if handler:
1087            sql = handler(self, expression)
1088        elif isinstance(expression, exp.Func):
1089            sql = self.function_fallback_sql(expression)
1090        elif isinstance(expression, exp.Property):
1091            sql = self.property_sql(expression)
1092        else:
1093            raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1094
1095        return self.maybe_comment(sql, expression) if self.comments and comment else sql
def uncache_sql(self, expression: sqlglot.expressions.core.Uncache) -> str:
1097    def uncache_sql(self, expression: exp.Uncache) -> str:
1098        table = self.sql(expression, "this")
1099        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1100        return f"UNCACHE TABLE{exists_sql} {table}"
def cache_sql(self, expression: sqlglot.expressions.core.Cache) -> str:
1102    def cache_sql(self, expression: exp.Cache) -> str:
1103        lazy = " LAZY" if expression.args.get("lazy") else ""
1104        table = self.sql(expression, "this")
1105        options = expression.args.get("options")
1106        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1107        sql = self.sql(expression, "expression")
1108        sql = f" AS{self.sep()}{sql}" if sql else ""
1109        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1110        return self.prepend_ctes(expression, sql)
def characterset_sql(self, expression: sqlglot.expressions.ddl.CharacterSet) -> str:
1112    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1113        default = "DEFAULT " if expression.args.get("default") else ""
1114        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
def column_parts(self, expression: sqlglot.expressions.core.Column) -> str:
1116    def column_parts(self, expression: exp.Column) -> str:
1117        return ".".join(
1118            self.sql(part)
1119            for part in (
1120                expression.args.get("catalog"),
1121                expression.args.get("db"),
1122                expression.args.get("table"),
1123                expression.args.get("this"),
1124            )
1125            if part
1126        )
def column_sql(self, expression: sqlglot.expressions.core.Column) -> str:
1128    def column_sql(self, expression: exp.Column) -> str:
1129        join_mark = " (+)" if expression.args.get("join_mark") else ""
1130
1131        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1132            join_mark = ""
1133            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1134
1135        return f"{self.column_parts(expression)}{join_mark}"
def pseudocolumn_sql(self, expression: sqlglot.expressions.core.Pseudocolumn) -> str:
1137    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1138        return self.column_sql(expression)
def columnposition_sql(self, expression: sqlglot.expressions.query.ColumnPosition) -> str:
1140    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1141        this = self.sql(expression, "this")
1142        this = f" {this}" if this else ""
1143        position = self.sql(expression, "position")
1144        return f"{position}{this}"
def columndef_sql( self, expression: sqlglot.expressions.query.ColumnDef, sep: str = ' ') -> str:
1146    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1147        column = self.sql(expression, "this")
1148        kind = self.sql(expression, "kind")
1149        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1150        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1151        kind = f"{sep}{kind}" if kind else ""
1152        constraints = f" {constraints}" if constraints else ""
1153        position = self.sql(expression, "position")
1154        position = f" {position}" if position else ""
1155
1156        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1157            kind = ""
1158
1159        return f"{exists}{column}{kind}{constraints}{position}"
def columnconstraint_sql( self, expression: sqlglot.expressions.constraints.ColumnConstraint) -> str:
1161    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1162        this = self.sql(expression, "this")
1163        kind_sql = self.sql(expression, "kind").strip()
1164        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
def computedcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.ComputedColumnConstraint) -> str:
1166    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1167        this = self.sql(expression, "this")
1168        if expression.args.get("not_null"):
1169            persisted = " PERSISTED NOT NULL"
1170        elif expression.args.get("persisted"):
1171            persisted = " PERSISTED"
1172        else:
1173            persisted = ""
1174
1175        return f"AS {this}{persisted}"
def autoincrementcolumnconstraint_sql( self, _: sqlglot.expressions.constraints.AutoIncrementColumnConstraint) -> str:
1177    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1178        return self.token_sql(TokenType.AUTO_INCREMENT)
def compresscolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CompressColumnConstraint) -> str:
1180    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1181        if isinstance(expression.this, list):
1182            this = self.wrap(self.expressions(expression, key="this", flat=True))
1183        else:
1184            this = self.sql(expression, "this")
1185
1186        return f"COMPRESS {this}"
def generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsIdentityColumnConstraint) -> str:
1188    def generatedasidentitycolumnconstraint_sql(
1189        self, expression: exp.GeneratedAsIdentityColumnConstraint
1190    ) -> str:
1191        this = ""
1192        if expression.this is not None:
1193            on_null = " ON NULL" if expression.args.get("on_null") else ""
1194            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1195
1196        start = expression.args.get("start")
1197        start = f"START WITH {start}" if start else ""
1198        increment = expression.args.get("increment")
1199        increment = f" INCREMENT BY {increment}" if increment else ""
1200        minvalue = expression.args.get("minvalue")
1201        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1202        maxvalue = expression.args.get("maxvalue")
1203        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1204        cycle = expression.args.get("cycle")
1205        cycle_sql = ""
1206
1207        if cycle is not None:
1208            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1209            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1210
1211        sequence_opts = ""
1212        if start or increment or cycle_sql:
1213            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1214            sequence_opts = f" ({sequence_opts.strip()})"
1215
1216        expr = self.sql(expression, "expression")
1217        expr = f"({expr})" if expr else "IDENTITY"
1218
1219        return f"GENERATED{this} AS {expr}{sequence_opts}"
def generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1221    def generatedasrowcolumnconstraint_sql(
1222        self, expression: exp.GeneratedAsRowColumnConstraint
1223    ) -> str:
1224        start = "START" if expression.args.get("start") else "END"
1225        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1226        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
def periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.constraints.PeriodForSystemTimeConstraint) -> str:
1228    def periodforsystemtimeconstraint_sql(
1229        self, expression: exp.PeriodForSystemTimeConstraint
1230    ) -> str:
1231        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
def notnullcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.NotNullColumnConstraint) -> str:
1233    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1234        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
def primarykeycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.PrimaryKeyColumnConstraint) -> str:
1236    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1237        desc = expression.args.get("desc")
1238        if desc is not None:
1239            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1240        options = self.expressions(expression, key="options", flat=True, sep=" ")
1241        options = f" {options}" if options else ""
1242        return f"PRIMARY KEY{options}"
def uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1244    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1245        this = self.sql(expression, "this")
1246        this = f" {this}" if this else ""
1247        index_type = expression.args.get("index_type")
1248        index_type = f" USING {index_type}" if index_type else ""
1249        on_conflict = self.sql(expression, "on_conflict")
1250        on_conflict = f" {on_conflict}" if on_conflict else ""
1251        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1252        options = self.expressions(expression, key="options", flat=True, sep=" ")
1253        options = f" {options}" if options else ""
1254        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1256    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1257        input_ = expression.args.get("input_")
1258        output = expression.args.get("output")
1259        variadic = expression.args.get("variadic")
1260
1261        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1262        if variadic:
1263            return "VARIADIC"
1264
1265        if input_ and output:
1266            return f"IN{self.INOUT_SEPARATOR}OUT"
1267        if input_:
1268            return "IN"
1269        if output:
1270            return "OUT"
1271
1272        return ""
def createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1274    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
1275        return self.sql(expression, "this")
def create_sql(self, expression: sqlglot.expressions.ddl.Create) -> str:
1277    def create_sql(self, expression: exp.Create) -> str:
1278        kind = self.sql(expression, "kind")
1279        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1280
1281        properties = expression.args.get("properties")
1282
1283        if (
1284            kind == "TRIGGER"
1285            and properties
1286            and properties.expressions
1287            and isinstance(properties.expressions[0], exp.TriggerProperties)
1288            and properties.expressions[0].args.get("constraint")
1289        ):
1290            kind = f"CONSTRAINT {kind}"
1291
1292        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1293
1294        this = self.createable_sql(expression, properties_locs)
1295
1296        properties_sql = ""
1297        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1298            exp.Properties.Location.POST_WITH
1299        ):
1300            props_ast = exp.Properties(
1301                expressions=[
1302                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1303                    *properties_locs[exp.Properties.Location.POST_WITH],
1304                ]
1305            )
1306            props_ast.parent = expression
1307            properties_sql = self.sql(props_ast)
1308
1309            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1310                properties_sql = self.sep() + properties_sql
1311            elif not self.pretty:
1312                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1313                properties_sql = f" {properties_sql}"
1314
1315        begin = " BEGIN" if expression.args.get("begin") else ""
1316
1317        expression_sql = self.sql(expression, "expression")
1318        if expression_sql:
1319            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1320
1321            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1322                postalias_props_sql = ""
1323                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1324                    postalias_props_sql = self.properties(
1325                        exp.Properties(
1326                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1327                        ),
1328                        wrapped=False,
1329                    )
1330                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1331                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1332
1333        postindex_props_sql = ""
1334        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1335            postindex_props_sql = self.properties(
1336                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1337                wrapped=False,
1338                prefix=" ",
1339            )
1340
1341        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1342        indexes = f" {indexes}" if indexes else ""
1343        index_sql = indexes + postindex_props_sql
1344
1345        replace = " OR REPLACE" if expression.args.get("replace") else ""
1346        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1347        unique = " UNIQUE" if expression.args.get("unique") else ""
1348
1349        clustered = expression.args.get("clustered")
1350        if clustered is None:
1351            clustered_sql = ""
1352        elif clustered:
1353            clustered_sql = " CLUSTERED COLUMNSTORE"
1354        else:
1355            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1356
1357        postcreate_props_sql = ""
1358        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1359            postcreate_props_sql = self.properties(
1360                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1361                sep=" ",
1362                prefix=" ",
1363                wrapped=False,
1364            )
1365
1366        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1367
1368        postexpression_props_sql = ""
1369        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1370            postexpression_props_sql = self.properties(
1371                exp.Properties(
1372                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1373                ),
1374                sep=" ",
1375                prefix=" ",
1376                wrapped=False,
1377            )
1378
1379        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1380        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1381        no_schema_binding = (
1382            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1383        )
1384
1385        clone = self.sql(expression, "clone")
1386        clone = f" {clone}" if clone else ""
1387
1388        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1389            properties_expression = f"{expression_sql}{properties_sql}"
1390        else:
1391            properties_expression = f"{properties_sql}{expression_sql}"
1392
1393        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1394        return self.prepend_ctes(expression, expression_sql)
def sequenceproperties_sql(self, expression: sqlglot.expressions.ddl.SequenceProperties) -> str:
1396    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1397        start = self.sql(expression, "start")
1398        start = f"START WITH {start}" if start else ""
1399        increment = self.sql(expression, "increment")
1400        increment = f" INCREMENT BY {increment}" if increment else ""
1401        minvalue = self.sql(expression, "minvalue")
1402        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1403        maxvalue = self.sql(expression, "maxvalue")
1404        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1405        owned = self.sql(expression, "owned")
1406        owned = f" OWNED BY {owned}" if owned else ""
1407
1408        cache = expression.args.get("cache")
1409        if cache is None:
1410            cache_str = ""
1411        elif cache is True:
1412            cache_str = " CACHE"
1413        else:
1414            cache_str = f" CACHE {cache}"
1415
1416        options = self.expressions(expression, key="options", flat=True, sep=" ")
1417        options = f" {options}" if options else ""
1418
1419        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
def triggerproperties_sql(self, expression: sqlglot.expressions.ddl.TriggerProperties) -> str:
1421    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1422        timing = expression.args.get("timing", "")
1423        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1424        timing_events = f"{timing} {events}".strip() if timing or events else ""
1425
1426        parts = [timing_events, "ON", self.sql(expression, "table")]
1427
1428        if referenced_table := expression.args.get("referenced_table"):
1429            parts.extend(["FROM", self.sql(referenced_table)])
1430
1431        if deferrable := expression.args.get("deferrable"):
1432            parts.append(deferrable)
1433
1434        if initially := expression.args.get("initially"):
1435            parts.append(f"INITIALLY {initially}")
1436
1437        if referencing := expression.args.get("referencing"):
1438            parts.append(self.sql(referencing))
1439
1440        if for_each := expression.args.get("for_each"):
1441            parts.append(f"FOR EACH {for_each}")
1442
1443        if when := expression.args.get("when"):
1444            parts.append(f"WHEN ({self.sql(when)})")
1445
1446        parts.append(self.sql(expression, "execute"))
1447
1448        return self.sep().join(parts)
def triggerreferencing_sql(self, expression: sqlglot.expressions.ddl.TriggerReferencing) -> str:
1450    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1451        parts = []
1452
1453        if old_alias := expression.args.get("old"):
1454            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1455
1456        if new_alias := expression.args.get("new"):
1457            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1458
1459        return f"REFERENCING {' '.join(parts)}"
def triggerevent_sql(self, expression: sqlglot.expressions.ddl.TriggerEvent) -> str:
1461    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1462        columns = expression.args.get("columns")
1463        if columns:
1464            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1465
1466        return self.sql(expression, "this")
def clone_sql(self, expression: sqlglot.expressions.ddl.Clone) -> str:
1468    def clone_sql(self, expression: exp.Clone) -> str:
1469        this = self.sql(expression, "this")
1470        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1471        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1472        return f"{shallow}{keyword} {this}"
def describe_sql(self, expression: sqlglot.expressions.ddl.Describe) -> str:
1474    def describe_sql(self, expression: exp.Describe) -> str:
1475        style = expression.args.get("style")
1476        style = f" {style}" if style else ""
1477        partition = self.sql(expression, "partition")
1478        partition = f" {partition}" if partition else ""
1479        format = self.sql(expression, "format")
1480        format = f" {format}" if format else ""
1481        as_json = " AS JSON" if expression.args.get("as_json") else ""
1482
1483        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
def heredoc_sql(self, expression: sqlglot.expressions.ddl.Heredoc) -> str:
1485    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1486        tag = self.sql(expression, "tag")
1487        return f"${tag}${self.sql(expression, 'this')}${tag}$"
def prepend_ctes(self, expression: sqlglot.expressions.core.Expr, sql: str) -> str:
1489    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:
1490        with_ = self.sql(expression, "with_")
1491        if with_:
1492            sql = f"{with_}{self.sep()}{sql}"
1493        return sql
def with_sql(self, expression: sqlglot.expressions.query.With) -> str:
1495    def with_sql(self, expression: exp.With) -> str:
1496        sql = self.expressions(expression, flat=True)
1497        recursive = (
1498            "RECURSIVE "
1499            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1500            else ""
1501        )
1502        search = self.sql(expression, "search")
1503        search = f" {search}" if search else ""
1504
1505        return f"WITH {recursive}{sql}{search}"
def cte_sql(self, expression: sqlglot.expressions.query.CTE) -> str:
1507    def cte_sql(self, expression: exp.CTE) -> str:
1508        alias = expression.args.get("alias")
1509        if alias:
1510            alias.add_comments(expression.pop_comments())
1511
1512        alias_sql = self.sql(expression, "alias")
1513
1514        materialized = expression.args.get("materialized")
1515        if materialized is False:
1516            materialized = "NOT MATERIALIZED "
1517        elif materialized:
1518            materialized = "MATERIALIZED "
1519
1520        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1521        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1522
1523        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
def tablealias_sql(self, expression: sqlglot.expressions.query.TableAlias) -> str:
1525    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1526        alias = self.sql(expression, "this")
1527        columns = self.expressions(expression, key="columns", flat=True)
1528        columns = f"({columns})" if columns else ""
1529
1530        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1531            columns = ""
1532            self.unsupported("Named columns are not supported in table alias.")
1533
1534        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1535            alias = self._next_name()
1536
1537        return f"{alias}{columns}"
def bitstring_sql(self, expression: sqlglot.expressions.query.BitString) -> str:
1539    def bitstring_sql(self, expression: exp.BitString) -> str:
1540        this = self.sql(expression, "this")
1541        if self.dialect.BIT_START:
1542            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1543        return f"{int(this, 2)}"
def hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1545    def hexstring_sql(
1546        self, expression: exp.HexString, binary_function_repr: str | None = None
1547    ) -> str:
1548        this = self.sql(expression, "this")
1549        is_integer_type = expression.args.get("is_integer")
1550
1551        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1552            not self.dialect.HEX_START and not binary_function_repr
1553        ):
1554            # Integer representation will be returned if:
1555            # - The read dialect treats the hex value as integer literal but not the write
1556            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1557            return f"{int(this, 16)}"
1558
1559        if not is_integer_type:
1560            # Read dialect treats the hex value as BINARY/BLOB
1561            if binary_function_repr:
1562                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1563                return self.func(binary_function_repr, exp.Literal.string(this))
1564            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1565                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1566                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1567
1568        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
def bytestring_sql(self, expression: sqlglot.expressions.query.ByteString) -> str:
1570    def bytestring_sql(self, expression: exp.ByteString) -> str:
1571        this = self.sql(expression, "this")
1572        if self.dialect.BYTE_START:
1573            escaped_byte_string = self.escape_str(
1574                this,
1575                escape_backslash=False,
1576                delimiter=self.dialect.BYTE_END,
1577                escaped_delimiter=self._escaped_byte_quote_end,
1578                is_byte_string=True,
1579            )
1580            is_bytes = expression.args.get("is_bytes", False)
1581            delimited_byte_string = (
1582                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1583            )
1584            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1585                return self.sql(
1586                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)
1587                )
1588            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1589                return self.sql(
1590                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)
1591                )
1592
1593            return delimited_byte_string
1594        return this
def unicodestring_sql(self, expression: sqlglot.expressions.query.UnicodeString) -> str:
1596    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1597        this = self.sql(expression, "this")
1598        escape = expression.args.get("escape")
1599
1600        if self.dialect.UNICODE_START:
1601            escape_substitute = r"\\\1"
1602            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1603        else:
1604            escape_substitute = r"\\u\1"
1605            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1606
1607        if escape:
1608            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1609            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1610        else:
1611            escape_pattern = ESCAPED_UNICODE_RE
1612            escape_sql = ""
1613
1614        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1615            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1616
1617        return f"{left_quote}{this}{right_quote}{escape_sql}"
def rawstring_sql(self, expression: sqlglot.expressions.query.RawString) -> str:
1619    def rawstring_sql(self, expression: exp.RawString) -> str:
1620        string = expression.this
1621        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1622            string = string.replace("\\", "\\\\")
1623
1624        string = self.escape_str(string, escape_backslash=False)
1625        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
def datatypeparam_sql(self, expression: sqlglot.expressions.datatypes.DataTypeParam) -> str:
1627    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1628        this = self.sql(expression, "this")
1629        specifier = self.sql(expression, "expression")
1630        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1631        return f"{this}{specifier}"
def datatype_sql(self, expression: sqlglot.expressions.datatypes.DataType) -> str:
1633    def datatype_sql(self, expression: exp.DataType) -> str:
1634        nested = ""
1635        values = ""
1636
1637        expr_nested = expression.args.get("nested")
1638        interior = (
1639            self.expressions(
1640                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1641            )
1642            if expr_nested and self.pretty
1643            else self.expressions(expression, flat=True)
1644        )
1645
1646        type_value = expression.this
1647        if type_value in self.UNSUPPORTED_TYPES:
1648            self.unsupported(
1649                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1650            )
1651
1652        type_sql: t.Any = ""
1653        if type_value == exp.DType.USERDEFINED and expression.args.get("kind"):
1654            type_sql = self.sql(expression, "kind")
1655        elif type_value == exp.DType.CHARACTER_SET:
1656            return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}"
1657        else:
1658            type_sql = (
1659                self.TYPE_MAPPING.get(type_value, type_value.value)
1660                if isinstance(type_value, exp.DType)
1661                else type_value
1662            )
1663
1664        if interior:
1665            if expr_nested:
1666                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1667                if expression.args.get("values") is not None:
1668                    delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")")
1669                    values = self.expressions(expression, key="values", flat=True)
1670                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1671            elif type_value == exp.DType.INTERVAL:
1672                nested = f" {interior}"
1673            else:
1674                nested = f"({interior})"
1675
1676        type_sql = f"{type_sql}{nested}{values}"
1677        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1678            exp.DType.TIMETZ,
1679            exp.DType.TIMESTAMPTZ,
1680        ):
1681            type_sql = f"{type_sql} WITH TIME ZONE"
1682
1683        return type_sql
def directory_sql(self, expression: sqlglot.expressions.dml.Directory) -> str:
1685    def directory_sql(self, expression: exp.Directory) -> str:
1686        local = "LOCAL " if expression.args.get("local") else ""
1687        row_format = self.sql(expression, "row_format")
1688        row_format = f" {row_format}" if row_format else ""
1689        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
def delete_sql(self, expression: sqlglot.expressions.dml.Delete) -> str:
1691    def delete_sql(self, expression: exp.Delete) -> str:
1692        hint = self.sql(expression, "hint")
1693        this = self.sql(expression, "this")
1694        this = f" FROM {this}" if this else ""
1695        using = self.expressions(expression, key="using")
1696        using = f" USING {using}" if using else ""
1697        cluster = self.sql(expression, "cluster")
1698        cluster = f" {cluster}" if cluster else ""
1699        where = self.sql(expression, "where")
1700        returning = self.sql(expression, "returning")
1701        order = self.sql(expression, "order")
1702        limit = self.sql(expression, "limit")
1703        tables = self.expressions(expression, key="tables")
1704        tables = f" {tables}" if tables else ""
1705        if self.RETURNING_END:
1706            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1707        else:
1708            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1709        return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
def drop_sql(self, expression: sqlglot.expressions.ddl.Drop) -> str:
1711    def drop_sql(self, expression: exp.Drop) -> str:
1712        this = self.sql(expression, "this")
1713        expressions = self.expressions(expression, flat=True)
1714        expressions = f" ({expressions})" if expressions else ""
1715        kind = expression.args["kind"]
1716        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1717        iceberg = (
1718            " ICEBERG"
1719            if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
1720            else ""
1721        )
1722        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1723        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1724        on_cluster = self.sql(expression, "cluster")
1725        on_cluster = f" {on_cluster}" if on_cluster else ""
1726        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1727        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1728        cascade = " CASCADE" if expression.args.get("cascade") else ""
1729        restrict = " RESTRICT" if expression.args.get("restrict") else ""
1730        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1731        purge = " PURGE" if expression.args.get("purge") else ""
1732        sync = " SYNC" if expression.args.get("sync") else ""
1733        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:
1735    def set_operation(self, expression: exp.SetOperation) -> str:
1736        op_type = type(expression)
1737        op_name = op_type.key.upper()
1738
1739        distinct = expression.args.get("distinct")
1740        if (
1741            distinct is False
1742            and op_type in (exp.Except, exp.Intersect)
1743            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1744        ):
1745            self.unsupported(f"{op_name} ALL is not supported")
1746
1747        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1748
1749        if distinct is None:
1750            distinct = default_distinct
1751            if distinct is None:
1752                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1753
1754        if distinct is default_distinct:
1755            distinct_or_all = ""
1756        else:
1757            distinct_or_all = " DISTINCT" if distinct else " ALL"
1758
1759        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1760        side_kind = f"{side_kind} " if side_kind else ""
1761
1762        by_name = " BY NAME" if expression.args.get("by_name") else ""
1763        on = self.expressions(expression, key="on", flat=True)
1764        on = f" ON ({on})" if on else ""
1765
1766        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
def set_operations(self, expression: sqlglot.expressions.query.SetOperation) -> str:
1768    def set_operations(self, expression: exp.SetOperation) -> str:
1769        if not self.SET_OP_MODIFIERS:
1770            limit = expression.args.get("limit")
1771            order = expression.args.get("order")
1772
1773            if limit or order:
1774                select = self._move_ctes_to_top_level(
1775                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1776                )
1777
1778                if limit:
1779                    select = select.limit(limit.pop(), copy=False)
1780                if order:
1781                    select = select.order_by(order.pop(), copy=False)
1782                return self.sql(select)
1783
1784        sqls: list[str] = []
1785        stack: list[str | exp.Expr] = [expression]
1786
1787        while stack:
1788            node = stack.pop()
1789
1790            if isinstance(node, exp.SetOperation):
1791                stack.append(node.expression)
1792                stack.append(
1793                    self.maybe_comment(
1794                        self.set_operation(node), comments=node.comments, separated=True
1795                    )
1796                )
1797                stack.append(node.this)
1798            else:
1799                sqls.append(self.sql(node))
1800
1801        this = self.sep().join(sqls)
1802        this = self.query_modifiers(expression, this)
1803        return self.prepend_ctes(expression, this)
def fetch_sql(self, expression: sqlglot.expressions.query.Fetch) -> str:
1805    def fetch_sql(self, expression: exp.Fetch) -> str:
1806        direction = expression.args.get("direction")
1807        direction = f" {direction}" if direction else ""
1808        count = self.sql(expression, "count")
1809        count = f" {count}" if count else ""
1810        limit_options = self.sql(expression, "limit_options")
1811        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1812        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
def limitoptions_sql(self, expression: sqlglot.expressions.query.LimitOptions) -> str:
1814    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1815        percent = " PERCENT" if expression.args.get("percent") else ""
1816        rows = " ROWS" if expression.args.get("rows") else ""
1817        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1818        if not with_ties and rows:
1819            with_ties = " ONLY"
1820        return f"{percent}{rows}{with_ties}"
def filter_sql(self, expression: sqlglot.expressions.core.Filter) -> str:
1822    def filter_sql(self, expression: exp.Filter) -> str:
1823        if self.AGGREGATE_FILTER_SUPPORTED:
1824            this = self.sql(expression, "this")
1825            where = self.sql(expression, "expression").strip()
1826            return f"{this} FILTER({where})"
1827
1828        agg = expression.this
1829        agg_arg = agg.this
1830        cond = expression.expression.this
1831        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1832        return self.sql(agg)
def hint_sql(self, expression: sqlglot.expressions.core.Hint) -> str:
1834    def hint_sql(self, expression: exp.Hint) -> str:
1835        if not self.QUERY_HINTS:
1836            self.unsupported("Hints are not supported")
1837            return ""
1838
1839        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
def indexparameters_sql(self, expression: sqlglot.expressions.constraints.IndexParameters) -> str:
1841    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1842        using = self.sql(expression, "using")
1843        using = f" USING {using}" if using else ""
1844        columns = self.expressions(expression, key="columns", flat=True)
1845        columns = f"({columns})" if columns else ""
1846        partition_by = self.expressions(expression, key="partition_by", flat=True)
1847        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1848        where = self.sql(expression, "where")
1849        include = self.expressions(expression, key="include", flat=True)
1850        if include:
1851            include = f" INCLUDE ({include})"
1852        with_storage = self.expressions(expression, key="with_storage", flat=True)
1853        with_storage = f" WITH ({with_storage})" if with_storage else ""
1854        tablespace = self.sql(expression, "tablespace")
1855        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1856        on = self.sql(expression, "on")
1857        on = f" ON {on}" if on else ""
1858
1859        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
def index_sql(self, expression: sqlglot.expressions.query.Index) -> str:
1861    def index_sql(self, expression: exp.Index) -> str:
1862        unique = "UNIQUE " if expression.args.get("unique") else ""
1863        primary = "PRIMARY " if expression.args.get("primary") else ""
1864        amp = "AMP " if expression.args.get("amp") else ""
1865        name = self.sql(expression, "this")
1866        name = f"{name} " if name else ""
1867        table = self.sql(expression, "table")
1868        table = f"{self.INDEX_ON} {table}" if table else ""
1869
1870        index = "INDEX " if not table else ""
1871
1872        params = self.sql(expression, "params")
1873        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
def identifier_sql(self, expression: sqlglot.expressions.core.Identifier) -> str:
1875    def identifier_sql(self, expression: exp.Identifier) -> str:
1876        text = expression.name
1877        lower = text.lower()
1878        quoted = expression.quoted
1879        text = lower if self.normalize and not quoted else text
1880        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1881        if (
1882            quoted
1883            or self.dialect.can_quote(expression, self.identify)
1884            or lower in self.RESERVED_KEYWORDS
1885            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1886        ):
1887            text = (
1888                f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}"
1889            )
1890        return text
def hex_sql(self, expression: sqlglot.expressions.string.Hex) -> str:
1892    def hex_sql(self, expression: exp.Hex) -> str:
1893        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1894        if self.dialect.HEX_LOWERCASE:
1895            text = self.func("LOWER", text)
1896
1897        return text
def lowerhex_sql(self, expression: sqlglot.expressions.string.LowerHex) -> str:
1899    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1900        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1901        if not self.dialect.HEX_LOWERCASE:
1902            text = self.func("LOWER", text)
1903        return text
def inputoutputformat_sql(self, expression: sqlglot.expressions.query.InputOutputFormat) -> str:
1905    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1906        input_format = self.sql(expression, "input_format")
1907        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1908        output_format = self.sql(expression, "output_format")
1909        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1910        return self.sep().join((input_format, output_format))
def national_sql( self, expression: sqlglot.expressions.query.National, prefix: str = 'N') -> str:
1912    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1913        string = self.sql(exp.Literal.string(expression.name))
1914        return f"{prefix}{string}"
def partition_sql(self, expression: sqlglot.expressions.query.Partition) -> str:
1916    def partition_sql(self, expression: exp.Partition) -> str:
1917        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1918        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
def properties_sql(self, expression: sqlglot.expressions.properties.Properties) -> str:
1920    def properties_sql(self, expression: exp.Properties) -> str:
1921        root_properties = []
1922        with_properties = []
1923
1924        for p in expression.expressions:
1925            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1926            if p_loc == exp.Properties.Location.POST_WITH:
1927                with_properties.append(p)
1928            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1929                root_properties.append(p)
1930
1931        root_props_ast = exp.Properties(expressions=root_properties)
1932        root_props_ast.parent = expression.parent
1933
1934        with_props_ast = exp.Properties(expressions=with_properties)
1935        with_props_ast.parent = expression.parent
1936
1937        root_props = self.root_properties(root_props_ast)
1938        with_props = self.with_properties(with_props_ast)
1939
1940        if root_props and with_props and not self.pretty:
1941            with_props = " " + with_props
1942
1943        return root_props + with_props
def root_properties(self, properties: sqlglot.expressions.properties.Properties) -> str:
1945    def root_properties(self, properties: exp.Properties) -> str:
1946        if properties.expressions:
1947            return self.expressions(properties, indent=False, sep=" ")
1948        return ""
def properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1950    def properties(
1951        self,
1952        properties: exp.Properties,
1953        prefix: str = "",
1954        sep: str = ", ",
1955        suffix: str = "",
1956        wrapped: bool = True,
1957    ) -> str:
1958        if properties.expressions:
1959            expressions = self.expressions(properties, sep=sep, indent=False)
1960            if expressions:
1961                expressions = self.wrap(expressions) if wrapped else expressions
1962                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1963        return ""
def with_properties(self, properties: sqlglot.expressions.properties.Properties) -> str:
1965    def with_properties(self, properties: exp.Properties) -> str:
1966        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
def locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
1968    def locate_properties(self, properties: exp.Properties) -> defaultdict:
1969        properties_locs = defaultdict(list)
1970        for p in properties.expressions:
1971            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1972            if p_loc != exp.Properties.Location.UNSUPPORTED:
1973                properties_locs[p_loc].append(p)
1974            else:
1975                self.unsupported(f"Unsupported property {p.key}")
1976
1977        return properties_locs
def property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
1979    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1980        if isinstance(expression.this, exp.Dot):
1981            return self.sql(expression, "this")
1982        return f"'{expression.name}'" if string_key else expression.name
def property_sql(self, expression: sqlglot.expressions.properties.Property) -> str:
1984    def property_sql(self, expression: exp.Property) -> str:
1985        property_cls = expression.__class__
1986        if property_cls == exp.Property:
1987            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1988
1989        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1990        if not property_name:
1991            self.unsupported(f"Unsupported property {expression.key}")
1992
1993        return f"{property_name}={self.sql(expression, 'this')}"
def uuidproperty_sql(self, expression: sqlglot.expressions.properties.UuidProperty) -> str:
1995    def uuidproperty_sql(self, expression: exp.UuidProperty) -> str:
1996        return f"UUID {self.sql(expression, 'this')}"
def likeproperty_sql(self, expression: sqlglot.expressions.properties.LikeProperty) -> str:
1998    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1999        if self.SUPPORTS_CREATE_TABLE_LIKE:
2000            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
2001            options = f" {options}" if options else ""
2002
2003            like = f"LIKE {self.sql(expression, 'this')}{options}"
2004            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
2005                like = f"({like})"
2006
2007            return like
2008
2009        if expression.expressions:
2010            self.unsupported("Transpilation of LIKE property options is unsupported")
2011
2012        select = exp.select("*").from_(expression.this).limit(0)
2013        return f"AS {self.sql(select)}"
def fallbackproperty_sql(self, expression: sqlglot.expressions.properties.FallbackProperty) -> str:
2015    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
2016        no = "NO " if expression.args.get("no") else ""
2017        protection = " PROTECTION" if expression.args.get("protection") else ""
2018        return f"{no}FALLBACK{protection}"
def journalproperty_sql(self, expression: sqlglot.expressions.properties.JournalProperty) -> str:
2020    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
2021        no = "NO " if expression.args.get("no") else ""
2022        local = expression.args.get("local")
2023        local = f"{local} " if local else ""
2024        dual = "DUAL " if expression.args.get("dual") else ""
2025        before = "BEFORE " if expression.args.get("before") else ""
2026        after = "AFTER " if expression.args.get("after") else ""
2027        return f"{no}{local}{dual}{before}{after}JOURNAL"
def freespaceproperty_sql( self, expression: sqlglot.expressions.properties.FreespaceProperty) -> str:
2029    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
2030        freespace = self.sql(expression, "this")
2031        percent = " PERCENT" if expression.args.get("percent") else ""
2032        return f"FREESPACE={freespace}{percent}"
def checksumproperty_sql(self, expression: sqlglot.expressions.properties.ChecksumProperty) -> str:
2034    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
2035        if expression.args.get("default"):
2036            property = "DEFAULT"
2037        elif expression.args.get("on"):
2038            property = "ON"
2039        else:
2040            property = "OFF"
2041        return f"CHECKSUM={property}"
def mergeblockratioproperty_sql( self, expression: sqlglot.expressions.properties.MergeBlockRatioProperty) -> str:
2043    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
2044        if expression.args.get("no"):
2045            return "NO MERGEBLOCKRATIO"
2046        if expression.args.get("default"):
2047            return "DEFAULT MERGEBLOCKRATIO"
2048
2049        percent = " PERCENT" if expression.args.get("percent") else ""
2050        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def moduleproperty_sql(self, expression: sqlglot.expressions.properties.ModuleProperty) -> str:
2052    def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str:
2053        expressions = self.expressions(expression, flat=True)
2054        expressions = f"({expressions})" if expressions else ""
2055        return f"USING {self.sql(expression, 'this')}{expressions}"
def datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2057    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
2058        default = expression.args.get("default")
2059        minimum = expression.args.get("minimum")
2060        maximum = expression.args.get("maximum")
2061        if default or minimum or maximum:
2062            if default:
2063                prop = "DEFAULT"
2064            elif minimum:
2065                prop = "MINIMUM"
2066            else:
2067                prop = "MAXIMUM"
2068            return f"{prop} DATABLOCKSIZE"
2069        units = expression.args.get("units")
2070        units = f" {units}" if units else ""
2071        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2073    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
2074        autotemp = expression.args.get("autotemp")
2075        always = expression.args.get("always")
2076        default = expression.args.get("default")
2077        manual = expression.args.get("manual")
2078        never = expression.args.get("never")
2079
2080        if autotemp is not None:
2081            prop = f"AUTOTEMP({self.expressions(autotemp)})"
2082        elif always:
2083            prop = "ALWAYS"
2084        elif default:
2085            prop = "DEFAULT"
2086        elif manual:
2087            prop = "MANUAL"
2088        elif never:
2089            prop = "NEVER"
2090        return f"BLOCKCOMPRESSION={prop}"
def isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2092    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
2093        no = expression.args.get("no")
2094        no = " NO" if no else ""
2095        concurrent = expression.args.get("concurrent")
2096        concurrent = " CONCURRENT" if concurrent else ""
2097        target = self.sql(expression, "target")
2098        target = f" {target}" if target else ""
2099        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2101    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2102        if isinstance(expression.this, list):
2103            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2104        if expression.this:
2105            modulus = self.sql(expression, "this")
2106            remainder = self.sql(expression, "expression")
2107            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2108
2109        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2110        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2111        return f"FROM ({from_expressions}) TO ({to_expressions})"
def partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2113    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2114        this = self.sql(expression, "this")
2115
2116        for_values_or_default = expression.expression
2117        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2118            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2119        else:
2120            for_values_or_default = " DEFAULT"
2121
2122        return f"PARTITION OF {this}{for_values_or_default}"
def lockingproperty_sql(self, expression: sqlglot.expressions.properties.LockingProperty) -> str:
2124    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2125        kind = expression.args.get("kind")
2126        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2127        for_or_in = expression.args.get("for_or_in")
2128        for_or_in = f" {for_or_in}" if for_or_in else ""
2129        lock_type = expression.args.get("lock_type")
2130        override = " OVERRIDE" if expression.args.get("override") else ""
2131        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
def withdataproperty_sql(self, expression: sqlglot.expressions.properties.WithDataProperty) -> str:
2133    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2134        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2135        statistics = expression.args.get("statistics")
2136        statistics_sql = ""
2137        if statistics is not None:
2138            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2139        return f"{data_sql}{statistics_sql}"
def withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2141    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2142        this = self.sql(expression, "this")
2143        this = f"HISTORY_TABLE={this}" if this else ""
2144        data_consistency: str | None = self.sql(expression, "data_consistency")
2145        data_consistency = (
2146            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2147        )
2148        retention_period: str | None = self.sql(expression, "retention_period")
2149        retention_period = (
2150            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2151        )
2152
2153        if this:
2154            on_sql = self.func("ON", this, data_consistency, retention_period)
2155        else:
2156            on_sql = "ON" if expression.args.get("on") else "OFF"
2157
2158        sql = f"SYSTEM_VERSIONING={on_sql}"
2159
2160        return f"WITH({sql})" if expression.args.get("with_") else sql
def insert_sql(self, expression: sqlglot.expressions.dml.Insert) -> str:
2162    def insert_sql(self, expression: exp.Insert) -> str:
2163        hint = self.sql(expression, "hint")
2164        overwrite = expression.args.get("overwrite")
2165
2166        if isinstance(expression.this, exp.Directory):
2167            this = " OVERWRITE" if overwrite else " INTO"
2168        else:
2169            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2170
2171        stored = self.sql(expression, "stored")
2172        stored = f" {stored}" if stored else ""
2173        alternative = expression.args.get("alternative")
2174        alternative = f" OR {alternative}" if alternative else ""
2175        ignore = " IGNORE" if expression.args.get("ignore") else ""
2176        is_function = expression.args.get("is_function")
2177        if is_function:
2178            this = f"{this} FUNCTION"
2179        this = f"{this} {self.sql(expression, 'this')}"
2180
2181        exists = " IF EXISTS" if expression.args.get("exists") else ""
2182        where = self.sql(expression, "where")
2183        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2184        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2185        on_conflict = self.sql(expression, "conflict")
2186        on_conflict = f" {on_conflict}" if on_conflict else ""
2187        by_name = " BY NAME" if expression.args.get("by_name") else ""
2188        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2189        returning = self.sql(expression, "returning")
2190
2191        if self.RETURNING_END:
2192            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2193        else:
2194            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2195
2196        partition_by = self.sql(expression, "partition")
2197        partition_by = f" {partition_by}" if partition_by else ""
2198        settings = self.sql(expression, "settings")
2199        settings = f" {settings}" if settings else ""
2200
2201        source = self.sql(expression, "source")
2202        source = f"TABLE {source}" if source else ""
2203
2204        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2205        return self.prepend_ctes(expression, sql)
def introducer_sql(self, expression: sqlglot.expressions.query.Introducer) -> str:
2207    def introducer_sql(self, expression: exp.Introducer) -> str:
2208        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def kill_sql(self, expression: sqlglot.expressions.ddl.Kill) -> str:
2210    def kill_sql(self, expression: exp.Kill) -> str:
2211        kind = self.sql(expression, "kind")
2212        kind = f" {kind}" if kind else ""
2213        this = self.sql(expression, "this")
2214        this = f" {this}" if this else ""
2215        return f"KILL{kind}{this}"
def pseudotype_sql(self, expression: sqlglot.expressions.datatypes.PseudoType) -> str:
2217    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2218        return expression.name
def objectidentifier_sql(self, expression: sqlglot.expressions.datatypes.ObjectIdentifier) -> str:
2220    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2221        return expression.name
def onconflict_sql(self, expression: sqlglot.expressions.dml.OnConflict) -> str:
2223    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2224        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2225
2226        constraint = self.sql(expression, "constraint")
2227        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2228
2229        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2230        if conflict_keys:
2231            conflict_keys = f"({conflict_keys})"
2232
2233        index_predicate = self.sql(expression, "index_predicate")
2234        conflict_keys = f"{conflict_keys}{index_predicate} "
2235
2236        action = self.sql(expression, "action")
2237
2238        expressions = self.expressions(expression, flat=True)
2239        if expressions:
2240            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2241            expressions = f" {set_keyword}{expressions}"
2242
2243        where = self.sql(expression, "where")
2244        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def returning_sql(self, expression: sqlglot.expressions.dml.Returning) -> str:
2246    def returning_sql(self, expression: exp.Returning) -> str:
2247        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
def rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2249    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2250        fields = self.sql(expression, "fields")
2251        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2252        escaped = self.sql(expression, "escaped")
2253        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2254        items = self.sql(expression, "collection_items")
2255        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2256        keys = self.sql(expression, "map_keys")
2257        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2258        lines = self.sql(expression, "lines")
2259        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2260        null = self.sql(expression, "null")
2261        null = f" NULL DEFINED AS {null}" if null else ""
2262        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
def withtablehint_sql(self, expression: sqlglot.expressions.query.WithTableHint) -> str:
2264    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2265        return f"WITH ({self.expressions(expression, flat=True)})"
def indextablehint_sql(self, expression: sqlglot.expressions.query.IndexTableHint) -> str:
2267    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2268        this = f"{self.sql(expression, 'this')} INDEX"
2269        target = self.sql(expression, "target")
2270        target = f" FOR {target}" if target else ""
2271        return f"{this}{target} ({self.expressions(expression, flat=True)})"
def historicaldata_sql(self, expression: sqlglot.expressions.query.HistoricalData) -> str:
2273    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2274        this = self.sql(expression, "this")
2275        kind = self.sql(expression, "kind")
2276        expr = self.sql(expression, "expression")
2277        return f"{this} ({kind} => {expr})"
def table_parts(self, expression: sqlglot.expressions.query.Table) -> str:
2279    def table_parts(self, expression: exp.Table) -> str:
2280        return ".".join(
2281            self.sql(part)
2282            for part in (
2283                expression.args.get("catalog"),
2284                expression.args.get("db"),
2285                expression.args.get("this"),
2286            )
2287            if part is not None
2288        )
def table_sql( self, expression: sqlglot.expressions.query.Table, sep: str = ' AS ') -> str:
2290    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2291        table = self.table_parts(expression)
2292        only = "ONLY " if expression.args.get("only") else ""
2293        partition = self.sql(expression, "partition")
2294        partition = f" {partition}" if partition else ""
2295        version = self.sql(expression, "version")
2296        version = f" {version}" if version else ""
2297        alias = self.sql(expression, "alias")
2298        alias = f"{sep}{alias}" if alias else ""
2299
2300        sample = self.sql(expression, "sample")
2301        post_alias = ""
2302        pre_alias = ""
2303
2304        if self.dialect.ALIAS_POST_TABLESAMPLE:
2305            pre_alias = sample
2306        else:
2307            post_alias = sample
2308
2309        if self.dialect.ALIAS_POST_VERSION:
2310            pre_alias = f"{pre_alias}{version}"
2311        else:
2312            post_alias = f"{post_alias}{version}"
2313
2314        hints = self.expressions(expression, key="hints", sep=" ")
2315        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2316        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2317        joins = self.indent(
2318            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2319        )
2320        laterals = self.expressions(expression, key="laterals", sep="")
2321
2322        file_format = self.sql(expression, "format")
2323        if file_format:
2324            pattern = self.sql(expression, "pattern")
2325            pattern = f", PATTERN => {pattern}" if pattern else ""
2326            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2327
2328        ordinality = expression.args.get("ordinality") or ""
2329        if ordinality:
2330            ordinality = f" WITH ORDINALITY{alias}"
2331            alias = ""
2332
2333        when = self.sql(expression, "when")
2334        if when:
2335            table = f"{table} {when}"
2336
2337        changes = self.sql(expression, "changes")
2338        changes = f" {changes}" if changes else ""
2339
2340        rows_from = self.expressions(expression, key="rows_from")
2341        if rows_from:
2342            table = f"ROWS FROM {self.wrap(rows_from)}"
2343
2344        indexed = expression.args.get("indexed")
2345        if indexed is not None:
2346            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2347        else:
2348            indexed = ""
2349
2350        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:
2352    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2353        table = self.func("TABLE", expression.this)
2354        alias = self.sql(expression, "alias")
2355        alias = f" AS {alias}" if alias else ""
2356        sample = self.sql(expression, "sample")
2357        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2358        joins = self.indent(
2359            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2360        )
2361        return f"{table}{alias}{pivots}{sample}{joins}"
def tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2363    def tablesample_sql(
2364        self,
2365        expression: exp.TableSample,
2366        tablesample_keyword: str | None = None,
2367    ) -> str:
2368        method = self.sql(expression, "method")
2369        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2370        numerator = self.sql(expression, "bucket_numerator")
2371        denominator = self.sql(expression, "bucket_denominator")
2372        field = self.sql(expression, "bucket_field")
2373        field = f" ON {field}" if field else ""
2374        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2375        seed = self.sql(expression, "seed")
2376        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2377
2378        size = self.sql(expression, "size")
2379        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2380            size = f"{size} ROWS"
2381
2382        percent = self.sql(expression, "percent")
2383        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2384            percent = f"{percent} PERCENT"
2385
2386        expr = f"{bucket}{percent}{size}"
2387        if self.TABLESAMPLE_REQUIRES_PARENS:
2388            expr = f"({expr})"
2389
2390        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
def pivot_sql(self, expression: sqlglot.expressions.query.Pivot) -> str:
2392    def pivot_sql(self, expression: exp.Pivot) -> str:
2393        expressions = self.expressions(expression, flat=True)
2394        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2395
2396        group = self.sql(expression, "group")
2397
2398        if expression.this:
2399            this = self.sql(expression, "this")
2400            if not expressions:
2401                sql = f"UNPIVOT {this}"
2402            else:
2403                on = f"{self.seg('ON')} {expressions}"
2404                into = self.sql(expression, "into")
2405                into = f"{self.seg('INTO')} {into}" if into else ""
2406                using = self.expressions(expression, key="using", flat=True)
2407                using = f"{self.seg('USING')} {using}" if using else ""
2408                sql = f"{direction} {this}{on}{into}{using}{group}"
2409            return self.prepend_ctes(expression, sql)
2410
2411        alias = self.sql(expression, "alias")
2412        alias = f" AS {alias}" if alias else ""
2413
2414        fields = self.expressions(
2415            expression,
2416            "fields",
2417            sep=" ",
2418            dynamic=True,
2419            new_line=True,
2420            skip_first=True,
2421            skip_last=True,
2422        )
2423
2424        include_nulls = expression.args.get("include_nulls")
2425        if include_nulls is not None:
2426            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2427        else:
2428            nulls = ""
2429
2430        default_on_null = self.sql(expression, "default_on_null")
2431        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2432        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2433        return self.prepend_ctes(expression, sql)
def version_sql(self, expression: sqlglot.expressions.query.Version) -> str:
2435    def version_sql(self, expression: exp.Version) -> str:
2436        this = f"FOR {expression.name}"
2437        kind = expression.text("kind")
2438        expr = self.sql(expression, "expression")
2439        return f"{this} {kind} {expr}"
def tuple_sql(self, expression: sqlglot.expressions.query.Tuple) -> str:
2441    def tuple_sql(self, expression: exp.Tuple) -> str:
2442        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:
2476    def update_sql(self, expression: exp.Update) -> str:
2477        hint = self.sql(expression, "hint")
2478        this = self.sql(expression, "this")
2479        join_sql, from_sql = self._update_from_joins_sql(expression)
2480        set_sql = self.expressions(expression, flat=True)
2481        where_sql = self.sql(expression, "where")
2482        returning = self.sql(expression, "returning")
2483        order = self.sql(expression, "order")
2484        limit = self.sql(expression, "limit")
2485        if self.RETURNING_END:
2486            expression_sql = f"{from_sql}{where_sql}{returning}"
2487        else:
2488            expression_sql = f"{returning}{from_sql}{where_sql}"
2489        options = self.expressions(expression, key="options")
2490        options = f" OPTION({options})" if options else ""
2491        sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2492        return self.prepend_ctes(expression, sql)
def values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2494    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2495        values_as_table = values_as_table and self.VALUES_AS_TABLE
2496
2497        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2498        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2499            args = self.expressions(expression)
2500            alias = self.sql(expression, "alias")
2501            values = f"VALUES{self.seg('')}{args}"
2502            values = (
2503                f"({values})"
2504                if self.WRAP_DERIVED_VALUES
2505                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2506                else values
2507            )
2508            values = self.query_modifiers(expression, values)
2509            return f"{values} AS {alias}" if alias else values
2510
2511        # Converts `VALUES...` expression into a series of select unions.
2512        alias_node = expression.args.get("alias")
2513        column_names = alias_node and alias_node.columns
2514
2515        selects: list[exp.Query] = []
2516
2517        for i, tup in enumerate(expression.expressions):
2518            row = tup.expressions
2519
2520            if i == 0 and column_names:
2521                row = [
2522                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2523                ]
2524
2525            selects.append(exp.Select(expressions=row))
2526
2527        if self.pretty:
2528            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2529            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2530            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2531            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2532            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2533
2534        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2535        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2536        return f"({unions}){alias}"
def var_sql(self, expression: sqlglot.expressions.core.Var) -> str:
2538    def var_sql(self, expression: exp.Var) -> str:
2539        return self.sql(expression, "this")
@unsupported_args('expressions')
def into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2541    @unsupported_args("expressions")
2542    def into_sql(self, expression: exp.Into) -> str:
2543        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2544        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2545        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
def from_sql(self, expression: sqlglot.expressions.query.From) -> str:
2547    def from_sql(self, expression: exp.From) -> str:
2548        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
def groupingsets_sql(self, expression: sqlglot.expressions.query.GroupingSets) -> str:
2550    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2551        grouping_sets = self.expressions(expression, indent=False)
2552        return f"GROUPING SETS {self.wrap(grouping_sets)}"
def rollup_sql(self, expression: sqlglot.expressions.query.Rollup) -> str:
2554    def rollup_sql(self, expression: exp.Rollup) -> str:
2555        expressions = self.expressions(expression, indent=False)
2556        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
def rollupindex_sql(self, expression: sqlglot.expressions.properties.RollupIndex) -> str:
2558    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2559        this = self.sql(expression, "this")
2560
2561        columns = self.expressions(expression, flat=True)
2562
2563        from_sql = self.sql(expression, "from_index")
2564        from_sql = f" FROM {from_sql}" if from_sql else ""
2565
2566        properties = expression.args.get("properties")
2567        properties_sql = (
2568            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2569        )
2570
2571        return f"{this}({columns}){from_sql}{properties_sql}"
def rollupproperty_sql(self, expression: sqlglot.expressions.properties.RollupProperty) -> str:
2573    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2574        return f"ROLLUP ({self.expressions(expression, flat=True)})"
def cube_sql(self, expression: sqlglot.expressions.query.Cube) -> str:
2576    def cube_sql(self, expression: exp.Cube) -> str:
2577        expressions = self.expressions(expression, indent=False)
2578        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
def group_sql(self, expression: sqlglot.expressions.query.Group) -> str:
2580    def group_sql(self, expression: exp.Group) -> str:
2581        group_by_all = expression.args.get("all")
2582        if group_by_all is True:
2583            modifier = " ALL"
2584        elif group_by_all is False:
2585            modifier = " DISTINCT"
2586        else:
2587            modifier = ""
2588
2589        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2590
2591        grouping_sets = self.expressions(expression, key="grouping_sets")
2592        cube = self.expressions(expression, key="cube")
2593        rollup = self.expressions(expression, key="rollup")
2594
2595        groupings = csv(
2596            self.seg(grouping_sets) if grouping_sets else "",
2597            self.seg(cube) if cube else "",
2598            self.seg(rollup) if rollup else "",
2599            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2600            sep=self.GROUPINGS_SEP,
2601        )
2602
2603        if (
2604            expression.expressions
2605            and groupings
2606            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2607        ):
2608            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2609
2610        return f"{group_by}{groupings}"
def having_sql(self, expression: sqlglot.expressions.query.Having) -> str:
2612    def having_sql(self, expression: exp.Having) -> str:
2613        this = self.indent(self.sql(expression, "this"))
2614        return f"{self.seg('HAVING')}{self.sep()}{this}"
def connect_sql(self, expression: sqlglot.expressions.query.Connect) -> str:
2616    def connect_sql(self, expression: exp.Connect) -> str:
2617        start = self.sql(expression, "start")
2618        start = self.seg(f"START WITH {start}") if start else ""
2619        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2620        connect = self.sql(expression, "connect")
2621        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2622        return start + connect
def prior_sql(self, expression: sqlglot.expressions.query.Prior) -> str:
2624    def prior_sql(self, expression: exp.Prior) -> str:
2625        return f"PRIOR {self.sql(expression, 'this')}"
def join_sql(self, expression: sqlglot.expressions.query.Join) -> str:
2627    def join_sql(self, expression: exp.Join) -> str:
2628        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2629            side = None
2630        else:
2631            side = expression.side
2632
2633        op_sql = " ".join(
2634            op
2635            for op in (
2636                expression.method,
2637                "GLOBAL" if expression.args.get("global_") else None,
2638                side,
2639                expression.kind,
2640                expression.hint if self.JOIN_HINTS else None,
2641                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2642            )
2643            if op
2644        )
2645        match_cond = self.sql(expression, "match_condition")
2646        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2647        on_sql = self.sql(expression, "on")
2648        using = expression.args.get("using")
2649
2650        if not on_sql and using:
2651            on_sql = csv(*(self.sql(column) for column in using))
2652
2653        this = expression.this
2654        this_sql = self.sql(this)
2655
2656        exprs = self.expressions(expression)
2657        if exprs:
2658            this_sql = f"{this_sql},{self.seg(exprs)}"
2659
2660        if on_sql:
2661            on_sql = self.indent(on_sql, skip_first=True)
2662            space = self.seg(" " * self.pad) if self.pretty else " "
2663            if using:
2664                on_sql = f"{space}USING ({on_sql})"
2665            else:
2666                on_sql = f"{space}ON {on_sql}"
2667        elif not op_sql:
2668            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2669                return f" {this_sql}"
2670
2671            return f", {this_sql}"
2672
2673        if op_sql != "STRAIGHT_JOIN":
2674            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2675
2676        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2677        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:
2679    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2680        args = self.expressions(expression, flat=True)
2681        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2682        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
def lateral_op(self, expression: sqlglot.expressions.query.Lateral) -> str:
2684    def lateral_op(self, expression: exp.Lateral) -> str:
2685        cross_apply = expression.args.get("cross_apply")
2686
2687        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2688        if cross_apply is True:
2689            op = "INNER JOIN "
2690        elif cross_apply is False:
2691            op = "LEFT JOIN "
2692        else:
2693            op = ""
2694
2695        return f"{op}LATERAL"
def lateral_sql(self, expression: sqlglot.expressions.query.Lateral) -> str:
2697    def lateral_sql(self, expression: exp.Lateral) -> str:
2698        this = self.sql(expression, "this")
2699
2700        if expression.args.get("view"):
2701            alias = expression.args["alias"]
2702            columns = self.expressions(alias, key="columns", flat=True)
2703            table = f" {alias.name}" if alias.name else ""
2704            columns = f" AS {columns}" if columns else ""
2705            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2706            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2707
2708        alias = self.sql(expression, "alias")
2709        alias = f" AS {alias}" if alias else ""
2710
2711        ordinality = expression.args.get("ordinality") or ""
2712        if ordinality:
2713            ordinality = f" WITH ORDINALITY{alias}"
2714            alias = ""
2715
2716        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
def limit_sql( self, expression: sqlglot.expressions.query.Limit, top: bool = False) -> str:
2718    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2719        this = self.sql(expression, "this")
2720
2721        args = [
2722            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2723            for e in (expression.args.get(k) for k in ("offset", "expression"))
2724            if e
2725        ]
2726
2727        args_sql = ", ".join(self.sql(e) for e in args)
2728        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2729        expressions = self.expressions(expression, flat=True)
2730        limit_options = self.sql(expression, "limit_options")
2731        expressions = f" BY {expressions}" if expressions else ""
2732
2733        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:
2735    def offset_sql(self, expression: exp.Offset) -> str:
2736        this = self.sql(expression, "this")
2737        value = expression.expression
2738        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2739        expressions = self.expressions(expression, flat=True)
2740        expressions = f" BY {expressions}" if expressions else ""
2741        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
def setitem_sql(self, expression: sqlglot.expressions.ddl.SetItem) -> str:
2743    def setitem_sql(self, expression: exp.SetItem) -> str:
2744        kind = self.sql(expression, "kind")
2745        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2746            kind = ""
2747        else:
2748            kind = f"{kind} " if kind else ""
2749        this = self.sql(expression, "this")
2750        expressions = self.expressions(expression)
2751        collate = self.sql(expression, "collate")
2752        collate = f" COLLATE {collate}" if collate else ""
2753        global_ = "GLOBAL " if expression.args.get("global_") else ""
2754        return f"{global_}{kind}{this}{expressions}{collate}"
def set_sql(self, expression: sqlglot.expressions.ddl.Set) -> str:
2756    def set_sql(self, expression: exp.Set) -> str:
2757        expressions = f" {self.expressions(expression, flat=True)}"
2758        tag = " TAG" if expression.args.get("tag") else ""
2759        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
def queryband_sql(self, expression: sqlglot.expressions.query.QueryBand) -> str:
2761    def queryband_sql(self, expression: exp.QueryBand) -> str:
2762        this = self.sql(expression, "this")
2763        update = " UPDATE" if expression.args.get("update") else ""
2764        scope = self.sql(expression, "scope")
2765        scope = f" FOR {scope}" if scope else ""
2766
2767        return f"QUERY_BAND = {this}{update}{scope}"
def pragma_sql(self, expression: sqlglot.expressions.ddl.Pragma) -> str:
2769    def pragma_sql(self, expression: exp.Pragma) -> str:
2770        return f"PRAGMA {self.sql(expression, 'this')}"
def lock_sql(self, expression: sqlglot.expressions.query.Lock) -> str:
2772    def lock_sql(self, expression: exp.Lock) -> str:
2773        if not self.LOCKING_READS_SUPPORTED:
2774            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2775            return ""
2776
2777        update = expression.args["update"]
2778        key = expression.args.get("key")
2779        if update:
2780            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2781        else:
2782            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2783        expressions = self.expressions(expression, flat=True)
2784        expressions = f" OF {expressions}" if expressions else ""
2785        wait = expression.args.get("wait")
2786
2787        if wait is not None:
2788            if isinstance(wait, exp.Literal):
2789                wait = f" WAIT {self.sql(wait)}"
2790            else:
2791                wait = " NOWAIT" if wait else " SKIP LOCKED"
2792
2793        return f"{lock_type}{expressions}{wait or ''}"
def literal_sql(self, expression: sqlglot.expressions.core.Literal) -> str:
2795    def literal_sql(self, expression: exp.Literal) -> str:
2796        text = expression.this or ""
2797        if expression.is_string:
2798            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2799        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:
2801    def escape_str(
2802        self,
2803        text: str,
2804        escape_backslash: bool = True,
2805        delimiter: str | None = None,
2806        escaped_delimiter: str | None = None,
2807        is_byte_string: bool = False,
2808    ) -> str:
2809        if is_byte_string:
2810            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
2811        else:
2812            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
2813
2814        if supports_escape_sequences:
2815            text = "".join(
2816                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
2817                for ch in text
2818            )
2819
2820        delimiter = delimiter or self.dialect.QUOTE_END
2821        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2822
2823        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
def loaddata_sql(self, expression: sqlglot.expressions.dml.LoadData) -> str:
2825    def loaddata_sql(self, expression: exp.LoadData) -> str:
2826        is_overwrite = expression.args.get("overwrite")
2827        overwrite = " OVERWRITE" if is_overwrite else ""
2828        this = self.sql(expression, "this")
2829
2830        files = expression.args.get("files")
2831        if files:
2832            files_sql = self.expressions(files, flat=True)
2833            files_sql = f"FILES{self.wrap(files_sql)}"
2834            this = f" {this}" if is_overwrite else f" INTO TABLE {this}"
2835            return f"LOAD DATA{overwrite}{this} FROM {files_sql}"
2836
2837        local = " LOCAL" if expression.args.get("local") else ""
2838        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2839        this = f" INTO TABLE {this}"
2840        partition = self.sql(expression, "partition")
2841        partition = f" {partition}" if partition else ""
2842        input_format = self.sql(expression, "input_format")
2843        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2844        serde = self.sql(expression, "serde")
2845        serde = f" SERDE {serde}" if serde else ""
2846        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
def null_sql(self, *_) -> str:
2848    def null_sql(self, *_) -> str:
2849        return "NULL"
def boolean_sql(self, expression: sqlglot.expressions.core.Boolean) -> str:
2851    def boolean_sql(self, expression: exp.Boolean) -> str:
2852        return "TRUE" if expression.this else "FALSE"
def booland_sql(self, expression: sqlglot.expressions.math.Booland) -> str:
2854    def booland_sql(self, expression: exp.Booland) -> str:
2855        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
def boolor_sql(self, expression: sqlglot.expressions.math.Boolor) -> str:
2857    def boolor_sql(self, expression: exp.Boolor) -> str:
2858        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
def order_sql( self, expression: sqlglot.expressions.query.Order, flat: bool = False) -> str:
2860    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2861        this = self.sql(expression, "this")
2862        this = f"{this} " if this else this
2863        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2864        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:
2866    def withfill_sql(self, expression: exp.WithFill) -> str:
2867        from_sql = self.sql(expression, "from_")
2868        from_sql = f" FROM {from_sql}" if from_sql else ""
2869        to_sql = self.sql(expression, "to")
2870        to_sql = f" TO {to_sql}" if to_sql else ""
2871        step_sql = self.sql(expression, "step")
2872        step_sql = f" STEP {step_sql}" if step_sql else ""
2873        interpolated_values = [
2874            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2875            if isinstance(e, exp.Alias)
2876            else self.sql(e, "this")
2877            for e in expression.args.get("interpolate") or []
2878        ]
2879        interpolate = (
2880            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2881        )
2882        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
def cluster_sql(self, expression: sqlglot.expressions.query.Cluster) -> str:
2884    def cluster_sql(self, expression: exp.Cluster) -> str:
2885        return self.op_expressions("CLUSTER BY", expression)
def distribute_sql(self, expression: sqlglot.expressions.query.Distribute) -> str:
2887    def distribute_sql(self, expression: exp.Distribute) -> str:
2888        return self.op_expressions("DISTRIBUTE BY", expression)
def sort_sql(self, expression: sqlglot.expressions.query.Sort) -> str:
2890    def sort_sql(self, expression: exp.Sort) -> str:
2891        return self.op_expressions("SORT BY", expression)
def ordered_sql(self, expression: sqlglot.expressions.core.Ordered) -> str:
2893    def ordered_sql(self, expression: exp.Ordered) -> str:
2894        desc = expression.args.get("desc")
2895        asc = not desc
2896
2897        nulls_first = expression.args.get("nulls_first")
2898        nulls_last = not nulls_first
2899        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2900        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2901        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2902
2903        this = self.sql(expression, "this")
2904
2905        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2906        nulls_sort_change = ""
2907        if nulls_first and (
2908            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2909        ):
2910            nulls_sort_change = " NULLS FIRST"
2911        elif (
2912            nulls_last
2913            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2914            and not nulls_are_last
2915        ):
2916            nulls_sort_change = " NULLS LAST"
2917
2918        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2919        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2920            window = expression.find_ancestor(exp.Window, exp.Select)
2921
2922            if isinstance(window, exp.Window):
2923                window_this = window.this
2924                if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)):
2925                    window_this = window_this.this
2926                spec = window.args.get("spec")
2927            else:
2928                window_this = None
2929                spec = None
2930
2931            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST
2932            # without a spec or with a ROWS spec, but not with RANGE
2933            if not (
2934                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)
2935                and (not spec or spec.text("kind").upper() == "ROWS")
2936            ):
2937                if window_this and spec:
2938                    self.unsupported(
2939                        f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}"
2940                    )
2941                    nulls_sort_change = ""
2942                elif self.NULL_ORDERING_SUPPORTED is False and (
2943                    (asc and nulls_sort_change == " NULLS LAST")
2944                    or (desc and nulls_sort_change == " NULLS FIRST")
2945                ):
2946                    # BigQuery does not allow these ordering/nulls combinations when used under
2947                    # an aggregation func or under a window containing one
2948                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2949
2950                    if isinstance(ancestor, exp.Window):
2951                        ancestor = ancestor.this
2952                    if isinstance(ancestor, exp.AggFunc):
2953                        self.unsupported(
2954                            f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order"
2955                        )
2956                        nulls_sort_change = ""
2957                elif self.NULL_ORDERING_SUPPORTED is None:
2958                    if expression.this.is_int:
2959                        self.unsupported(
2960                            f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2961                        )
2962                    elif not isinstance(expression.this, exp.Rand):
2963                        null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2964                        this = (
2965                            f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2966                        )
2967                    nulls_sort_change = ""
2968
2969        with_fill = self.sql(expression, "with_fill")
2970        with_fill = f" {with_fill}" if with_fill else ""
2971
2972        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
2974    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2975        window_frame = self.sql(expression, "window_frame")
2976        window_frame = f"{window_frame} " if window_frame else ""
2977
2978        this = self.sql(expression, "this")
2979
2980        return f"{window_frame}{this}"
def matchrecognize_sql(self, expression: sqlglot.expressions.query.MatchRecognize) -> str:
2982    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2983        partition = self.partition_by_sql(expression)
2984        order = self.sql(expression, "order")
2985        measures = self.expressions(expression, key="measures")
2986        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2987        rows = self.sql(expression, "rows")
2988        rows = self.seg(rows) if rows else ""
2989        after = self.sql(expression, "after")
2990        after = self.seg(after) if after else ""
2991        pattern = self.sql(expression, "pattern")
2992        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2993        definition_sqls = [
2994            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2995            for definition in expression.args.get("define", [])
2996        ]
2997        definitions = self.expressions(sqls=definition_sqls)
2998        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2999        body = "".join(
3000            (
3001                partition,
3002                order,
3003                measures,
3004                rows,
3005                after,
3006                pattern,
3007                define,
3008            )
3009        )
3010        alias = self.sql(expression, "alias")
3011        alias = f" {alias}" if alias else ""
3012        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
def query_modifiers(self, expression: sqlglot.expressions.core.Expr, *sqls: str) -> str:
3014    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:
3015        limit = expression.args.get("limit")
3016
3017        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
3018            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
3019        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
3020            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
3021
3022        return csv(
3023            *sqls,
3024            *[self.sql(join) for join in expression.args.get("joins") or []],
3025            self.sql(expression, "match"),
3026            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
3027            self.sql(expression, "prewhere"),
3028            self.sql(expression, "where"),
3029            self.sql(expression, "connect"),
3030            self.sql(expression, "group"),
3031            self.sql(expression, "having"),
3032            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
3033            self.sql(expression, "order"),
3034            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
3035            *self.after_limit_modifiers(expression),
3036            self.options_modifier(expression),
3037            self.for_modifiers(expression),
3038            sep="",
3039        )
def options_modifier(self, expression: sqlglot.expressions.core.Expr) -> str:
3041    def options_modifier(self, expression: exp.Expr) -> str:
3042        options = self.expressions(expression, key="options")
3043        return f" {options}" if options else ""
def for_modifiers(self, expression: sqlglot.expressions.core.Expr) -> str:
3045    def for_modifiers(self, expression: exp.Expr) -> str:
3046        for_modifiers = self.expressions(expression, key="for_")
3047        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
def queryoption_sql(self, expression: sqlglot.expressions.query.QueryOption) -> str:
3049    def queryoption_sql(self, expression: exp.QueryOption) -> str:
3050        self.unsupported("Unsupported query option.")
3051        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]:
3053    def offset_limit_modifiers(
3054        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
3055    ) -> list[str]:
3056        return [
3057            self.sql(expression, "offset") if fetch else self.sql(limit),
3058            self.sql(limit) if fetch else self.sql(expression, "offset"),
3059        ]
def after_limit_modifiers(self, expression: sqlglot.expressions.core.Expr) -> list[str]:
3061    def after_limit_modifiers(self, expression: exp.Expr) -> list[str]:
3062        locks = self.expressions(expression, key="locks", sep=" ")
3063        locks = f" {locks}" if locks else ""
3064        return [locks, self.sql(expression, "sample")]
def select_sql(self, expression: sqlglot.expressions.query.Select) -> str:
3066    def select_sql(self, expression: exp.Select) -> str:
3067        into = expression.args.get("into")
3068        if not self.SUPPORTS_SELECT_INTO and into:
3069            into.pop()
3070
3071        hint = self.sql(expression, "hint")
3072        distinct = self.sql(expression, "distinct")
3073        distinct = f" {distinct}" if distinct else ""
3074        kind = self.sql(expression, "kind")
3075
3076        limit = expression.args.get("limit")
3077        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
3078            top = self.limit_sql(limit, top=True)
3079            limit.pop()
3080        else:
3081            top = ""
3082
3083        expressions = self.expressions(expression)
3084
3085        if kind:
3086            if kind in self.SELECT_KINDS:
3087                kind = f" AS {kind}"
3088            else:
3089                if kind == "STRUCT":
3090                    expressions = self.expressions(
3091                        sqls=[
3092                            self.sql(
3093                                exp.Struct(
3094                                    expressions=[
3095                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
3096                                        if isinstance(e, exp.Alias)
3097                                        else e
3098                                        for e in expression.expressions
3099                                    ]
3100                                )
3101                            )
3102                        ]
3103                    )
3104                kind = ""
3105
3106        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
3107        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
3108
3109        exclude = expression.args.get("exclude")
3110
3111        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3112            exclude_sql = self.expressions(sqls=exclude, flat=True)
3113            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
3114
3115        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
3116        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
3117        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
3118        expressions = f"{self.sep()}{expressions}" if expressions else expressions
3119        sql = self.query_modifiers(
3120            expression,
3121            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
3122            self.sql(expression, "into", comment=False),
3123            self.sql(expression, "from_", comment=False),
3124        )
3125
3126        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3127        if expression.args.get("with_"):
3128            sql = self.maybe_comment(sql, expression)
3129            expression.pop_comments()
3130
3131        sql = self.prepend_ctes(expression, sql)
3132
3133        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3134            expression.set("exclude", None)
3135            subquery = expression.subquery(copy=False)
3136            star = exp.Star(except_=exclude)
3137            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3138
3139        if not self.SUPPORTS_SELECT_INTO and into:
3140            if into.args.get("temporary"):
3141                table_kind = " TEMPORARY"
3142            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3143                table_kind = " UNLOGGED"
3144            else:
3145                table_kind = ""
3146            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3147
3148        return sql
def schema_sql(self, expression: sqlglot.expressions.query.Schema) -> str:
3150    def schema_sql(self, expression: exp.Schema) -> str:
3151        this = self.sql(expression, "this")
3152        sql = self.schema_columns_sql(expression)
3153        return f"{this} {sql}" if this and sql else this or sql
def schema_columns_sql(self, expression: sqlglot.expressions.core.Expr) -> str:
3155    def schema_columns_sql(self, expression: exp.Expr) -> str:
3156        if expression.expressions:
3157            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3158        return ""
def star_sql(self, expression: sqlglot.expressions.core.Star) -> str:
3160    def star_sql(self, expression: exp.Star) -> str:
3161        except_ = self.expressions(expression, key="except_", flat=True)
3162        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3163        replace = self.expressions(expression, key="replace", flat=True)
3164        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3165        rename = self.expressions(expression, key="rename", flat=True)
3166        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3167        return f"*{except_}{replace}{rename}"
def parameter_sql(self, expression: sqlglot.expressions.core.Parameter) -> str:
3169    def parameter_sql(self, expression: exp.Parameter) -> str:
3170        this = self.sql(expression, "this")
3171        return f"{self.PARAMETER_TOKEN}{this}"
def sessionparameter_sql(self, expression: sqlglot.expressions.core.SessionParameter) -> str:
3173    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3174        this = self.sql(expression, "this")
3175        kind = expression.text("kind")
3176        if kind:
3177            kind = f"{kind}."
3178        return f"@@{kind}{this}"
def placeholder_sql(self, expression: sqlglot.expressions.core.Placeholder) -> str:
3180    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3181        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:
3183    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3184        alias = self.sql(expression, "alias")
3185        alias = f"{sep}{alias}" if alias else ""
3186        sample = self.sql(expression, "sample")
3187        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3188            alias = f"{sample}{alias}"
3189
3190            # Set to None so it's not generated again by self.query_modifiers()
3191            expression.set("sample", None)
3192
3193        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3194        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3195        return self.prepend_ctes(expression, sql)
def qualify_sql(self, expression: sqlglot.expressions.query.Qualify) -> str:
3197    def qualify_sql(self, expression: exp.Qualify) -> str:
3198        this = self.indent(self.sql(expression, "this"))
3199        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
def unnest_sql(self, expression: sqlglot.expressions.array.Unnest) -> str:
3201    def unnest_sql(self, expression: exp.Unnest) -> str:
3202        args = self.expressions(expression, flat=True)
3203
3204        alias = expression.args.get("alias")
3205        offset = expression.args.get("offset")
3206
3207        if self.UNNEST_WITH_ORDINALITY:
3208            if alias and isinstance(offset, exp.Expr):
3209                alias.append("columns", offset)
3210                expression.set("offset", None)
3211
3212        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3213            columns = alias.columns
3214            alias = self.sql(columns[0]) if columns else ""
3215        else:
3216            alias = self.sql(alias)
3217
3218        alias = f" AS {alias}" if alias else alias
3219        if self.UNNEST_WITH_ORDINALITY:
3220            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3221        else:
3222            if isinstance(offset, exp.Expr):
3223                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3224            elif offset:
3225                suffix = f"{alias} WITH OFFSET"
3226            else:
3227                suffix = alias
3228
3229        return f"UNNEST({args}){suffix}"
def prewhere_sql(self, expression: sqlglot.expressions.query.PreWhere) -> str:
3231    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3232        return ""
def where_sql(self, expression: sqlglot.expressions.query.Where) -> str:
3234    def where_sql(self, expression: exp.Where) -> str:
3235        this = self.indent(self.sql(expression, "this"))
3236        return f"{self.seg('WHERE')}{self.sep()}{this}"
def window_sql(self, expression: sqlglot.expressions.query.Window) -> str:
3238    def window_sql(self, expression: exp.Window) -> str:
3239        this = self.sql(expression, "this")
3240        partition = self.partition_by_sql(expression)
3241        order = expression.args.get("order")
3242        order = self.order_sql(order, flat=True) if order else ""
3243        spec = self.sql(expression, "spec")
3244        alias = self.sql(expression, "alias")
3245        over = self.sql(expression, "over") or "OVER"
3246
3247        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3248
3249        first = expression.args.get("first")
3250        if first is None:
3251            first = ""
3252        else:
3253            first = "FIRST" if first else "LAST"
3254
3255        if not partition and not order and not spec and alias:
3256            return f"{this} {alias}"
3257
3258        args = self.format_args(
3259            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3260        )
3261        return f"{this} ({args})"
def partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3263    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3264        partition = self.expressions(expression, key="partition_by", flat=True)
3265        return f"PARTITION BY {partition}" if partition else ""
def windowspec_sql(self, expression: sqlglot.expressions.query.WindowSpec) -> str:
3267    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3268        kind = self.sql(expression, "kind")
3269        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3270        end = (
3271            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3272            or "CURRENT ROW"
3273        )
3274
3275        window_spec = f"{kind} BETWEEN {start} AND {end}"
3276
3277        exclude = self.sql(expression, "exclude")
3278        if exclude:
3279            if self.SUPPORTS_WINDOW_EXCLUDE:
3280                window_spec += f" EXCLUDE {exclude}"
3281            else:
3282                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3283
3284        return window_spec
def withingroup_sql(self, expression: sqlglot.expressions.core.WithinGroup) -> str:
3286    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3287        this = self.sql(expression, "this")
3288        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3289        return f"{this} WITHIN GROUP ({expression_sql})"
def between_sql(self, expression: sqlglot.expressions.core.Between) -> str:
3291    def between_sql(self, expression: exp.Between) -> str:
3292        this = self.sql(expression, "this")
3293        low = self.sql(expression, "low")
3294        high = self.sql(expression, "high")
3295        symmetric = expression.args.get("symmetric")
3296
3297        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3298            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3299
3300        flag = (
3301            " SYMMETRIC"
3302            if symmetric
3303            else " ASYMMETRIC"
3304            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3305            else ""  # silently drop ASYMMETRIC – semantics identical
3306        )
3307        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]:
3309    def bracket_offset_expressions(
3310        self, expression: exp.Bracket, index_offset: int | None = None
3311    ) -> list[exp.Expr]:
3312        if expression.args.get("json_access"):
3313            return expression.expressions
3314
3315        return apply_index_offset(
3316            expression.this,
3317            expression.expressions,
3318            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3319            dialect=self.dialect,
3320        )
def bracket_sql(self, expression: sqlglot.expressions.core.Bracket) -> str:
3322    def bracket_sql(self, expression: exp.Bracket) -> str:
3323        expressions = self.bracket_offset_expressions(expression)
3324        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3325        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
def all_sql(self, expression: sqlglot.expressions.core.All) -> str:
3327    def all_sql(self, expression: exp.All) -> str:
3328        this = self.sql(expression, "this")
3329        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3330            this = self.wrap(this)
3331        return f"ALL {this}"
def any_sql(self, expression: sqlglot.expressions.core.Any) -> str:
3333    def any_sql(self, expression: exp.Any) -> str:
3334        this = self.sql(expression, "this")
3335        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3336            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3337                this = self.wrap(this)
3338            return f"ANY{this}"
3339        return f"ANY {this}"
def exists_sql(self, expression: sqlglot.expressions.functions.Exists) -> str:
3341    def exists_sql(self, expression: exp.Exists) -> str:
3342        return f"EXISTS{self.wrap(expression)}"
def case_sql(self, expression: sqlglot.expressions.functions.Case) -> str:
3344    def case_sql(self, expression: exp.Case) -> str:
3345        this = self.sql(expression, "this")
3346        statements = [f"CASE {this}" if this else "CASE"]
3347
3348        for e in expression.args["ifs"]:
3349            statements.append(f"WHEN {self.sql(e, 'this')}")
3350            statements.append(f"THEN {self.sql(e, 'true')}")
3351
3352        default = self.sql(expression, "default")
3353
3354        if default:
3355            statements.append(f"ELSE {default}")
3356
3357        statements.append("END")
3358
3359        if self.pretty and self.too_wide(statements):
3360            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3361
3362        return " ".join(statements)
def constraint_sql(self, expression: sqlglot.expressions.constraints.Constraint) -> str:
3364    def constraint_sql(self, expression: exp.Constraint) -> str:
3365        this = self.sql(expression, "this")
3366        expressions = self.expressions(expression, flat=True)
3367        return f"CONSTRAINT {this} {expressions}"
def nextvaluefor_sql(self, expression: sqlglot.expressions.ddl.NextValueFor) -> str:
3369    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3370        order = expression.args.get("order")
3371        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3372        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
def extract_sql(self, expression: sqlglot.expressions.temporal.Extract) -> str:
3374    def extract_sql(self, expression: exp.Extract) -> str:
3375        import sqlglot.dialects.dialect
3376
3377        this = (
3378            sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect)
3379            if self.NORMALIZE_EXTRACT_DATE_PARTS
3380            else expression.this
3381        )
3382        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3383        expression_sql = self.sql(expression, "expression")
3384
3385        return f"EXTRACT({this_sql} FROM {expression_sql})"
def trim_sql(self, expression: sqlglot.expressions.string.Trim) -> str:
3387    def trim_sql(self, expression: exp.Trim) -> str:
3388        trim_type = self.sql(expression, "position")
3389
3390        if trim_type == "LEADING":
3391            func_name = "LTRIM"
3392        elif trim_type == "TRAILING":
3393            func_name = "RTRIM"
3394        else:
3395            func_name = "TRIM"
3396
3397        return self.func(func_name, expression.this, expression.expression)
def convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3399    def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]:
3400        args = expression.expressions
3401        if isinstance(expression, exp.ConcatWs):
3402            args = args[1:]  # Skip the delimiter
3403
3404        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3405            args = [exp.cast(e, exp.DType.TEXT) for e in args]
3406
3407        concat_coalesce = (
3408            self.dialect.CONCAT_WS_COALESCE
3409            if isinstance(expression, exp.ConcatWs)
3410            else self.dialect.CONCAT_COALESCE
3411        )
3412
3413        if not concat_coalesce and expression.args.get("coalesce"):
3414
3415            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:
3416                if not e.type:
3417                    import sqlglot.optimizer.annotate_types
3418
3419                    e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect)
3420
3421                if e.is_string or e.is_type(exp.DType.ARRAY):
3422                    return e
3423
3424                return exp.func("coalesce", e, exp.Literal.string(""))
3425
3426            args = [_wrap_with_coalesce(e) for e in args]
3427
3428        return args
def concat_sql(self, expression: sqlglot.expressions.string.Concat) -> str:
3430    def concat_sql(self, expression: exp.Concat) -> str:
3431        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3432            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3433            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3434            # instead of coalescing them to empty string.
3435            import sqlglot.dialects.dialect
3436
3437            return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression)
3438
3439        expressions = self.convert_concat_args(expression)
3440
3441        # Some dialects don't allow a single-argument CONCAT call
3442        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3443            return self.sql(expressions[0])
3444
3445        return self.func("CONCAT", *expressions)
def concatws_sql(self, expression: sqlglot.expressions.string.ConcatWs) -> str:
3447    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3448        if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"):
3449            # Dialect's CONCAT_WS function skips NULL args, but the expression does not.
3450            # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL.
3451            all_args = expression.expressions
3452            expression.set("coalesce", True)
3453            return self.sql(
3454                exp.case()
3455                .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null())
3456                .else_(expression)
3457            )
3458
3459        return self.func(
3460            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3461        )
def check_sql(self, expression: sqlglot.expressions.core.Check) -> str:
3463    def check_sql(self, expression: exp.Check) -> str:
3464        this = self.sql(expression, key="this")
3465        return f"CHECK ({this})"
def foreignkey_sql(self, expression: sqlglot.expressions.constraints.ForeignKey) -> str:
3467    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3468        expressions = self.expressions(expression, flat=True)
3469        expressions = f" ({expressions})" if expressions else ""
3470        reference = self.sql(expression, "reference")
3471        reference = f" {reference}" if reference else ""
3472        delete = self.sql(expression, "delete")
3473        delete = f" ON DELETE {delete}" if delete else ""
3474        update = self.sql(expression, "update")
3475        update = f" ON UPDATE {update}" if update else ""
3476        options = self.expressions(expression, key="options", flat=True, sep=" ")
3477        options = f" {options}" if options else ""
3478        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
def primarykey_sql(self, expression: sqlglot.expressions.constraints.PrimaryKey) -> str:
3480    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3481        this = self.sql(expression, "this")
3482        this = f" {this}" if this else ""
3483        expressions = self.expressions(expression, flat=True)
3484        include = self.sql(expression, "include")
3485        options = self.expressions(expression, key="options", flat=True, sep=" ")
3486        options = f" {options}" if options else ""
3487        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
def if_sql(self, expression: sqlglot.expressions.functions.If) -> str:
3489    def if_sql(self, expression: exp.If) -> str:
3490        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
def matchagainst_sql(self, expression: sqlglot.expressions.string.MatchAgainst) -> str:
3492    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3493        if self.MATCH_AGAINST_TABLE_PREFIX:
3494            expressions = []
3495            for expr in expression.expressions:
3496                if isinstance(expr, exp.Table):
3497                    expressions.append(f"TABLE {self.sql(expr)}")
3498                else:
3499                    expressions.append(expr)
3500        else:
3501            expressions = expression.expressions
3502
3503        modifier = expression.args.get("modifier")
3504        modifier = f" {modifier}" if modifier else ""
3505        return (
3506            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3507        )
def jsonkeyvalue_sql(self, expression: sqlglot.expressions.query.JSONKeyValue) -> str:
3509    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3510        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:
3512    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3513        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3514
3515        if expression.args.get("escape"):
3516            path = self.escape_str(path)
3517
3518        if self.QUOTE_JSON_PATH:
3519            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3520
3521        return path
def json_path_part( self, expression: int | str | sqlglot.expressions.query.JSONPathPart) -> str:
3523    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3524        if isinstance(expression, exp.JSONPathPart):
3525            transform = self.TRANSFORMS.get(expression.__class__)
3526            if not callable(transform):
3527                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3528                return ""
3529
3530            return transform(self, expression)
3531
3532        if isinstance(expression, int):
3533            return str(expression)
3534
3535        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3536            escaped = expression.replace("'", "\\'")
3537            escaped = f"\\'{expression}\\'"
3538        else:
3539            escaped = expression.replace('"', '\\"')
3540            escaped = f'"{escaped}"'
3541
3542        return escaped
def formatjson_sql(self, expression: sqlglot.expressions.query.FormatJson) -> str:
3544    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3545        return f"{self.sql(expression, 'this')} FORMAT JSON"
def formatphrase_sql(self, expression: sqlglot.expressions.core.FormatPhrase) -> str:
3547    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3548        # Output the Teradata column FORMAT override.
3549        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3550        this = self.sql(expression, "this")
3551        fmt = self.sql(expression, "format")
3552        return f"{this} (FORMAT {fmt})"
def jsonarray_sql(self, expression: sqlglot.expressions.json.JSONArray) -> str:
3580    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
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_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3588        )
def jsonarrayagg_sql(self, expression: sqlglot.expressions.json.JSONArrayAgg) -> str:
3590    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3591        this = self.sql(expression, "this")
3592        order = self.sql(expression, "order")
3593        null_handling = expression.args.get("null_handling")
3594        null_handling = f" {null_handling}" if null_handling else ""
3595        return_type = self.sql(expression, "return_type")
3596        return_type = f" RETURNING {return_type}" if return_type else ""
3597        strict = " STRICT" if expression.args.get("strict") else ""
3598        return self.func(
3599            "JSON_ARRAYAGG",
3600            this,
3601            suffix=f"{order}{null_handling}{return_type}{strict})",
3602        )
def jsoncolumndef_sql(self, expression: sqlglot.expressions.query.JSONColumnDef) -> str:
3604    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3605        path = self.sql(expression, "path")
3606        path = f" PATH {path}" if path else ""
3607        nested_schema = self.sql(expression, "nested_schema")
3608
3609        if nested_schema:
3610            return f"NESTED{path} {nested_schema}"
3611
3612        this = self.sql(expression, "this")
3613        kind = self.sql(expression, "kind")
3614        kind = f" {kind}" if kind else ""
3615        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
3616
3617        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3618        return f"{this}{kind}{format_json}{path}{ordinality}"
def jsonschema_sql(self, expression: sqlglot.expressions.query.JSONSchema) -> str:
3620    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3621        return self.func("COLUMNS", *expression.expressions)
def jsontable_sql(self, expression: sqlglot.expressions.json.JSONTable) -> str:
3623    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3624        this = self.sql(expression, "this")
3625        path = self.sql(expression, "path")
3626        path = f", {path}" if path else ""
3627        error_handling = expression.args.get("error_handling")
3628        error_handling = f" {error_handling}" if error_handling else ""
3629        empty_handling = expression.args.get("empty_handling")
3630        empty_handling = f" {empty_handling}" if empty_handling else ""
3631        schema = self.sql(expression, "schema")
3632        return self.func(
3633            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3634        )
def openjsoncolumndef_sql(self, expression: sqlglot.expressions.query.OpenJSONColumnDef) -> str:
3636    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3637        this = self.sql(expression, "this")
3638        kind = self.sql(expression, "kind")
3639        path = self.sql(expression, "path")
3640        path = f" {path}" if path else ""
3641        as_json = " AS JSON" if expression.args.get("as_json") else ""
3642        return f"{this} {kind}{path}{as_json}"
def openjson_sql(self, expression: sqlglot.expressions.json.OpenJSON) -> str:
3644    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3645        this = self.sql(expression, "this")
3646        path = self.sql(expression, "path")
3647        path = f", {path}" if path else ""
3648        expressions = self.expressions(expression)
3649        with_ = (
3650            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3651            if expressions
3652            else ""
3653        )
3654        return f"OPENJSON({this}{path}){with_}"
def in_sql(self, expression: sqlglot.expressions.core.In) -> str:
3656    def in_sql(self, expression: exp.In) -> str:
3657        query = expression.args.get("query")
3658        unnest = expression.args.get("unnest")
3659        field = expression.args.get("field")
3660        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3661
3662        if query:
3663            in_sql = self.sql(query)
3664        elif unnest:
3665            in_sql = self.in_unnest_op(unnest)
3666        elif field:
3667            in_sql = self.sql(field)
3668        else:
3669            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3670
3671        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
def in_unnest_op(self, unnest: sqlglot.expressions.array.Unnest) -> str:
3673    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3674        return f"(SELECT {self.sql(unnest)})"
def interval_sql(self, expression: sqlglot.expressions.datatypes.Interval) -> str:
3676    def interval_sql(self, expression: exp.Interval) -> str:
3677        unit_expression = expression.args.get("unit")
3678        unit = self.sql(unit_expression) if unit_expression else ""
3679        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3680            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3681        unit = f" {unit}" if unit else ""
3682
3683        if self.SINGLE_STRING_INTERVAL:
3684            this = expression.this.name if expression.this else ""
3685            if this:
3686                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3687                    return f"INTERVAL '{this}'{unit}"
3688                return f"INTERVAL '{this}{unit}'"
3689            return f"INTERVAL{unit}"
3690
3691        this = self.sql(expression, "this")
3692        if this:
3693            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3694            this = f" {this}" if unwrapped else f" ({this})"
3695
3696        return f"INTERVAL{this}{unit}"
def return_sql(self, expression: sqlglot.expressions.query.Return) -> str:
3698    def return_sql(self, expression: exp.Return) -> str:
3699        return f"RETURN {self.sql(expression, 'this')}"
def reference_sql(self, expression: sqlglot.expressions.constraints.Reference) -> str:
3701    def reference_sql(self, expression: exp.Reference) -> str:
3702        this = self.sql(expression, "this")
3703        expressions = self.expressions(expression, flat=True)
3704        expressions = f"({expressions})" if expressions else ""
3705        options = self.expressions(expression, key="options", flat=True, sep=" ")
3706        options = f" {options}" if options else ""
3707        return f"REFERENCES {this}{expressions}{options}"
def anonymous_sql(self, expression: sqlglot.expressions.core.Anonymous) -> str:
3709    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3710        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3711        parent = expression.parent
3712        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3713
3714        return self.func(
3715            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3716        )
def paren_sql(self, expression: sqlglot.expressions.core.Paren) -> str:
3718    def paren_sql(self, expression: exp.Paren) -> str:
3719        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3720        return f"({sql}{self.seg(')', sep='')}"
def neg_sql(self, expression: sqlglot.expressions.core.Neg) -> str:
3722    def neg_sql(self, expression: exp.Neg) -> str:
3723        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3724        this_sql = self.sql(expression, "this")
3725        sep = " " if this_sql[0] == "-" else ""
3726        return f"-{sep}{this_sql}"
def not_sql(self, expression: sqlglot.expressions.core.Not) -> str:
3728    def not_sql(self, expression: exp.Not) -> str:
3729        return f"NOT {self.sql(expression, 'this')}"
def alias_sql(self, expression: sqlglot.expressions.core.Alias) -> str:
3731    def alias_sql(self, expression: exp.Alias) -> str:
3732        alias = self.sql(expression, "alias")
3733        alias = f" AS {alias}" if alias else ""
3734        return f"{self.sql(expression, 'this')}{alias}"
def pivotalias_sql(self, expression: sqlglot.expressions.core.PivotAlias) -> str:
3736    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3737        alias = expression.args["alias"]
3738
3739        parent = expression.parent
3740        pivot = parent and parent.parent
3741
3742        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3743            identifier_alias = isinstance(alias, exp.Identifier)
3744            literal_alias = isinstance(alias, exp.Literal)
3745
3746            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3747                alias.replace(exp.Literal.string(alias.output_name))
3748            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3749                alias.replace(exp.to_identifier(alias.output_name))
3750
3751        return self.alias_sql(expression)
def aliases_sql(self, expression: sqlglot.expressions.core.Aliases) -> str:
3753    def aliases_sql(self, expression: exp.Aliases) -> str:
3754        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
def atindex_sql(self, expression: sqlglot.expressions.core.AtIndex) -> str:
3756    def atindex_sql(self, expression: exp.AtIndex) -> str:
3757        this = self.sql(expression, "this")
3758        index = self.sql(expression, "expression")
3759        return f"{this} AT {index}"
def attimezone_sql(self, expression: sqlglot.expressions.core.AtTimeZone) -> str:
3761    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3762        this = self.sql(expression, "this")
3763        zone = self.sql(expression, "zone")
3764        return f"{this} AT TIME ZONE {zone}"
def fromtimezone_sql(self, expression: sqlglot.expressions.core.FromTimeZone) -> str:
3766    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3767        this = self.sql(expression, "this")
3768        zone = self.sql(expression, "zone")
3769        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
def add_sql(self, expression: sqlglot.expressions.core.Add) -> str:
3771    def add_sql(self, expression: exp.Add) -> str:
3772        return self.binary(expression, "+")
def and_sql( self, expression: sqlglot.expressions.core.And, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str: