Edit on GitHub

sqlglot.generator

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

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

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

Apply generic preprocessing transformations to a given expression.

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