Edit on GitHub

sqlglot.generator

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

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

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

Apply generic preprocessing transformations to a given expression.

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