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

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

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

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: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = 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)
750    def __init__(
751        self,
752        pretty: t.Optional[bool] = None,
753        identify: str | bool = False,
754        normalize: bool = False,
755        pad: int = 2,
756        indent: int = 2,
757        normalize_functions: t.Optional[str | bool] = None,
758        unsupported_level: ErrorLevel = ErrorLevel.WARN,
759        max_unsupported: int = 3,
760        leading_comma: bool = False,
761        max_text_width: int = 80,
762        comments: bool = True,
763        dialect: DialectType = None,
764    ):
765        import sqlglot
766        from sqlglot.dialects import Dialect
767
768        self.pretty = pretty if pretty is not None else sqlglot.pretty
769        self.identify = identify
770        self.normalize = normalize
771        self.pad = pad
772        self._indent = indent
773        self.unsupported_level = unsupported_level
774        self.max_unsupported = max_unsupported
775        self.leading_comma = leading_comma
776        self.max_text_width = max_text_width
777        self.comments = comments
778        self.dialect = Dialect.get_or_raise(dialect)
779
780        # This is both a Dialect property and a Generator argument, so we prioritize the latter
781        self.normalize_functions = (
782            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
783        )
784
785        self.unsupported_messages: t.List[str] = []
786        self._escaped_quote_end: str = (
787            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
788        )
789        self._escaped_byte_quote_end: str = (
790            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
791            if self.dialect.BYTE_END
792            else ""
793        )
794        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
795
796        self._next_name = name_sequence("_t")
797
798        self._identifier_start = self.dialect.IDENTIFIER_START
799        self._identifier_end = self.dialect.IDENTIFIER_END
800
801        self._quote_json_path_key_using_brackets = True
TRANSFORMS: Dict[Type[sqlglot.expressions.Expression], Callable[..., str]] = {<class 'sqlglot.expressions.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.Adjacent'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SessionUser'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExtendsRight'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ZeroFillColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ForceProperty'>: <function Generator.<lambda>>}
NULL_ORDERING_SUPPORTED: Optional[bool] = True
IGNORE_NULLS_IN_FUNC = False
LOCKING_READS_SUPPORTED = False
EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
WRAP_DERIVED_VALUES = True
CREATE_FUNCTION_RETURN_AS = True
MATCHED_BY_SOURCE = True
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'
JOIN_HINTS = True
TABLE_HINTS = True
QUERY_HINTS = True
QUERY_HINT_SEP = ', '
IS_BOOL_ALLOWED = True
DUPLICATE_KEY_UPDATE_WITH_SET = True
LIMIT_IS_TOP = False
RETURNING_END = True
EXTRACT_ALLOWS_QUOTES = True
TZ_TO_WITH_TIME_ZONE = False
NVL2_SUPPORTED = True
SELECT_KINDS: Tuple[str, ...] = ('STRUCT', 'VALUE')
VALUES_AS_TABLE = True
ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
UNNEST_WITH_ORDINALITY = True
AGGREGATE_FILTER_SUPPORTED = True
SEMI_ANTI_JOIN_WITH_SIDE = True
COMPUTED_COLUMN_WITH_TYPE = True
SUPPORTS_TABLE_COPY = True
TABLESAMPLE_REQUIRES_PARENS = True
TABLESAMPLE_SIZE_IS_ROWS = True
TABLESAMPLE_KEYWORDS = 'TABLESAMPLE'
TABLESAMPLE_WITH_METHOD = True
TABLESAMPLE_SEED_KEYWORD = 'SEED'
COLLATE_IS_FUNC = False
DATA_TYPE_SPECIFIERS_ALLOWED = False
ENSURE_BOOLS = False
CTE_RECURSIVE_KEYWORD_REQUIRED = True
SUPPORTS_SINGLE_ARG_CONCAT = True
LAST_DAY_SUPPORTS_DATE_PART = True
SUPPORTS_TABLE_ALIAS_COLUMNS = True
UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
JSON_KEY_VALUE_PAIR_SEP = ':'
INSERT_OVERWRITE = ' OVERWRITE TABLE'
SUPPORTS_SELECT_INTO = False
SUPPORTS_UNLOGGED_TABLES = False
SUPPORTS_CREATE_TABLE_LIKE = True
LIKE_PROPERTY_INSIDE_SCHEMA = False
MULTI_ARG_DISTINCT = True
JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
JSON_PATH_BRACKETED_KEY_SUPPORTED = True
JSON_PATH_SINGLE_QUOTE_ESCAPE = False
CAN_IMPLEMENT_ARRAY_ANY = False
SUPPORTS_TO_NUMBER = True
SUPPORTS_WINDOW_EXCLUDE = False
SET_OP_MODIFIERS = True
COPY_PARAMS_ARE_WRAPPED = True
COPY_PARAMS_EQ_REQUIRED = False
COPY_HAS_INTO_KEYWORD = True
TRY_SUPPORTED = True
SUPPORTS_UESCAPE = True
UNICODE_SUBSTITUTE: Optional[Callable[[re.Match[str]], str]] = 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: Optional[str] = 'PARSE_JSON'
ARRAY_SIZE_NAME: str = 'ARRAY_LENGTH'
ALTER_SET_TYPE = 'SET DATA TYPE'
ARRAY_SIZE_DIM_REQUIRED: Optional[bool] = None
SUPPORTS_DECODE_CASE = True
SUPPORTS_BETWEEN_FLAGS = False
SUPPORTS_LIKE_QUANTIFIERS = True
MATCH_AGAINST_TABLE_PREFIX: Optional[str] = None
SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
UPDATE_STATEMENT_SUPPORTS_FROM = True
TYPE_MAPPING = {<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
UNSUPPORTED_TYPES: set[sqlglot.expressions.DataType.Type] = set()
TIME_PART_SINGULARS = {'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS = {'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
TOKEN_MAPPING: Dict[sqlglot.tokens.TokenType, str] = {}
STRUCT_DELIMITER = ('<', '>')
PARAMETER_TOKEN = '@'
NAMED_PLACEHOLDER_TOKEN = ':'
EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: Set[str] = set()
PROPERTIES_LOCATION = {<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
RESERVED_KEYWORDS: Set[str] = set()
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES = {<Type.NVARCHAR: 'NVARCHAR'>, <Type.CHAR: 'CHAR'>, <Type.NCHAR: 'NCHAR'>, <Type.VARCHAR: 'VARCHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: Set[Type[sqlglot.expressions.Expression]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: Tuple[Type[sqlglot.expressions.Expression], ...] = ()
SAFE_JSON_PATH_KEY_RE = 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.Expression, copy: bool = True) -> str:
803    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
804        """
805        Generates the SQL string corresponding to the given syntax tree.
806
807        Args:
808            expression: The syntax tree.
809            copy: Whether to copy the expression. The generator performs mutations so
810                it is safer to copy.
811
812        Returns:
813            The SQL string corresponding to `expression`.
814        """
815        if copy:
816            expression = expression.copy()
817
818        expression = self.preprocess(expression)
819
820        self.unsupported_messages = []
821        sql = self.sql(expression).strip()
822
823        if self.pretty:
824            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
825
826        if self.unsupported_level == ErrorLevel.IGNORE:
827            return sql
828
829        if self.unsupported_level == ErrorLevel.WARN:
830            for msg in self.unsupported_messages:
831                logger.warning(msg)
832        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
833            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
834
835        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.Expression) -> sqlglot.expressions.Expression:
837    def preprocess(self, expression: exp.Expression) -> exp.Expression:
838        """Apply generic preprocessing transformations to a given expression."""
839        expression = self._move_ctes_to_top_level(expression)
840
841        if self.ENSURE_BOOLS:
842            from sqlglot.transforms import ensure_bools
843
844            expression = ensure_bools(expression)
845
846        return expression

Apply generic preprocessing transformations to a given expression.

def unsupported(self, message: str) -> None:
859    def unsupported(self, message: str) -> None:
860        if self.unsupported_level == ErrorLevel.IMMEDIATE:
861            raise UnsupportedError(message)
862        self.unsupported_messages.append(message)
def sep(self, sep: str = ' ') -> str:
864    def sep(self, sep: str = " ") -> str:
865        return f"{sep.strip()}\n" if self.pretty else sep
def seg(self, sql: str, sep: str = ' ') -> str:
867    def seg(self, sql: str, sep: str = " ") -> str:
868        return f"{self.sep(sep)}{sql}"
def sanitize_comment(self, comment: str) -> str:
870    def sanitize_comment(self, comment: str) -> str:
871        comment = " " + comment if comment[0].strip() else comment
872        comment = comment + " " if comment[-1].strip() else comment
873
874        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
875            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
876            comment = comment.replace("*/", "* /")
877
878        return comment
def maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
880    def maybe_comment(
881        self,
882        sql: str,
883        expression: t.Optional[exp.Expression] = None,
884        comments: t.Optional[t.List[str]] = None,
885        separated: bool = False,
886    ) -> str:
887        comments = (
888            ((expression and expression.comments) if comments is None else comments)  # type: ignore
889            if self.comments
890            else None
891        )
892
893        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
894            return sql
895
896        comments_sql = " ".join(
897            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
898        )
899
900        if not comments_sql:
901            return sql
902
903        comments_sql = self._replace_line_breaks(comments_sql)
904
905        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
906            return (
907                f"{self.sep()}{comments_sql}{sql}"
908                if not sql or sql[0].isspace()
909                else f"{comments_sql}{self.sep()}{sql}"
910            )
911
912        return f"{sql} {comments_sql}"
def wrap(self, expression: sqlglot.expressions.Expression | str) -> str:
914    def wrap(self, expression: exp.Expression | str) -> str:
915        this_sql = (
916            self.sql(expression)
917            if isinstance(expression, exp.UNWRAPPED_QUERIES)
918            else self.sql(expression, "this")
919        )
920        if not this_sql:
921            return "()"
922
923        this_sql = self.indent(this_sql, level=1, pad=0)
924        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def no_identify(self, func: Callable[..., str], *args, **kwargs) -> str:
926    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
927        original = self.identify
928        self.identify = False
929        result = func(*args, **kwargs)
930        self.identify = original
931        return result
def normalize_func(self, name: str) -> str:
933    def normalize_func(self, name: str) -> str:
934        if self.normalize_functions == "upper" or self.normalize_functions is True:
935            return name.upper()
936        if self.normalize_functions == "lower":
937            return name.lower()
938        return name
def indent( self, sql: str, level: int = 0, pad: Optional[int] = None, skip_first: bool = False, skip_last: bool = False) -> str:
940    def indent(
941        self,
942        sql: str,
943        level: int = 0,
944        pad: t.Optional[int] = None,
945        skip_first: bool = False,
946        skip_last: bool = False,
947    ) -> str:
948        if not self.pretty or not sql:
949            return sql
950
951        pad = self.pad if pad is None else pad
952        lines = sql.split("\n")
953
954        return "\n".join(
955            (
956                line
957                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
958                else f"{' ' * (level * self._indent + pad)}{line}"
959            )
960            for i, line in enumerate(lines)
961        )
def sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
963    def sql(
964        self,
965        expression: t.Optional[str | exp.Expression],
966        key: t.Optional[str] = None,
967        comment: bool = True,
968    ) -> str:
969        if not expression:
970            return ""
971
972        if isinstance(expression, str):
973            return expression
974
975        if key:
976            value = expression.args.get(key)
977            if value:
978                return self.sql(value)
979            return ""
980
981        transform = self.TRANSFORMS.get(expression.__class__)
982
983        if callable(transform):
984            sql = transform(self, expression)
985        elif isinstance(expression, exp.Expression):
986            exp_handler_name = f"{expression.key}_sql"
987
988            if hasattr(self, exp_handler_name):
989                sql = getattr(self, exp_handler_name)(expression)
990            elif isinstance(expression, exp.Func):
991                sql = self.function_fallback_sql(expression)
992            elif isinstance(expression, exp.Property):
993                sql = self.property_sql(expression)
994            else:
995                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
996        else:
997            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
998
999        return self.maybe_comment(sql, expression) if self.comments and comment else sql
def uncache_sql(self, expression: sqlglot.expressions.Uncache) -> str:
1001    def uncache_sql(self, expression: exp.Uncache) -> str:
1002        table = self.sql(expression, "this")
1003        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1004        return f"UNCACHE TABLE{exists_sql} {table}"
def cache_sql(self, expression: sqlglot.expressions.Cache) -> str:
1006    def cache_sql(self, expression: exp.Cache) -> str:
1007        lazy = " LAZY" if expression.args.get("lazy") else ""
1008        table = self.sql(expression, "this")
1009        options = expression.args.get("options")
1010        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1011        sql = self.sql(expression, "expression")
1012        sql = f" AS{self.sep()}{sql}" if sql else ""
1013        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1014        return self.prepend_ctes(expression, sql)
def characterset_sql(self, expression: sqlglot.expressions.CharacterSet) -> str:
1016    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1017        if isinstance(expression.parent, exp.Cast):
1018            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
1019        default = "DEFAULT " if expression.args.get("default") else ""
1020        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
def column_parts(self, expression: sqlglot.expressions.Column) -> str:
1022    def column_parts(self, expression: exp.Column) -> str:
1023        return ".".join(
1024            self.sql(part)
1025            for part in (
1026                expression.args.get("catalog"),
1027                expression.args.get("db"),
1028                expression.args.get("table"),
1029                expression.args.get("this"),
1030            )
1031            if part
1032        )
def column_sql(self, expression: sqlglot.expressions.Column) -> str:
1034    def column_sql(self, expression: exp.Column) -> str:
1035        join_mark = " (+)" if expression.args.get("join_mark") else ""
1036
1037        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1038            join_mark = ""
1039            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1040
1041        return f"{self.column_parts(expression)}{join_mark}"
def pseudocolumn_sql(self, expression: sqlglot.expressions.Pseudocolumn) -> str:
1043    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1044        return self.column_sql(expression)
def columnposition_sql(self, expression: sqlglot.expressions.ColumnPosition) -> str:
1046    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1047        this = self.sql(expression, "this")
1048        this = f" {this}" if this else ""
1049        position = self.sql(expression, "position")
1050        return f"{position}{this}"
def columndef_sql(self, expression: sqlglot.expressions.ColumnDef, sep: str = ' ') -> str:
1052    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1053        column = self.sql(expression, "this")
1054        kind = self.sql(expression, "kind")
1055        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1056        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1057        kind = f"{sep}{kind}" if kind else ""
1058        constraints = f" {constraints}" if constraints else ""
1059        position = self.sql(expression, "position")
1060        position = f" {position}" if position else ""
1061
1062        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1063            kind = ""
1064
1065        return f"{exists}{column}{kind}{constraints}{position}"
def columnconstraint_sql(self, expression: sqlglot.expressions.ColumnConstraint) -> str:
1067    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1068        this = self.sql(expression, "this")
1069        kind_sql = self.sql(expression, "kind").strip()
1070        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
def computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1072    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1073        this = self.sql(expression, "this")
1074        if expression.args.get("not_null"):
1075            persisted = " PERSISTED NOT NULL"
1076        elif expression.args.get("persisted"):
1077            persisted = " PERSISTED"
1078        else:
1079            persisted = ""
1080
1081        return f"AS {this}{persisted}"
def autoincrementcolumnconstraint_sql(self, _) -> str:
1083    def autoincrementcolumnconstraint_sql(self, _) -> str:
1084        return self.token_sql(TokenType.AUTO_INCREMENT)
def compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
1086    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1087        if isinstance(expression.this, list):
1088            this = self.wrap(self.expressions(expression, key="this", flat=True))
1089        else:
1090            this = self.sql(expression, "this")
1091
1092        return f"COMPRESS {this}"
def generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1094    def generatedasidentitycolumnconstraint_sql(
1095        self, expression: exp.GeneratedAsIdentityColumnConstraint
1096    ) -> str:
1097        this = ""
1098        if expression.this is not None:
1099            on_null = " ON NULL" if expression.args.get("on_null") else ""
1100            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1101
1102        start = expression.args.get("start")
1103        start = f"START WITH {start}" if start else ""
1104        increment = expression.args.get("increment")
1105        increment = f" INCREMENT BY {increment}" if increment else ""
1106        minvalue = expression.args.get("minvalue")
1107        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1108        maxvalue = expression.args.get("maxvalue")
1109        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1110        cycle = expression.args.get("cycle")
1111        cycle_sql = ""
1112
1113        if cycle is not None:
1114            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1115            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1116
1117        sequence_opts = ""
1118        if start or increment or cycle_sql:
1119            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1120            sequence_opts = f" ({sequence_opts.strip()})"
1121
1122        expr = self.sql(expression, "expression")
1123        expr = f"({expr})" if expr else "IDENTITY"
1124
1125        return f"GENERATED{this} AS {expr}{sequence_opts}"
def generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1127    def generatedasrowcolumnconstraint_sql(
1128        self, expression: exp.GeneratedAsRowColumnConstraint
1129    ) -> str:
1130        start = "START" if expression.args.get("start") else "END"
1131        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1132        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
def periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.PeriodForSystemTimeConstraint) -> str:
1134    def periodforsystemtimeconstraint_sql(
1135        self, expression: exp.PeriodForSystemTimeConstraint
1136    ) -> str:
1137        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
def notnullcolumnconstraint_sql(self, expression: sqlglot.expressions.NotNullColumnConstraint) -> str:
1139    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1140        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
def primarykeycolumnconstraint_sql(self, expression: sqlglot.expressions.PrimaryKeyColumnConstraint) -> str:
1142    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1143        desc = expression.args.get("desc")
1144        if desc is not None:
1145            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1146        options = self.expressions(expression, key="options", flat=True, sep=" ")
1147        options = f" {options}" if options else ""
1148        return f"PRIMARY KEY{options}"
def uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1150    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1151        this = self.sql(expression, "this")
1152        this = f" {this}" if this else ""
1153        index_type = expression.args.get("index_type")
1154        index_type = f" USING {index_type}" if index_type else ""
1155        on_conflict = self.sql(expression, "on_conflict")
1156        on_conflict = f" {on_conflict}" if on_conflict else ""
1157        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1158        options = self.expressions(expression, key="options", flat=True, sep=" ")
1159        options = f" {options}" if options else ""
1160        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def inoutcolumnconstraint_sql(self, expression: sqlglot.expressions.InOutColumnConstraint) -> str:
1162    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1163        input_ = expression.args.get("input_")
1164        output = expression.args.get("output")
1165
1166        if input_ and output:
1167            return "IN OUT"
1168        if input_:
1169            return "IN"
1170        if output:
1171            return "OUT"
1172
1173        return ""
def createable_sql( self, expression: sqlglot.expressions.Create, locations: DefaultDict) -> str:
1175    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1176        return self.sql(expression, "this")
def create_sql(self, expression: sqlglot.expressions.Create) -> str:
1178    def create_sql(self, expression: exp.Create) -> str:
1179        kind = self.sql(expression, "kind")
1180        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1181        properties = expression.args.get("properties")
1182        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1183
1184        this = self.createable_sql(expression, properties_locs)
1185
1186        properties_sql = ""
1187        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1188            exp.Properties.Location.POST_WITH
1189        ):
1190            props_ast = exp.Properties(
1191                expressions=[
1192                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1193                    *properties_locs[exp.Properties.Location.POST_WITH],
1194                ]
1195            )
1196            props_ast.parent = expression
1197            properties_sql = self.sql(props_ast)
1198
1199            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1200                properties_sql = self.sep() + properties_sql
1201            elif not self.pretty:
1202                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1203                properties_sql = f" {properties_sql}"
1204
1205        begin = " BEGIN" if expression.args.get("begin") else ""
1206        end = " END" if expression.args.get("end") else ""
1207
1208        expression_sql = self.sql(expression, "expression")
1209        if expression_sql:
1210            expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
1211
1212            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1213                postalias_props_sql = ""
1214                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1215                    postalias_props_sql = self.properties(
1216                        exp.Properties(
1217                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1218                        ),
1219                        wrapped=False,
1220                    )
1221                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1222                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1223
1224        postindex_props_sql = ""
1225        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1226            postindex_props_sql = self.properties(
1227                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1228                wrapped=False,
1229                prefix=" ",
1230            )
1231
1232        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1233        indexes = f" {indexes}" if indexes else ""
1234        index_sql = indexes + postindex_props_sql
1235
1236        replace = " OR REPLACE" if expression.args.get("replace") else ""
1237        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1238        unique = " UNIQUE" if expression.args.get("unique") else ""
1239
1240        clustered = expression.args.get("clustered")
1241        if clustered is None:
1242            clustered_sql = ""
1243        elif clustered:
1244            clustered_sql = " CLUSTERED COLUMNSTORE"
1245        else:
1246            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1247
1248        postcreate_props_sql = ""
1249        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1250            postcreate_props_sql = self.properties(
1251                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1252                sep=" ",
1253                prefix=" ",
1254                wrapped=False,
1255            )
1256
1257        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1258
1259        postexpression_props_sql = ""
1260        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1261            postexpression_props_sql = self.properties(
1262                exp.Properties(
1263                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1264                ),
1265                sep=" ",
1266                prefix=" ",
1267                wrapped=False,
1268            )
1269
1270        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1271        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1272        no_schema_binding = (
1273            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1274        )
1275
1276        clone = self.sql(expression, "clone")
1277        clone = f" {clone}" if clone else ""
1278
1279        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1280            properties_expression = f"{expression_sql}{properties_sql}"
1281        else:
1282            properties_expression = f"{properties_sql}{expression_sql}"
1283
1284        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1285        return self.prepend_ctes(expression, expression_sql)
def sequenceproperties_sql(self, expression: sqlglot.expressions.SequenceProperties) -> str:
1287    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1288        start = self.sql(expression, "start")
1289        start = f"START WITH {start}" if start else ""
1290        increment = self.sql(expression, "increment")
1291        increment = f" INCREMENT BY {increment}" if increment else ""
1292        minvalue = self.sql(expression, "minvalue")
1293        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1294        maxvalue = self.sql(expression, "maxvalue")
1295        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1296        owned = self.sql(expression, "owned")
1297        owned = f" OWNED BY {owned}" if owned else ""
1298
1299        cache = expression.args.get("cache")
1300        if cache is None:
1301            cache_str = ""
1302        elif cache is True:
1303            cache_str = " CACHE"
1304        else:
1305            cache_str = f" CACHE {cache}"
1306
1307        options = self.expressions(expression, key="options", flat=True, sep=" ")
1308        options = f" {options}" if options else ""
1309
1310        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
def clone_sql(self, expression: sqlglot.expressions.Clone) -> str:
1312    def clone_sql(self, expression: exp.Clone) -> str:
1313        this = self.sql(expression, "this")
1314        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1315        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1316        return f"{shallow}{keyword} {this}"
def describe_sql(self, expression: sqlglot.expressions.Describe) -> str:
1318    def describe_sql(self, expression: exp.Describe) -> str:
1319        style = expression.args.get("style")
1320        style = f" {style}" if style else ""
1321        partition = self.sql(expression, "partition")
1322        partition = f" {partition}" if partition else ""
1323        format = self.sql(expression, "format")
1324        format = f" {format}" if format else ""
1325
1326        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
def heredoc_sql(self, expression: sqlglot.expressions.Heredoc) -> str:
1328    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1329        tag = self.sql(expression, "tag")
1330        return f"${tag}${self.sql(expression, 'this')}${tag}$"
def prepend_ctes(self, expression: sqlglot.expressions.Expression, sql: str) -> str:
1332    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1333        with_ = self.sql(expression, "with_")
1334        if with_:
1335            sql = f"{with_}{self.sep()}{sql}"
1336        return sql
def with_sql(self, expression: sqlglot.expressions.With) -> str:
1338    def with_sql(self, expression: exp.With) -> str:
1339        sql = self.expressions(expression, flat=True)
1340        recursive = (
1341            "RECURSIVE "
1342            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1343            else ""
1344        )
1345        search = self.sql(expression, "search")
1346        search = f" {search}" if search else ""
1347
1348        return f"WITH {recursive}{sql}{search}"
def cte_sql(self, expression: sqlglot.expressions.CTE) -> str:
1350    def cte_sql(self, expression: exp.CTE) -> str:
1351        alias = expression.args.get("alias")
1352        if alias:
1353            alias.add_comments(expression.pop_comments())
1354
1355        alias_sql = self.sql(expression, "alias")
1356
1357        materialized = expression.args.get("materialized")
1358        if materialized is False:
1359            materialized = "NOT MATERIALIZED "
1360        elif materialized:
1361            materialized = "MATERIALIZED "
1362
1363        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1364        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1365
1366        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
def tablealias_sql(self, expression: sqlglot.expressions.TableAlias) -> str:
1368    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1369        alias = self.sql(expression, "this")
1370        columns = self.expressions(expression, key="columns", flat=True)
1371        columns = f"({columns})" if columns else ""
1372
1373        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1374            columns = ""
1375            self.unsupported("Named columns are not supported in table alias.")
1376
1377        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1378            alias = self._next_name()
1379
1380        return f"{alias}{columns}"
def bitstring_sql(self, expression: sqlglot.expressions.BitString) -> str:
1382    def bitstring_sql(self, expression: exp.BitString) -> str:
1383        this = self.sql(expression, "this")
1384        if self.dialect.BIT_START:
1385            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1386        return f"{int(this, 2)}"
def hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1388    def hexstring_sql(
1389        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1390    ) -> str:
1391        this = self.sql(expression, "this")
1392        is_integer_type = expression.args.get("is_integer")
1393
1394        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1395            not self.dialect.HEX_START and not binary_function_repr
1396        ):
1397            # Integer representation will be returned if:
1398            # - The read dialect treats the hex value as integer literal but not the write
1399            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1400            return f"{int(this, 16)}"
1401
1402        if not is_integer_type:
1403            # Read dialect treats the hex value as BINARY/BLOB
1404            if binary_function_repr:
1405                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1406                return self.func(binary_function_repr, exp.Literal.string(this))
1407            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1408                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1409                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1410
1411        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
def bytestring_sql(self, expression: sqlglot.expressions.ByteString) -> str:
1413    def bytestring_sql(self, expression: exp.ByteString) -> str:
1414        this = self.sql(expression, "this")
1415        if self.dialect.BYTE_START:
1416            escaped_byte_string = self.escape_str(
1417                this,
1418                escape_backslash=False,
1419                delimiter=self.dialect.BYTE_END,
1420                escaped_delimiter=self._escaped_byte_quote_end,
1421            )
1422            is_bytes = expression.args.get("is_bytes", False)
1423            delimited_byte_string = (
1424                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1425            )
1426            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1427                return self.sql(
1428                    exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect)
1429                )
1430            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1431                return self.sql(
1432                    exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect)
1433                )
1434
1435            return delimited_byte_string
1436        return this
def unicodestring_sql(self, expression: sqlglot.expressions.UnicodeString) -> str:
1438    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1439        this = self.sql(expression, "this")
1440        escape = expression.args.get("escape")
1441
1442        if self.dialect.UNICODE_START:
1443            escape_substitute = r"\\\1"
1444            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1445        else:
1446            escape_substitute = r"\\u\1"
1447            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1448
1449        if escape:
1450            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1451            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1452        else:
1453            escape_pattern = ESCAPED_UNICODE_RE
1454            escape_sql = ""
1455
1456        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1457            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1458
1459        return f"{left_quote}{this}{right_quote}{escape_sql}"
def rawstring_sql(self, expression: sqlglot.expressions.RawString) -> str:
1461    def rawstring_sql(self, expression: exp.RawString) -> str:
1462        string = expression.this
1463        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1464            string = string.replace("\\", "\\\\")
1465
1466        string = self.escape_str(string, escape_backslash=False)
1467        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
def datatypeparam_sql(self, expression: sqlglot.expressions.DataTypeParam) -> str:
1469    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1470        this = self.sql(expression, "this")
1471        specifier = self.sql(expression, "expression")
1472        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1473        return f"{this}{specifier}"
def datatype_sql(self, expression: sqlglot.expressions.DataType) -> str:
1475    def datatype_sql(self, expression: exp.DataType) -> str:
1476        nested = ""
1477        values = ""
1478
1479        expr_nested = expression.args.get("nested")
1480        interior = (
1481            self.expressions(
1482                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1483            )
1484            if expr_nested and self.pretty
1485            else self.expressions(expression, flat=True)
1486        )
1487
1488        type_value = expression.this
1489        if type_value in self.UNSUPPORTED_TYPES:
1490            self.unsupported(
1491                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1492            )
1493
1494        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1495            type_sql = self.sql(expression, "kind")
1496        else:
1497            type_sql = (
1498                self.TYPE_MAPPING.get(type_value, type_value.value)
1499                if isinstance(type_value, exp.DataType.Type)
1500                else type_value
1501            )
1502
1503        if interior:
1504            if expr_nested:
1505                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1506                if expression.args.get("values") is not None:
1507                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1508                    values = self.expressions(expression, key="values", flat=True)
1509                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1510            elif type_value == exp.DataType.Type.INTERVAL:
1511                nested = f" {interior}"
1512            else:
1513                nested = f"({interior})"
1514
1515        type_sql = f"{type_sql}{nested}{values}"
1516        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1517            exp.DataType.Type.TIMETZ,
1518            exp.DataType.Type.TIMESTAMPTZ,
1519        ):
1520            type_sql = f"{type_sql} WITH TIME ZONE"
1521
1522        return type_sql
def directory_sql(self, expression: sqlglot.expressions.Directory) -> str:
1524    def directory_sql(self, expression: exp.Directory) -> str:
1525        local = "LOCAL " if expression.args.get("local") else ""
1526        row_format = self.sql(expression, "row_format")
1527        row_format = f" {row_format}" if row_format else ""
1528        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
def delete_sql(self, expression: sqlglot.expressions.Delete) -> str:
1530    def delete_sql(self, expression: exp.Delete) -> str:
1531        this = self.sql(expression, "this")
1532        this = f" FROM {this}" if this else ""
1533        using = self.expressions(expression, key="using")
1534        using = f" USING {using}" if using else ""
1535        cluster = self.sql(expression, "cluster")
1536        cluster = f" {cluster}" if cluster else ""
1537        where = self.sql(expression, "where")
1538        returning = self.sql(expression, "returning")
1539        order = self.sql(expression, "order")
1540        limit = self.sql(expression, "limit")
1541        tables = self.expressions(expression, key="tables")
1542        tables = f" {tables}" if tables else ""
1543        if self.RETURNING_END:
1544            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1545        else:
1546            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1547        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
def drop_sql(self, expression: sqlglot.expressions.Drop) -> str:
1549    def drop_sql(self, expression: exp.Drop) -> str:
1550        this = self.sql(expression, "this")
1551        expressions = self.expressions(expression, flat=True)
1552        expressions = f" ({expressions})" if expressions else ""
1553        kind = expression.args["kind"]
1554        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1555        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1556        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1557        on_cluster = self.sql(expression, "cluster")
1558        on_cluster = f" {on_cluster}" if on_cluster else ""
1559        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1560        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1561        cascade = " CASCADE" if expression.args.get("cascade") else ""
1562        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1563        purge = " PURGE" if expression.args.get("purge") else ""
1564        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
def set_operation(self, expression: sqlglot.expressions.SetOperation) -> str:
1566    def set_operation(self, expression: exp.SetOperation) -> str:
1567        op_type = type(expression)
1568        op_name = op_type.key.upper()
1569
1570        distinct = expression.args.get("distinct")
1571        if (
1572            distinct is False
1573            and op_type in (exp.Except, exp.Intersect)
1574            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1575        ):
1576            self.unsupported(f"{op_name} ALL is not supported")
1577
1578        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1579
1580        if distinct is None:
1581            distinct = default_distinct
1582            if distinct is None:
1583                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1584
1585        if distinct is default_distinct:
1586            distinct_or_all = ""
1587        else:
1588            distinct_or_all = " DISTINCT" if distinct else " ALL"
1589
1590        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1591        side_kind = f"{side_kind} " if side_kind else ""
1592
1593        by_name = " BY NAME" if expression.args.get("by_name") else ""
1594        on = self.expressions(expression, key="on", flat=True)
1595        on = f" ON ({on})" if on else ""
1596
1597        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
def set_operations(self, expression: sqlglot.expressions.SetOperation) -> str:
1599    def set_operations(self, expression: exp.SetOperation) -> str:
1600        if not self.SET_OP_MODIFIERS:
1601            limit = expression.args.get("limit")
1602            order = expression.args.get("order")
1603
1604            if limit or order:
1605                select = self._move_ctes_to_top_level(
1606                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1607                )
1608
1609                if limit:
1610                    select = select.limit(limit.pop(), copy=False)
1611                if order:
1612                    select = select.order_by(order.pop(), copy=False)
1613                return self.sql(select)
1614
1615        sqls: t.List[str] = []
1616        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1617
1618        while stack:
1619            node = stack.pop()
1620
1621            if isinstance(node, exp.SetOperation):
1622                stack.append(node.expression)
1623                stack.append(
1624                    self.maybe_comment(
1625                        self.set_operation(node), comments=node.comments, separated=True
1626                    )
1627                )
1628                stack.append(node.this)
1629            else:
1630                sqls.append(self.sql(node))
1631
1632        this = self.sep().join(sqls)
1633        this = self.query_modifiers(expression, this)
1634        return self.prepend_ctes(expression, this)
def fetch_sql(self, expression: sqlglot.expressions.Fetch) -> str:
1636    def fetch_sql(self, expression: exp.Fetch) -> str:
1637        direction = expression.args.get("direction")
1638        direction = f" {direction}" if direction else ""
1639        count = self.sql(expression, "count")
1640        count = f" {count}" if count else ""
1641        limit_options = self.sql(expression, "limit_options")
1642        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1643        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
def limitoptions_sql(self, expression: sqlglot.expressions.LimitOptions) -> str:
1645    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1646        percent = " PERCENT" if expression.args.get("percent") else ""
1647        rows = " ROWS" if expression.args.get("rows") else ""
1648        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1649        if not with_ties and rows:
1650            with_ties = " ONLY"
1651        return f"{percent}{rows}{with_ties}"
def filter_sql(self, expression: sqlglot.expressions.Filter) -> str:
1653    def filter_sql(self, expression: exp.Filter) -> str:
1654        if self.AGGREGATE_FILTER_SUPPORTED:
1655            this = self.sql(expression, "this")
1656            where = self.sql(expression, "expression").strip()
1657            return f"{this} FILTER({where})"
1658
1659        agg = expression.this
1660        agg_arg = agg.this
1661        cond = expression.expression.this
1662        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1663        return self.sql(agg)
def hint_sql(self, expression: sqlglot.expressions.Hint) -> str:
1665    def hint_sql(self, expression: exp.Hint) -> str:
1666        if not self.QUERY_HINTS:
1667            self.unsupported("Hints are not supported")
1668            return ""
1669
1670        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
def indexparameters_sql(self, expression: sqlglot.expressions.IndexParameters) -> str:
1672    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1673        using = self.sql(expression, "using")
1674        using = f" USING {using}" if using else ""
1675        columns = self.expressions(expression, key="columns", flat=True)
1676        columns = f"({columns})" if columns else ""
1677        partition_by = self.expressions(expression, key="partition_by", flat=True)
1678        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1679        where = self.sql(expression, "where")
1680        include = self.expressions(expression, key="include", flat=True)
1681        if include:
1682            include = f" INCLUDE ({include})"
1683        with_storage = self.expressions(expression, key="with_storage", flat=True)
1684        with_storage = f" WITH ({with_storage})" if with_storage else ""
1685        tablespace = self.sql(expression, "tablespace")
1686        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1687        on = self.sql(expression, "on")
1688        on = f" ON {on}" if on else ""
1689
1690        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
def index_sql(self, expression: sqlglot.expressions.Index) -> str:
1692    def index_sql(self, expression: exp.Index) -> str:
1693        unique = "UNIQUE " if expression.args.get("unique") else ""
1694        primary = "PRIMARY " if expression.args.get("primary") else ""
1695        amp = "AMP " if expression.args.get("amp") else ""
1696        name = self.sql(expression, "this")
1697        name = f"{name} " if name else ""
1698        table = self.sql(expression, "table")
1699        table = f"{self.INDEX_ON} {table}" if table else ""
1700
1701        index = "INDEX " if not table else ""
1702
1703        params = self.sql(expression, "params")
1704        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
def identifier_sql(self, expression: sqlglot.expressions.Identifier) -> str:
1706    def identifier_sql(self, expression: exp.Identifier) -> str:
1707        text = expression.name
1708        lower = text.lower()
1709        text = lower if self.normalize and not expression.quoted else text
1710        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1711        if (
1712            expression.quoted
1713            or self.dialect.can_quote(expression, self.identify)
1714            or lower in self.RESERVED_KEYWORDS
1715            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1716        ):
1717            text = f"{self._identifier_start}{text}{self._identifier_end}"
1718        return text
def hex_sql(self, expression: sqlglot.expressions.Hex) -> str:
1720    def hex_sql(self, expression: exp.Hex) -> str:
1721        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1722        if self.dialect.HEX_LOWERCASE:
1723            text = self.func("LOWER", text)
1724
1725        return text
def lowerhex_sql(self, expression: sqlglot.expressions.LowerHex) -> str:
1727    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1728        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1729        if not self.dialect.HEX_LOWERCASE:
1730            text = self.func("LOWER", text)
1731        return text
def inputoutputformat_sql(self, expression: sqlglot.expressions.InputOutputFormat) -> str:
1733    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1734        input_format = self.sql(expression, "input_format")
1735        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1736        output_format = self.sql(expression, "output_format")
1737        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1738        return self.sep().join((input_format, output_format))
def national_sql(self, expression: sqlglot.expressions.National, prefix: str = 'N') -> str:
1740    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1741        string = self.sql(exp.Literal.string(expression.name))
1742        return f"{prefix}{string}"
def partition_sql(self, expression: sqlglot.expressions.Partition) -> str:
1744    def partition_sql(self, expression: exp.Partition) -> str:
1745        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1746        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
def properties_sql(self, expression: sqlglot.expressions.Properties) -> str:
1748    def properties_sql(self, expression: exp.Properties) -> str:
1749        root_properties = []
1750        with_properties = []
1751
1752        for p in expression.expressions:
1753            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1754            if p_loc == exp.Properties.Location.POST_WITH:
1755                with_properties.append(p)
1756            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1757                root_properties.append(p)
1758
1759        root_props_ast = exp.Properties(expressions=root_properties)
1760        root_props_ast.parent = expression.parent
1761
1762        with_props_ast = exp.Properties(expressions=with_properties)
1763        with_props_ast.parent = expression.parent
1764
1765        root_props = self.root_properties(root_props_ast)
1766        with_props = self.with_properties(with_props_ast)
1767
1768        if root_props and with_props and not self.pretty:
1769            with_props = " " + with_props
1770
1771        return root_props + with_props
def root_properties(self, properties: sqlglot.expressions.Properties) -> str:
1773    def root_properties(self, properties: exp.Properties) -> str:
1774        if properties.expressions:
1775            return self.expressions(properties, indent=False, sep=" ")
1776        return ""
def properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1778    def properties(
1779        self,
1780        properties: exp.Properties,
1781        prefix: str = "",
1782        sep: str = ", ",
1783        suffix: str = "",
1784        wrapped: bool = True,
1785    ) -> str:
1786        if properties.expressions:
1787            expressions = self.expressions(properties, sep=sep, indent=False)
1788            if expressions:
1789                expressions = self.wrap(expressions) if wrapped else expressions
1790                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1791        return ""
def with_properties(self, properties: sqlglot.expressions.Properties) -> str:
1793    def with_properties(self, properties: exp.Properties) -> str:
1794        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
def locate_properties(self, properties: sqlglot.expressions.Properties) -> DefaultDict:
1796    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1797        properties_locs = defaultdict(list)
1798        for p in properties.expressions:
1799            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1800            if p_loc != exp.Properties.Location.UNSUPPORTED:
1801                properties_locs[p_loc].append(p)
1802            else:
1803                self.unsupported(f"Unsupported property {p.key}")
1804
1805        return properties_locs
def property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1807    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1808        if isinstance(expression.this, exp.Dot):
1809            return self.sql(expression, "this")
1810        return f"'{expression.name}'" if string_key else expression.name
def property_sql(self, expression: sqlglot.expressions.Property) -> str:
1812    def property_sql(self, expression: exp.Property) -> str:
1813        property_cls = expression.__class__
1814        if property_cls == exp.Property:
1815            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1816
1817        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1818        if not property_name:
1819            self.unsupported(f"Unsupported property {expression.key}")
1820
1821        return f"{property_name}={self.sql(expression, 'this')}"
def likeproperty_sql(self, expression: sqlglot.expressions.LikeProperty) -> str:
1823    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1824        if self.SUPPORTS_CREATE_TABLE_LIKE:
1825            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1826            options = f" {options}" if options else ""
1827
1828            like = f"LIKE {self.sql(expression, 'this')}{options}"
1829            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1830                like = f"({like})"
1831
1832            return like
1833
1834        if expression.expressions:
1835            self.unsupported("Transpilation of LIKE property options is unsupported")
1836
1837        select = exp.select("*").from_(expression.this).limit(0)
1838        return f"AS {self.sql(select)}"
def fallbackproperty_sql(self, expression: sqlglot.expressions.FallbackProperty) -> str:
1840    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1841        no = "NO " if expression.args.get("no") else ""
1842        protection = " PROTECTION" if expression.args.get("protection") else ""
1843        return f"{no}FALLBACK{protection}"
def journalproperty_sql(self, expression: sqlglot.expressions.JournalProperty) -> str:
1845    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1846        no = "NO " if expression.args.get("no") else ""
1847        local = expression.args.get("local")
1848        local = f"{local} " if local else ""
1849        dual = "DUAL " if expression.args.get("dual") else ""
1850        before = "BEFORE " if expression.args.get("before") else ""
1851        after = "AFTER " if expression.args.get("after") else ""
1852        return f"{no}{local}{dual}{before}{after}JOURNAL"
def freespaceproperty_sql(self, expression: sqlglot.expressions.FreespaceProperty) -> str:
1854    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1855        freespace = self.sql(expression, "this")
1856        percent = " PERCENT" if expression.args.get("percent") else ""
1857        return f"FREESPACE={freespace}{percent}"
def checksumproperty_sql(self, expression: sqlglot.expressions.ChecksumProperty) -> str:
1859    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1860        if expression.args.get("default"):
1861            property = "DEFAULT"
1862        elif expression.args.get("on"):
1863            property = "ON"
1864        else:
1865            property = "OFF"
1866        return f"CHECKSUM={property}"
def mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1868    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1869        if expression.args.get("no"):
1870            return "NO MERGEBLOCKRATIO"
1871        if expression.args.get("default"):
1872            return "DEFAULT MERGEBLOCKRATIO"
1873
1874        percent = " PERCENT" if expression.args.get("percent") else ""
1875        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def datablocksizeproperty_sql(self, expression: sqlglot.expressions.DataBlocksizeProperty) -> str:
1877    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1878        default = expression.args.get("default")
1879        minimum = expression.args.get("minimum")
1880        maximum = expression.args.get("maximum")
1881        if default or minimum or maximum:
1882            if default:
1883                prop = "DEFAULT"
1884            elif minimum:
1885                prop = "MINIMUM"
1886            else:
1887                prop = "MAXIMUM"
1888            return f"{prop} DATABLOCKSIZE"
1889        units = expression.args.get("units")
1890        units = f" {units}" if units else ""
1891        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1893    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1894        autotemp = expression.args.get("autotemp")
1895        always = expression.args.get("always")
1896        default = expression.args.get("default")
1897        manual = expression.args.get("manual")
1898        never = expression.args.get("never")
1899
1900        if autotemp is not None:
1901            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1902        elif always:
1903            prop = "ALWAYS"
1904        elif default:
1905            prop = "DEFAULT"
1906        elif manual:
1907            prop = "MANUAL"
1908        elif never:
1909            prop = "NEVER"
1910        return f"BLOCKCOMPRESSION={prop}"
def isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1912    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1913        no = expression.args.get("no")
1914        no = " NO" if no else ""
1915        concurrent = expression.args.get("concurrent")
1916        concurrent = " CONCURRENT" if concurrent else ""
1917        target = self.sql(expression, "target")
1918        target = f" {target}" if target else ""
1919        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def partitionboundspec_sql(self, expression: sqlglot.expressions.PartitionBoundSpec) -> str:
1921    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1922        if isinstance(expression.this, list):
1923            return f"IN ({self.expressions(expression, key='this', flat=True)})"
1924        if expression.this:
1925            modulus = self.sql(expression, "this")
1926            remainder = self.sql(expression, "expression")
1927            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1928
1929        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1930        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1931        return f"FROM ({from_expressions}) TO ({to_expressions})"
def partitionedofproperty_sql(self, expression: sqlglot.expressions.PartitionedOfProperty) -> str:
1933    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1934        this = self.sql(expression, "this")
1935
1936        for_values_or_default = expression.expression
1937        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1938            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1939        else:
1940            for_values_or_default = " DEFAULT"
1941
1942        return f"PARTITION OF {this}{for_values_or_default}"
def lockingproperty_sql(self, expression: sqlglot.expressions.LockingProperty) -> str:
1944    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1945        kind = expression.args.get("kind")
1946        this = f" {self.sql(expression, 'this')}" if expression.this else ""
1947        for_or_in = expression.args.get("for_or_in")
1948        for_or_in = f" {for_or_in}" if for_or_in else ""
1949        lock_type = expression.args.get("lock_type")
1950        override = " OVERRIDE" if expression.args.get("override") else ""
1951        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
def withdataproperty_sql(self, expression: sqlglot.expressions.WithDataProperty) -> str:
1953    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1954        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1955        statistics = expression.args.get("statistics")
1956        statistics_sql = ""
1957        if statistics is not None:
1958            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1959        return f"{data_sql}{statistics_sql}"
def withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1961    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1962        this = self.sql(expression, "this")
1963        this = f"HISTORY_TABLE={this}" if this else ""
1964        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
1965        data_consistency = (
1966            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
1967        )
1968        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
1969        retention_period = (
1970            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
1971        )
1972
1973        if this:
1974            on_sql = self.func("ON", this, data_consistency, retention_period)
1975        else:
1976            on_sql = "ON" if expression.args.get("on") else "OFF"
1977
1978        sql = f"SYSTEM_VERSIONING={on_sql}"
1979
1980        return f"WITH({sql})" if expression.args.get("with_") else sql
def insert_sql(self, expression: sqlglot.expressions.Insert) -> str:
1982    def insert_sql(self, expression: exp.Insert) -> str:
1983        hint = self.sql(expression, "hint")
1984        overwrite = expression.args.get("overwrite")
1985
1986        if isinstance(expression.this, exp.Directory):
1987            this = " OVERWRITE" if overwrite else " INTO"
1988        else:
1989            this = self.INSERT_OVERWRITE if overwrite else " INTO"
1990
1991        stored = self.sql(expression, "stored")
1992        stored = f" {stored}" if stored else ""
1993        alternative = expression.args.get("alternative")
1994        alternative = f" OR {alternative}" if alternative else ""
1995        ignore = " IGNORE" if expression.args.get("ignore") else ""
1996        is_function = expression.args.get("is_function")
1997        if is_function:
1998            this = f"{this} FUNCTION"
1999        this = f"{this} {self.sql(expression, 'this')}"
2000
2001        exists = " IF EXISTS" if expression.args.get("exists") else ""
2002        where = self.sql(expression, "where")
2003        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2004        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2005        on_conflict = self.sql(expression, "conflict")
2006        on_conflict = f" {on_conflict}" if on_conflict else ""
2007        by_name = " BY NAME" if expression.args.get("by_name") else ""
2008        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2009        returning = self.sql(expression, "returning")
2010
2011        if self.RETURNING_END:
2012            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2013        else:
2014            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2015
2016        partition_by = self.sql(expression, "partition")
2017        partition_by = f" {partition_by}" if partition_by else ""
2018        settings = self.sql(expression, "settings")
2019        settings = f" {settings}" if settings else ""
2020
2021        source = self.sql(expression, "source")
2022        source = f"TABLE {source}" if source else ""
2023
2024        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2025        return self.prepend_ctes(expression, sql)
def introducer_sql(self, expression: sqlglot.expressions.Introducer) -> str:
2027    def introducer_sql(self, expression: exp.Introducer) -> str:
2028        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def kill_sql(self, expression: sqlglot.expressions.Kill) -> str:
2030    def kill_sql(self, expression: exp.Kill) -> str:
2031        kind = self.sql(expression, "kind")
2032        kind = f" {kind}" if kind else ""
2033        this = self.sql(expression, "this")
2034        this = f" {this}" if this else ""
2035        return f"KILL{kind}{this}"
def pseudotype_sql(self, expression: sqlglot.expressions.PseudoType) -> str:
2037    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2038        return expression.name
def objectidentifier_sql(self, expression: sqlglot.expressions.ObjectIdentifier) -> str:
2040    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2041        return expression.name
def onconflict_sql(self, expression: sqlglot.expressions.OnConflict) -> str:
2043    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2044        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2045
2046        constraint = self.sql(expression, "constraint")
2047        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2048
2049        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2050        if conflict_keys:
2051            conflict_keys = f"({conflict_keys})"
2052
2053        index_predicate = self.sql(expression, "index_predicate")
2054        conflict_keys = f"{conflict_keys}{index_predicate} "
2055
2056        action = self.sql(expression, "action")
2057
2058        expressions = self.expressions(expression, flat=True)
2059        if expressions:
2060            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2061            expressions = f" {set_keyword}{expressions}"
2062
2063        where = self.sql(expression, "where")
2064        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def returning_sql(self, expression: sqlglot.expressions.Returning) -> str:
2066    def returning_sql(self, expression: exp.Returning) -> str:
2067        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
def rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
2069    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2070        fields = self.sql(expression, "fields")
2071        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2072        escaped = self.sql(expression, "escaped")
2073        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2074        items = self.sql(expression, "collection_items")
2075        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2076        keys = self.sql(expression, "map_keys")
2077        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2078        lines = self.sql(expression, "lines")
2079        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2080        null = self.sql(expression, "null")
2081        null = f" NULL DEFINED AS {null}" if null else ""
2082        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
def withtablehint_sql(self, expression: sqlglot.expressions.WithTableHint) -> str:
2084    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2085        return f"WITH ({self.expressions(expression, flat=True)})"
def indextablehint_sql(self, expression: sqlglot.expressions.IndexTableHint) -> str:
2087    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2088        this = f"{self.sql(expression, 'this')} INDEX"
2089        target = self.sql(expression, "target")
2090        target = f" FOR {target}" if target else ""
2091        return f"{this}{target} ({self.expressions(expression, flat=True)})"
def historicaldata_sql(self, expression: sqlglot.expressions.HistoricalData) -> str:
2093    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2094        this = self.sql(expression, "this")
2095        kind = self.sql(expression, "kind")
2096        expr = self.sql(expression, "expression")
2097        return f"{this} ({kind} => {expr})"
def table_parts(self, expression: sqlglot.expressions.Table) -> str:
2099    def table_parts(self, expression: exp.Table) -> str:
2100        return ".".join(
2101            self.sql(part)
2102            for part in (
2103                expression.args.get("catalog"),
2104                expression.args.get("db"),
2105                expression.args.get("this"),
2106            )
2107            if part is not None
2108        )
def table_sql(self, expression: sqlglot.expressions.Table, sep: str = ' AS ') -> str:
2110    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2111        table = self.table_parts(expression)
2112        only = "ONLY " if expression.args.get("only") else ""
2113        partition = self.sql(expression, "partition")
2114        partition = f" {partition}" if partition else ""
2115        version = self.sql(expression, "version")
2116        version = f" {version}" if version else ""
2117        alias = self.sql(expression, "alias")
2118        alias = f"{sep}{alias}" if alias else ""
2119
2120        sample = self.sql(expression, "sample")
2121        if self.dialect.ALIAS_POST_TABLESAMPLE:
2122            sample_pre_alias = sample
2123            sample_post_alias = ""
2124        else:
2125            sample_pre_alias = ""
2126            sample_post_alias = sample
2127
2128        hints = self.expressions(expression, key="hints", sep=" ")
2129        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2130        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2131        joins = self.indent(
2132            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2133        )
2134        laterals = self.expressions(expression, key="laterals", sep="")
2135
2136        file_format = self.sql(expression, "format")
2137        if file_format:
2138            pattern = self.sql(expression, "pattern")
2139            pattern = f", PATTERN => {pattern}" if pattern else ""
2140            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2141
2142        ordinality = expression.args.get("ordinality") or ""
2143        if ordinality:
2144            ordinality = f" WITH ORDINALITY{alias}"
2145            alias = ""
2146
2147        when = self.sql(expression, "when")
2148        if when:
2149            table = f"{table} {when}"
2150
2151        changes = self.sql(expression, "changes")
2152        changes = f" {changes}" if changes else ""
2153
2154        rows_from = self.expressions(expression, key="rows_from")
2155        if rows_from:
2156            table = f"ROWS FROM {self.wrap(rows_from)}"
2157
2158        indexed = expression.args.get("indexed")
2159        if indexed is not None:
2160            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2161        else:
2162            indexed = ""
2163
2164        return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{indexed}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
def tablefromrows_sql(self, expression: sqlglot.expressions.TableFromRows) -> str:
2166    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2167        table = self.func("TABLE", expression.this)
2168        alias = self.sql(expression, "alias")
2169        alias = f" AS {alias}" if alias else ""
2170        sample = self.sql(expression, "sample")
2171        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2172        joins = self.indent(
2173            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2174        )
2175        return f"{table}{alias}{pivots}{sample}{joins}"
def tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2177    def tablesample_sql(
2178        self,
2179        expression: exp.TableSample,
2180        tablesample_keyword: t.Optional[str] = None,
2181    ) -> str:
2182        method = self.sql(expression, "method")
2183        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2184        numerator = self.sql(expression, "bucket_numerator")
2185        denominator = self.sql(expression, "bucket_denominator")
2186        field = self.sql(expression, "bucket_field")
2187        field = f" ON {field}" if field else ""
2188        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2189        seed = self.sql(expression, "seed")
2190        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2191
2192        size = self.sql(expression, "size")
2193        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2194            size = f"{size} ROWS"
2195
2196        percent = self.sql(expression, "percent")
2197        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2198            percent = f"{percent} PERCENT"
2199
2200        expr = f"{bucket}{percent}{size}"
2201        if self.TABLESAMPLE_REQUIRES_PARENS:
2202            expr = f"({expr})"
2203
2204        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
def pivot_sql(self, expression: sqlglot.expressions.Pivot) -> str:
2206    def pivot_sql(self, expression: exp.Pivot) -> str:
2207        expressions = self.expressions(expression, flat=True)
2208        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2209
2210        group = self.sql(expression, "group")
2211
2212        if expression.this:
2213            this = self.sql(expression, "this")
2214            if not expressions:
2215                sql = f"UNPIVOT {this}"
2216            else:
2217                on = f"{self.seg('ON')} {expressions}"
2218                into = self.sql(expression, "into")
2219                into = f"{self.seg('INTO')} {into}" if into else ""
2220                using = self.expressions(expression, key="using", flat=True)
2221                using = f"{self.seg('USING')} {using}" if using else ""
2222                sql = f"{direction} {this}{on}{into}{using}{group}"
2223            return self.prepend_ctes(expression, sql)
2224
2225        alias = self.sql(expression, "alias")
2226        alias = f" AS {alias}" if alias else ""
2227
2228        fields = self.expressions(
2229            expression,
2230            "fields",
2231            sep=" ",
2232            dynamic=True,
2233            new_line=True,
2234            skip_first=True,
2235            skip_last=True,
2236        )
2237
2238        include_nulls = expression.args.get("include_nulls")
2239        if include_nulls is not None:
2240            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2241        else:
2242            nulls = ""
2243
2244        default_on_null = self.sql(expression, "default_on_null")
2245        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2246        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2247        return self.prepend_ctes(expression, sql)
def version_sql(self, expression: sqlglot.expressions.Version) -> str:
2249    def version_sql(self, expression: exp.Version) -> str:
2250        this = f"FOR {expression.name}"
2251        kind = expression.text("kind")
2252        expr = self.sql(expression, "expression")
2253        return f"{this} {kind} {expr}"
def tuple_sql(self, expression: sqlglot.expressions.Tuple) -> str:
2255    def tuple_sql(self, expression: exp.Tuple) -> str:
2256        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
def update_sql(self, expression: sqlglot.expressions.Update) -> str:
2290    def update_sql(self, expression: exp.Update) -> str:
2291        this = self.sql(expression, "this")
2292        join_sql, from_sql = self._update_from_joins_sql(expression)
2293        set_sql = self.expressions(expression, flat=True)
2294        where_sql = self.sql(expression, "where")
2295        returning = self.sql(expression, "returning")
2296        order = self.sql(expression, "order")
2297        limit = self.sql(expression, "limit")
2298        if self.RETURNING_END:
2299            expression_sql = f"{from_sql}{where_sql}{returning}"
2300        else:
2301            expression_sql = f"{returning}{from_sql}{where_sql}"
2302        options = self.expressions(expression, key="options")
2303        options = f" OPTION({options})" if options else ""
2304        sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2305        return self.prepend_ctes(expression, sql)
def values_sql( self, expression: sqlglot.expressions.Values, values_as_table: bool = True) -> str:
2307    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2308        values_as_table = values_as_table and self.VALUES_AS_TABLE
2309
2310        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2311        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2312            args = self.expressions(expression)
2313            alias = self.sql(expression, "alias")
2314            values = f"VALUES{self.seg('')}{args}"
2315            values = (
2316                f"({values})"
2317                if self.WRAP_DERIVED_VALUES
2318                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2319                else values
2320            )
2321            values = self.query_modifiers(expression, values)
2322            return f"{values} AS {alias}" if alias else values
2323
2324        # Converts `VALUES...` expression into a series of select unions.
2325        alias_node = expression.args.get("alias")
2326        column_names = alias_node and alias_node.columns
2327
2328        selects: t.List[exp.Query] = []
2329
2330        for i, tup in enumerate(expression.expressions):
2331            row = tup.expressions
2332
2333            if i == 0 and column_names:
2334                row = [
2335                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2336                ]
2337
2338            selects.append(exp.Select(expressions=row))
2339
2340        if self.pretty:
2341            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2342            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2343            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2344            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2345            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2346
2347        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2348        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2349        return f"({unions}){alias}"
def var_sql(self, expression: sqlglot.expressions.Var) -> str:
2351    def var_sql(self, expression: exp.Var) -> str:
2352        return self.sql(expression, "this")
@unsupported_args('expressions')
def into_sql(self, expression: sqlglot.expressions.Into) -> str:
2354    @unsupported_args("expressions")
2355    def into_sql(self, expression: exp.Into) -> str:
2356        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2357        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2358        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
def from_sql(self, expression: sqlglot.expressions.From) -> str:
2360    def from_sql(self, expression: exp.From) -> str:
2361        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
def groupingsets_sql(self, expression: sqlglot.expressions.GroupingSets) -> str:
2363    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2364        grouping_sets = self.expressions(expression, indent=False)
2365        return f"GROUPING SETS {self.wrap(grouping_sets)}"
def rollup_sql(self, expression: sqlglot.expressions.Rollup) -> str:
2367    def rollup_sql(self, expression: exp.Rollup) -> str:
2368        expressions = self.expressions(expression, indent=False)
2369        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
def cube_sql(self, expression: sqlglot.expressions.Cube) -> str:
2371    def cube_sql(self, expression: exp.Cube) -> str:
2372        expressions = self.expressions(expression, indent=False)
2373        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
def group_sql(self, expression: sqlglot.expressions.Group) -> str:
2375    def group_sql(self, expression: exp.Group) -> str:
2376        group_by_all = expression.args.get("all")
2377        if group_by_all is True:
2378            modifier = " ALL"
2379        elif group_by_all is False:
2380            modifier = " DISTINCT"
2381        else:
2382            modifier = ""
2383
2384        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2385
2386        grouping_sets = self.expressions(expression, key="grouping_sets")
2387        cube = self.expressions(expression, key="cube")
2388        rollup = self.expressions(expression, key="rollup")
2389
2390        groupings = csv(
2391            self.seg(grouping_sets) if grouping_sets else "",
2392            self.seg(cube) if cube else "",
2393            self.seg(rollup) if rollup else "",
2394            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2395            sep=self.GROUPINGS_SEP,
2396        )
2397
2398        if (
2399            expression.expressions
2400            and groupings
2401            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2402        ):
2403            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2404
2405        return f"{group_by}{groupings}"
def having_sql(self, expression: sqlglot.expressions.Having) -> str:
2407    def having_sql(self, expression: exp.Having) -> str:
2408        this = self.indent(self.sql(expression, "this"))
2409        return f"{self.seg('HAVING')}{self.sep()}{this}"
def connect_sql(self, expression: sqlglot.expressions.Connect) -> str:
2411    def connect_sql(self, expression: exp.Connect) -> str:
2412        start = self.sql(expression, "start")
2413        start = self.seg(f"START WITH {start}") if start else ""
2414        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2415        connect = self.sql(expression, "connect")
2416        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2417        return start + connect
def prior_sql(self, expression: sqlglot.expressions.Prior) -> str:
2419    def prior_sql(self, expression: exp.Prior) -> str:
2420        return f"PRIOR {self.sql(expression, 'this')}"
def join_sql(self, expression: sqlglot.expressions.Join) -> str:
2422    def join_sql(self, expression: exp.Join) -> str:
2423        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2424            side = None
2425        else:
2426            side = expression.side
2427
2428        op_sql = " ".join(
2429            op
2430            for op in (
2431                expression.method,
2432                "GLOBAL" if expression.args.get("global_") else None,
2433                side,
2434                expression.kind,
2435                expression.hint if self.JOIN_HINTS else None,
2436            )
2437            if op
2438        )
2439        match_cond = self.sql(expression, "match_condition")
2440        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2441        on_sql = self.sql(expression, "on")
2442        using = expression.args.get("using")
2443
2444        if not on_sql and using:
2445            on_sql = csv(*(self.sql(column) for column in using))
2446
2447        this = expression.this
2448        this_sql = self.sql(this)
2449
2450        exprs = self.expressions(expression)
2451        if exprs:
2452            this_sql = f"{this_sql},{self.seg(exprs)}"
2453
2454        if on_sql:
2455            on_sql = self.indent(on_sql, skip_first=True)
2456            space = self.seg(" " * self.pad) if self.pretty else " "
2457            if using:
2458                on_sql = f"{space}USING ({on_sql})"
2459            else:
2460                on_sql = f"{space}ON {on_sql}"
2461        elif not op_sql:
2462            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2463                return f" {this_sql}"
2464
2465            return f", {this_sql}"
2466
2467        if op_sql != "STRAIGHT_JOIN":
2468            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2469
2470        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2471        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
def lambda_sql( self, expression: sqlglot.expressions.Lambda, arrow_sep: str = '->', wrap: bool = True) -> str:
2473    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2474        args = self.expressions(expression, flat=True)
2475        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2476        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
def lateral_op(self, expression: sqlglot.expressions.Lateral) -> str:
2478    def lateral_op(self, expression: exp.Lateral) -> str:
2479        cross_apply = expression.args.get("cross_apply")
2480
2481        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2482        if cross_apply is True:
2483            op = "INNER JOIN "
2484        elif cross_apply is False:
2485            op = "LEFT JOIN "
2486        else:
2487            op = ""
2488
2489        return f"{op}LATERAL"
def lateral_sql(self, expression: sqlglot.expressions.Lateral) -> str:
2491    def lateral_sql(self, expression: exp.Lateral) -> str:
2492        this = self.sql(expression, "this")
2493
2494        if expression.args.get("view"):
2495            alias = expression.args["alias"]
2496            columns = self.expressions(alias, key="columns", flat=True)
2497            table = f" {alias.name}" if alias.name else ""
2498            columns = f" AS {columns}" if columns else ""
2499            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2500            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2501
2502        alias = self.sql(expression, "alias")
2503        alias = f" AS {alias}" if alias else ""
2504
2505        ordinality = expression.args.get("ordinality") or ""
2506        if ordinality:
2507            ordinality = f" WITH ORDINALITY{alias}"
2508            alias = ""
2509
2510        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
def limit_sql(self, expression: sqlglot.expressions.Limit, top: bool = False) -> str:
2512    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2513        this = self.sql(expression, "this")
2514
2515        args = [
2516            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2517            for e in (expression.args.get(k) for k in ("offset", "expression"))
2518            if e
2519        ]
2520
2521        args_sql = ", ".join(self.sql(e) for e in args)
2522        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2523        expressions = self.expressions(expression, flat=True)
2524        limit_options = self.sql(expression, "limit_options")
2525        expressions = f" BY {expressions}" if expressions else ""
2526
2527        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
def offset_sql(self, expression: sqlglot.expressions.Offset) -> str:
2529    def offset_sql(self, expression: exp.Offset) -> str:
2530        this = self.sql(expression, "this")
2531        value = expression.expression
2532        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2533        expressions = self.expressions(expression, flat=True)
2534        expressions = f" BY {expressions}" if expressions else ""
2535        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
def setitem_sql(self, expression: sqlglot.expressions.SetItem) -> str:
2537    def setitem_sql(self, expression: exp.SetItem) -> str:
2538        kind = self.sql(expression, "kind")
2539        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2540            kind = ""
2541        else:
2542            kind = f"{kind} " if kind else ""
2543        this = self.sql(expression, "this")
2544        expressions = self.expressions(expression)
2545        collate = self.sql(expression, "collate")
2546        collate = f" COLLATE {collate}" if collate else ""
2547        global_ = "GLOBAL " if expression.args.get("global_") else ""
2548        return f"{global_}{kind}{this}{expressions}{collate}"
def set_sql(self, expression: sqlglot.expressions.Set) -> str:
2550    def set_sql(self, expression: exp.Set) -> str:
2551        expressions = f" {self.expressions(expression, flat=True)}"
2552        tag = " TAG" if expression.args.get("tag") else ""
2553        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
def queryband_sql(self, expression: sqlglot.expressions.QueryBand) -> str:
2555    def queryband_sql(self, expression: exp.QueryBand) -> str:
2556        this = self.sql(expression, "this")
2557        update = " UPDATE" if expression.args.get("update") else ""
2558        scope = self.sql(expression, "scope")
2559        scope = f" FOR {scope}" if scope else ""
2560
2561        return f"QUERY_BAND = {this}{update}{scope}"
def pragma_sql(self, expression: sqlglot.expressions.Pragma) -> str:
2563    def pragma_sql(self, expression: exp.Pragma) -> str:
2564        return f"PRAGMA {self.sql(expression, 'this')}"
def lock_sql(self, expression: sqlglot.expressions.Lock) -> str:
2566    def lock_sql(self, expression: exp.Lock) -> str:
2567        if not self.LOCKING_READS_SUPPORTED:
2568            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2569            return ""
2570
2571        update = expression.args["update"]
2572        key = expression.args.get("key")
2573        if update:
2574            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2575        else:
2576            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2577        expressions = self.expressions(expression, flat=True)
2578        expressions = f" OF {expressions}" if expressions else ""
2579        wait = expression.args.get("wait")
2580
2581        if wait is not None:
2582            if isinstance(wait, exp.Literal):
2583                wait = f" WAIT {self.sql(wait)}"
2584            else:
2585                wait = " NOWAIT" if wait else " SKIP LOCKED"
2586
2587        return f"{lock_type}{expressions}{wait or ''}"
def literal_sql(self, expression: sqlglot.expressions.Literal) -> str:
2589    def literal_sql(self, expression: exp.Literal) -> str:
2590        text = expression.this or ""
2591        if expression.is_string:
2592            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2593        return text
def escape_str( self, text: str, escape_backslash: bool = True, delimiter: Optional[str] = None, escaped_delimiter: Optional[str] = None) -> str:
2595    def escape_str(
2596        self,
2597        text: str,
2598        escape_backslash: bool = True,
2599        delimiter: t.Optional[str] = None,
2600        escaped_delimiter: t.Optional[str] = None,
2601    ) -> str:
2602        if self.dialect.ESCAPED_SEQUENCES:
2603            to_escaped = self.dialect.ESCAPED_SEQUENCES
2604            text = "".join(
2605                to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text
2606            )
2607
2608        delimiter = delimiter or self.dialect.QUOTE_END
2609        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2610
2611        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
def loaddata_sql(self, expression: sqlglot.expressions.LoadData) -> str:
2613    def loaddata_sql(self, expression: exp.LoadData) -> str:
2614        local = " LOCAL" if expression.args.get("local") else ""
2615        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2616        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2617        this = f" INTO TABLE {self.sql(expression, 'this')}"
2618        partition = self.sql(expression, "partition")
2619        partition = f" {partition}" if partition else ""
2620        input_format = self.sql(expression, "input_format")
2621        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2622        serde = self.sql(expression, "serde")
2623        serde = f" SERDE {serde}" if serde else ""
2624        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
def null_sql(self, *_) -> str:
2626    def null_sql(self, *_) -> str:
2627        return "NULL"
def boolean_sql(self, expression: sqlglot.expressions.Boolean) -> str:
2629    def boolean_sql(self, expression: exp.Boolean) -> str:
2630        return "TRUE" if expression.this else "FALSE"
def booland_sql(self, expression: sqlglot.expressions.Booland) -> str:
2632    def booland_sql(self, expression: exp.Booland) -> str:
2633        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
def boolor_sql(self, expression: sqlglot.expressions.Boolor) -> str:
2635    def boolor_sql(self, expression: exp.Boolor) -> str:
2636        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
def order_sql(self, expression: sqlglot.expressions.Order, flat: bool = False) -> str:
2638    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2639        this = self.sql(expression, "this")
2640        this = f"{this} " if this else this
2641        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2642        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
def withfill_sql(self, expression: sqlglot.expressions.WithFill) -> str:
2644    def withfill_sql(self, expression: exp.WithFill) -> str:
2645        from_sql = self.sql(expression, "from_")
2646        from_sql = f" FROM {from_sql}" if from_sql else ""
2647        to_sql = self.sql(expression, "to")
2648        to_sql = f" TO {to_sql}" if to_sql else ""
2649        step_sql = self.sql(expression, "step")
2650        step_sql = f" STEP {step_sql}" if step_sql else ""
2651        interpolated_values = [
2652            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2653            if isinstance(e, exp.Alias)
2654            else self.sql(e, "this")
2655            for e in expression.args.get("interpolate") or []
2656        ]
2657        interpolate = (
2658            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2659        )
2660        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
def cluster_sql(self, expression: sqlglot.expressions.Cluster) -> str:
2662    def cluster_sql(self, expression: exp.Cluster) -> str:
2663        return self.op_expressions("CLUSTER BY", expression)
def distribute_sql(self, expression: sqlglot.expressions.Distribute) -> str:
2665    def distribute_sql(self, expression: exp.Distribute) -> str:
2666        return self.op_expressions("DISTRIBUTE BY", expression)
def sort_sql(self, expression: sqlglot.expressions.Sort) -> str:
2668    def sort_sql(self, expression: exp.Sort) -> str:
2669        return self.op_expressions("SORT BY", expression)
def ordered_sql(self, expression: sqlglot.expressions.Ordered) -> str:
2671    def ordered_sql(self, expression: exp.Ordered) -> str:
2672        desc = expression.args.get("desc")
2673        asc = not desc
2674
2675        nulls_first = expression.args.get("nulls_first")
2676        nulls_last = not nulls_first
2677        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2678        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2679        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2680
2681        this = self.sql(expression, "this")
2682
2683        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2684        nulls_sort_change = ""
2685        if nulls_first and (
2686            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2687        ):
2688            nulls_sort_change = " NULLS FIRST"
2689        elif (
2690            nulls_last
2691            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2692            and not nulls_are_last
2693        ):
2694            nulls_sort_change = " NULLS LAST"
2695
2696        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2697        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2698            window = expression.find_ancestor(exp.Window, exp.Select)
2699            if isinstance(window, exp.Window) and window.args.get("spec"):
2700                self.unsupported(
2701                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2702                )
2703                nulls_sort_change = ""
2704            elif self.NULL_ORDERING_SUPPORTED is False and (
2705                (asc and nulls_sort_change == " NULLS LAST")
2706                or (desc and nulls_sort_change == " NULLS FIRST")
2707            ):
2708                # BigQuery does not allow these ordering/nulls combinations when used under
2709                # an aggregation func or under a window containing one
2710                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2711
2712                if isinstance(ancestor, exp.Window):
2713                    ancestor = ancestor.this
2714                if isinstance(ancestor, exp.AggFunc):
2715                    self.unsupported(
2716                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2717                    )
2718                    nulls_sort_change = ""
2719            elif self.NULL_ORDERING_SUPPORTED is None:
2720                if expression.this.is_int:
2721                    self.unsupported(
2722                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2723                    )
2724                elif not isinstance(expression.this, exp.Rand):
2725                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2726                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2727                nulls_sort_change = ""
2728
2729        with_fill = self.sql(expression, "with_fill")
2730        with_fill = f" {with_fill}" if with_fill else ""
2731
2732        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def matchrecognizemeasure_sql(self, expression: sqlglot.expressions.MatchRecognizeMeasure) -> str:
2734    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2735        window_frame = self.sql(expression, "window_frame")
2736        window_frame = f"{window_frame} " if window_frame else ""
2737
2738        this = self.sql(expression, "this")
2739
2740        return f"{window_frame}{this}"
def matchrecognize_sql(self, expression: sqlglot.expressions.MatchRecognize) -> str:
2742    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2743        partition = self.partition_by_sql(expression)
2744        order = self.sql(expression, "order")
2745        measures = self.expressions(expression, key="measures")
2746        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2747        rows = self.sql(expression, "rows")
2748        rows = self.seg(rows) if rows else ""
2749        after = self.sql(expression, "after")
2750        after = self.seg(after) if after else ""
2751        pattern = self.sql(expression, "pattern")
2752        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2753        definition_sqls = [
2754            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2755            for definition in expression.args.get("define", [])
2756        ]
2757        definitions = self.expressions(sqls=definition_sqls)
2758        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2759        body = "".join(
2760            (
2761                partition,
2762                order,
2763                measures,
2764                rows,
2765                after,
2766                pattern,
2767                define,
2768            )
2769        )
2770        alias = self.sql(expression, "alias")
2771        alias = f" {alias}" if alias else ""
2772        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
def query_modifiers(self, expression: sqlglot.expressions.Expression, *sqls: str) -> str:
2774    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2775        limit = expression.args.get("limit")
2776
2777        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2778            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2779        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2780            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2781
2782        return csv(
2783            *sqls,
2784            *[self.sql(join) for join in expression.args.get("joins") or []],
2785            self.sql(expression, "match"),
2786            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2787            self.sql(expression, "prewhere"),
2788            self.sql(expression, "where"),
2789            self.sql(expression, "connect"),
2790            self.sql(expression, "group"),
2791            self.sql(expression, "having"),
2792            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2793            self.sql(expression, "order"),
2794            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2795            *self.after_limit_modifiers(expression),
2796            self.options_modifier(expression),
2797            self.for_modifiers(expression),
2798            sep="",
2799        )
def options_modifier(self, expression: sqlglot.expressions.Expression) -> str:
2801    def options_modifier(self, expression: exp.Expression) -> str:
2802        options = self.expressions(expression, key="options")
2803        return f" {options}" if options else ""
def for_modifiers(self, expression: sqlglot.expressions.Expression) -> str:
2805    def for_modifiers(self, expression: exp.Expression) -> str:
2806        for_modifiers = self.expressions(expression, key="for_")
2807        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
def queryoption_sql(self, expression: sqlglot.expressions.QueryOption) -> str:
2809    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2810        self.unsupported("Unsupported query option.")
2811        return ""
def offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2813    def offset_limit_modifiers(
2814        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2815    ) -> t.List[str]:
2816        return [
2817            self.sql(expression, "offset") if fetch else self.sql(limit),
2818            self.sql(limit) if fetch else self.sql(expression, "offset"),
2819        ]
def after_limit_modifiers(self, expression: sqlglot.expressions.Expression) -> List[str]:
2821    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2822        locks = self.expressions(expression, key="locks", sep=" ")
2823        locks = f" {locks}" if locks else ""
2824        return [locks, self.sql(expression, "sample")]
def select_sql(self, expression: sqlglot.expressions.Select) -> str:
2826    def select_sql(self, expression: exp.Select) -> str:
2827        into = expression.args.get("into")
2828        if not self.SUPPORTS_SELECT_INTO and into:
2829            into.pop()
2830
2831        hint = self.sql(expression, "hint")
2832        distinct = self.sql(expression, "distinct")
2833        distinct = f" {distinct}" if distinct else ""
2834        kind = self.sql(expression, "kind")
2835
2836        limit = expression.args.get("limit")
2837        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2838            top = self.limit_sql(limit, top=True)
2839            limit.pop()
2840        else:
2841            top = ""
2842
2843        expressions = self.expressions(expression)
2844
2845        if kind:
2846            if kind in self.SELECT_KINDS:
2847                kind = f" AS {kind}"
2848            else:
2849                if kind == "STRUCT":
2850                    expressions = self.expressions(
2851                        sqls=[
2852                            self.sql(
2853                                exp.Struct(
2854                                    expressions=[
2855                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2856                                        if isinstance(e, exp.Alias)
2857                                        else e
2858                                        for e in expression.expressions
2859                                    ]
2860                                )
2861                            )
2862                        ]
2863                    )
2864                kind = ""
2865
2866        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2867        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2868
2869        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2870        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2871        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2872        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2873        sql = self.query_modifiers(
2874            expression,
2875            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2876            self.sql(expression, "into", comment=False),
2877            self.sql(expression, "from_", comment=False),
2878        )
2879
2880        # If both the CTE and SELECT clauses have comments, generate the latter earlier
2881        if expression.args.get("with_"):
2882            sql = self.maybe_comment(sql, expression)
2883            expression.pop_comments()
2884
2885        sql = self.prepend_ctes(expression, sql)
2886
2887        if not self.SUPPORTS_SELECT_INTO and into:
2888            if into.args.get("temporary"):
2889                table_kind = " TEMPORARY"
2890            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
2891                table_kind = " UNLOGGED"
2892            else:
2893                table_kind = ""
2894            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
2895
2896        return sql
def schema_sql(self, expression: sqlglot.expressions.Schema) -> str:
2898    def schema_sql(self, expression: exp.Schema) -> str:
2899        this = self.sql(expression, "this")
2900        sql = self.schema_columns_sql(expression)
2901        return f"{this} {sql}" if this and sql else this or sql
def schema_columns_sql(self, expression: sqlglot.expressions.Schema) -> str:
2903    def schema_columns_sql(self, expression: exp.Schema) -> str:
2904        if expression.expressions:
2905            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2906        return ""
def star_sql(self, expression: sqlglot.expressions.Star) -> str:
2908    def star_sql(self, expression: exp.Star) -> str:
2909        except_ = self.expressions(expression, key="except_", flat=True)
2910        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2911        replace = self.expressions(expression, key="replace", flat=True)
2912        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
2913        rename = self.expressions(expression, key="rename", flat=True)
2914        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
2915        return f"*{except_}{replace}{rename}"
def parameter_sql(self, expression: sqlglot.expressions.Parameter) -> str:
2917    def parameter_sql(self, expression: exp.Parameter) -> str:
2918        this = self.sql(expression, "this")
2919        return f"{self.PARAMETER_TOKEN}{this}"
def sessionparameter_sql(self, expression: sqlglot.expressions.SessionParameter) -> str:
2921    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2922        this = self.sql(expression, "this")
2923        kind = expression.text("kind")
2924        if kind:
2925            kind = f"{kind}."
2926        return f"@@{kind}{this}"
def placeholder_sql(self, expression: sqlglot.expressions.Placeholder) -> str:
2928    def placeholder_sql(self, expression: exp.Placeholder) -> str:
2929        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
def subquery_sql(self, expression: sqlglot.expressions.Subquery, sep: str = ' AS ') -> str:
2931    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2932        alias = self.sql(expression, "alias")
2933        alias = f"{sep}{alias}" if alias else ""
2934        sample = self.sql(expression, "sample")
2935        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
2936            alias = f"{sample}{alias}"
2937
2938            # Set to None so it's not generated again by self.query_modifiers()
2939            expression.set("sample", None)
2940
2941        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2942        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2943        return self.prepend_ctes(expression, sql)
def qualify_sql(self, expression: sqlglot.expressions.Qualify) -> str:
2945    def qualify_sql(self, expression: exp.Qualify) -> str:
2946        this = self.indent(self.sql(expression, "this"))
2947        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
def unnest_sql(self, expression: sqlglot.expressions.Unnest) -> str:
2949    def unnest_sql(self, expression: exp.Unnest) -> str:
2950        args = self.expressions(expression, flat=True)
2951
2952        alias = expression.args.get("alias")
2953        offset = expression.args.get("offset")
2954
2955        if self.UNNEST_WITH_ORDINALITY:
2956            if alias and isinstance(offset, exp.Expression):
2957                alias.append("columns", offset)
2958
2959        if alias and self.dialect.UNNEST_COLUMN_ONLY:
2960            columns = alias.columns
2961            alias = self.sql(columns[0]) if columns else ""
2962        else:
2963            alias = self.sql(alias)
2964
2965        alias = f" AS {alias}" if alias else alias
2966        if self.UNNEST_WITH_ORDINALITY:
2967            suffix = f" WITH ORDINALITY{alias}" if offset else alias
2968        else:
2969            if isinstance(offset, exp.Expression):
2970                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
2971            elif offset:
2972                suffix = f"{alias} WITH OFFSET"
2973            else:
2974                suffix = alias
2975
2976        return f"UNNEST({args}){suffix}"
def prewhere_sql(self, expression: sqlglot.expressions.PreWhere) -> str:
2978    def prewhere_sql(self, expression: exp.PreWhere) -> str:
2979        return ""
def where_sql(self, expression: sqlglot.expressions.Where) -> str:
2981    def where_sql(self, expression: exp.Where) -> str:
2982        this = self.indent(self.sql(expression, "this"))
2983        return f"{self.seg('WHERE')}{self.sep()}{this}"
def window_sql(self, expression: sqlglot.expressions.Window) -> str:
2985    def window_sql(self, expression: exp.Window) -> str:
2986        this = self.sql(expression, "this")
2987        partition = self.partition_by_sql(expression)
2988        order = expression.args.get("order")
2989        order = self.order_sql(order, flat=True) if order else ""
2990        spec = self.sql(expression, "spec")
2991        alias = self.sql(expression, "alias")
2992        over = self.sql(expression, "over") or "OVER"
2993
2994        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
2995
2996        first = expression.args.get("first")
2997        if first is None:
2998            first = ""
2999        else:
3000            first = "FIRST" if first else "LAST"
3001
3002        if not partition and not order and not spec and alias:
3003            return f"{this} {alias}"
3004
3005        args = self.format_args(
3006            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3007        )
3008        return f"{this} ({args})"
def partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
3010    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3011        partition = self.expressions(expression, key="partition_by", flat=True)
3012        return f"PARTITION BY {partition}" if partition else ""
def windowspec_sql(self, expression: sqlglot.expressions.WindowSpec) -> str:
3014    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3015        kind = self.sql(expression, "kind")
3016        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3017        end = (
3018            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3019            or "CURRENT ROW"
3020        )
3021
3022        window_spec = f"{kind} BETWEEN {start} AND {end}"
3023
3024        exclude = self.sql(expression, "exclude")
3025        if exclude:
3026            if self.SUPPORTS_WINDOW_EXCLUDE:
3027                window_spec += f" EXCLUDE {exclude}"
3028            else:
3029                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3030
3031        return window_spec
def withingroup_sql(self, expression: sqlglot.expressions.WithinGroup) -> str:
3033    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3034        this = self.sql(expression, "this")
3035        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3036        return f"{this} WITHIN GROUP ({expression_sql})"
def between_sql(self, expression: sqlglot.expressions.Between) -> str:
3038    def between_sql(self, expression: exp.Between) -> str:
3039        this = self.sql(expression, "this")
3040        low = self.sql(expression, "low")
3041        high = self.sql(expression, "high")
3042        symmetric = expression.args.get("symmetric")
3043
3044        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3045            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3046
3047        flag = (
3048            " SYMMETRIC"
3049            if symmetric
3050            else " ASYMMETRIC"
3051            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3052            else ""  # silently drop ASYMMETRIC – semantics identical
3053        )
3054        return f"{this} BETWEEN{flag} {low} AND {high}"
def bracket_offset_expressions( self, expression: sqlglot.expressions.Bracket, index_offset: Optional[int] = None) -> List[sqlglot.expressions.Expression]:
3056    def bracket_offset_expressions(
3057        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
3058    ) -> t.List[exp.Expression]:
3059        return apply_index_offset(
3060            expression.this,
3061            expression.expressions,
3062            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3063            dialect=self.dialect,
3064        )
def bracket_sql(self, expression: sqlglot.expressions.Bracket) -> str:
3066    def bracket_sql(self, expression: exp.Bracket) -> str:
3067        expressions = self.bracket_offset_expressions(expression)
3068        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3069        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
def all_sql(self, expression: sqlglot.expressions.All) -> str:
3071    def all_sql(self, expression: exp.All) -> str:
3072        this = self.sql(expression, "this")
3073        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3074            this = self.wrap(this)
3075        return f"ALL {this}"
def any_sql(self, expression: sqlglot.expressions.Any) -> str:
3077    def any_sql(self, expression: exp.Any) -> str:
3078        this = self.sql(expression, "this")
3079        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3080            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3081                this = self.wrap(this)
3082            return f"ANY{this}"
3083        return f"ANY {this}"
def exists_sql(self, expression: sqlglot.expressions.Exists) -> str:
3085    def exists_sql(self, expression: exp.Exists) -> str:
3086        return f"EXISTS{self.wrap(expression)}"
def case_sql(self, expression: sqlglot.expressions.Case) -> str:
3088    def case_sql(self, expression: exp.Case) -> str:
3089        this = self.sql(expression, "this")
3090        statements = [f"CASE {this}" if this else "CASE"]
3091
3092        for e in expression.args["ifs"]:
3093            statements.append(f"WHEN {self.sql(e, 'this')}")
3094            statements.append(f"THEN {self.sql(e, 'true')}")
3095
3096        default = self.sql(expression, "default")
3097
3098        if default:
3099            statements.append(f"ELSE {default}")
3100
3101        statements.append("END")
3102
3103        if self.pretty and self.too_wide(statements):
3104            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3105
3106        return " ".join(statements)
def constraint_sql(self, expression: sqlglot.expressions.Constraint) -> str:
3108    def constraint_sql(self, expression: exp.Constraint) -> str:
3109        this = self.sql(expression, "this")
3110        expressions = self.expressions(expression, flat=True)
3111        return f"CONSTRAINT {this} {expressions}"
def nextvaluefor_sql(self, expression: sqlglot.expressions.NextValueFor) -> str:
3113    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3114        order = expression.args.get("order")
3115        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3116        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
def extract_sql(self, expression: sqlglot.expressions.Extract) -> str:
3118    def extract_sql(self, expression: exp.Extract) -> str:
3119        from sqlglot.dialects.dialect import map_date_part
3120
3121        this = (
3122            map_date_part(expression.this, self.dialect)
3123            if self.NORMALIZE_EXTRACT_DATE_PARTS
3124            else expression.this
3125        )
3126        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3127        expression_sql = self.sql(expression, "expression")
3128
3129        return f"EXTRACT({this_sql} FROM {expression_sql})"
def trim_sql(self, expression: sqlglot.expressions.Trim) -> str:
3131    def trim_sql(self, expression: exp.Trim) -> str:
3132        trim_type = self.sql(expression, "position")
3133
3134        if trim_type == "LEADING":
3135            func_name = "LTRIM"
3136        elif trim_type == "TRAILING":
3137            func_name = "RTRIM"
3138        else:
3139            func_name = "TRIM"
3140
3141        return self.func(func_name, expression.this, expression.expression)
def convert_concat_args( self, expression: sqlglot.expressions.Concat | sqlglot.expressions.ConcatWs) -> List[sqlglot.expressions.Expression]:
3143    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
3144        args = expression.expressions
3145        if isinstance(expression, exp.ConcatWs):
3146            args = args[1:]  # Skip the delimiter
3147
3148        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3149            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
3150
3151        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
3152
3153            def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression:
3154                if not e.type:
3155                    from sqlglot.optimizer.annotate_types import annotate_types
3156
3157                    e = annotate_types(e, dialect=self.dialect)
3158
3159                if e.is_string or e.is_type(exp.DataType.Type.ARRAY):
3160                    return e
3161
3162                return exp.func("coalesce", e, exp.Literal.string(""))
3163
3164            args = [_wrap_with_coalesce(e) for e in args]
3165
3166        return args
def concat_sql(self, expression: sqlglot.expressions.Concat) -> str:
3168    def concat_sql(self, expression: exp.Concat) -> str:
3169        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3170            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3171            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3172            # instead of coalescing them to empty string.
3173            from sqlglot.dialects.dialect import concat_to_dpipe_sql
3174
3175            return concat_to_dpipe_sql(self, expression)
3176
3177        expressions = self.convert_concat_args(expression)
3178
3179        # Some dialects don't allow a single-argument CONCAT call
3180        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3181            return self.sql(expressions[0])
3182
3183        return self.func("CONCAT", *expressions)
def concatws_sql(self, expression: sqlglot.expressions.ConcatWs) -> str:
3185    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3186        return self.func(
3187            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3188        )
def check_sql(self, expression: sqlglot.expressions.Check) -> str:
3190    def check_sql(self, expression: exp.Check) -> str:
3191        this = self.sql(expression, key="this")
3192        return f"CHECK ({this})"
def foreignkey_sql(self, expression: sqlglot.expressions.ForeignKey) -> str:
3194    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3195        expressions = self.expressions(expression, flat=True)
3196        expressions = f" ({expressions})" if expressions else ""
3197        reference = self.sql(expression, "reference")
3198        reference = f" {reference}" if reference else ""
3199        delete = self.sql(expression, "delete")
3200        delete = f" ON DELETE {delete}" if delete else ""
3201        update = self.sql(expression, "update")
3202        update = f" ON UPDATE {update}" if update else ""
3203        options = self.expressions(expression, key="options", flat=True, sep=" ")
3204        options = f" {options}" if options else ""
3205        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
def primarykey_sql(self, expression: sqlglot.expressions.PrimaryKey) -> str:
3207    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3208        this = self.sql(expression, "this")
3209        this = f" {this}" if this else ""
3210        expressions = self.expressions(expression, flat=True)
3211        include = self.sql(expression, "include")
3212        options = self.expressions(expression, key="options", flat=True, sep=" ")
3213        options = f" {options}" if options else ""
3214        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
def if_sql(self, expression: sqlglot.expressions.If) -> str:
3216    def if_sql(self, expression: exp.If) -> str:
3217        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
def matchagainst_sql(self, expression: sqlglot.expressions.MatchAgainst) -> str:
3219    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3220        if self.MATCH_AGAINST_TABLE_PREFIX:
3221            expressions = []
3222            for expr in expression.expressions:
3223                if isinstance(expr, exp.Table):
3224                    expressions.append(f"TABLE {self.sql(expr)}")
3225                else:
3226                    expressions.append(expr)
3227        else:
3228            expressions = expression.expressions
3229
3230        modifier = expression.args.get("modifier")
3231        modifier = f" {modifier}" if modifier else ""
3232        return (
3233            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3234        )
def jsonkeyvalue_sql(self, expression: sqlglot.expressions.JSONKeyValue) -> str:
3236    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3237        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
def jsonpath_sql(self, expression: sqlglot.expressions.JSONPath) -> str:
3239    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3240        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3241
3242        if expression.args.get("escape"):
3243            path = self.escape_str(path)
3244
3245        if self.QUOTE_JSON_PATH:
3246            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3247
3248        return path
def json_path_part(self, expression: int | str | sqlglot.expressions.JSONPathPart) -> str:
3250    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3251        if isinstance(expression, exp.JSONPathPart):
3252            transform = self.TRANSFORMS.get(expression.__class__)
3253            if not callable(transform):
3254                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3255                return ""
3256
3257            return transform(self, expression)
3258
3259        if isinstance(expression, int):
3260            return str(expression)
3261
3262        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3263            escaped = expression.replace("'", "\\'")
3264            escaped = f"\\'{expression}\\'"
3265        else:
3266            escaped = expression.replace('"', '\\"')
3267            escaped = f'"{escaped}"'
3268
3269        return escaped
def formatjson_sql(self, expression: sqlglot.expressions.FormatJson) -> str:
3271    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3272        return f"{self.sql(expression, 'this')} FORMAT JSON"
def formatphrase_sql(self, expression: sqlglot.expressions.FormatPhrase) -> str:
3274    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3275        # Output the Teradata column FORMAT override.
3276        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3277        this = self.sql(expression, "this")
3278        fmt = self.sql(expression, "format")
3279        return f"{this} (FORMAT {fmt})"
def jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3281    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3282        null_handling = expression.args.get("null_handling")
3283        null_handling = f" {null_handling}" if null_handling else ""
3284
3285        unique_keys = expression.args.get("unique_keys")
3286        if unique_keys is not None:
3287            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3288        else:
3289            unique_keys = ""
3290
3291        return_type = self.sql(expression, "return_type")
3292        return_type = f" RETURNING {return_type}" if return_type else ""
3293        encoding = self.sql(expression, "encoding")
3294        encoding = f" ENCODING {encoding}" if encoding else ""
3295
3296        return self.func(
3297            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3298            *expression.expressions,
3299            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3300        )
def jsonobjectagg_sql(self, expression: sqlglot.expressions.JSONObjectAgg) -> str:
3302    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3303        return self.jsonobject_sql(expression)
def jsonarray_sql(self, expression: sqlglot.expressions.JSONArray) -> str:
3305    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3306        null_handling = expression.args.get("null_handling")
3307        null_handling = f" {null_handling}" if null_handling else ""
3308        return_type = self.sql(expression, "return_type")
3309        return_type = f" RETURNING {return_type}" if return_type else ""
3310        strict = " STRICT" if expression.args.get("strict") else ""
3311        return self.func(
3312            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3313        )
def jsonarrayagg_sql(self, expression: sqlglot.expressions.JSONArrayAgg) -> str:
3315    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3316        this = self.sql(expression, "this")
3317        order = self.sql(expression, "order")
3318        null_handling = expression.args.get("null_handling")
3319        null_handling = f" {null_handling}" if null_handling else ""
3320        return_type = self.sql(expression, "return_type")
3321        return_type = f" RETURNING {return_type}" if return_type else ""
3322        strict = " STRICT" if expression.args.get("strict") else ""
3323        return self.func(
3324            "JSON_ARRAYAGG",
3325            this,
3326            suffix=f"{order}{null_handling}{return_type}{strict})",
3327        )
def jsoncolumndef_sql(self, expression: sqlglot.expressions.JSONColumnDef) -> str:
3329    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3330        path = self.sql(expression, "path")
3331        path = f" PATH {path}" if path else ""
3332        nested_schema = self.sql(expression, "nested_schema")
3333
3334        if nested_schema:
3335            return f"NESTED{path} {nested_schema}"
3336
3337        this = self.sql(expression, "this")
3338        kind = self.sql(expression, "kind")
3339        kind = f" {kind}" if kind else ""
3340
3341        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3342        return f"{this}{kind}{path}{ordinality}"
def jsonschema_sql(self, expression: sqlglot.expressions.JSONSchema) -> str:
3344    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3345        return self.func("COLUMNS", *expression.expressions)
def jsontable_sql(self, expression: sqlglot.expressions.JSONTable) -> str:
3347    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3348        this = self.sql(expression, "this")
3349        path = self.sql(expression, "path")
3350        path = f", {path}" if path else ""
3351        error_handling = expression.args.get("error_handling")
3352        error_handling = f" {error_handling}" if error_handling else ""
3353        empty_handling = expression.args.get("empty_handling")
3354        empty_handling = f" {empty_handling}" if empty_handling else ""
3355        schema = self.sql(expression, "schema")
3356        return self.func(
3357            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3358        )
def openjsoncolumndef_sql(self, expression: sqlglot.expressions.OpenJSONColumnDef) -> str:
3360    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3361        this = self.sql(expression, "this")
3362        kind = self.sql(expression, "kind")
3363        path = self.sql(expression, "path")
3364        path = f" {path}" if path else ""
3365        as_json = " AS JSON" if expression.args.get("as_json") else ""
3366        return f"{this} {kind}{path}{as_json}"
def openjson_sql(self, expression: sqlglot.expressions.OpenJSON) -> str:
3368    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3369        this = self.sql(expression, "this")
3370        path = self.sql(expression, "path")
3371        path = f", {path}" if path else ""
3372        expressions = self.expressions(expression)
3373        with_ = (
3374            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3375            if expressions
3376            else ""
3377        )
3378        return f"OPENJSON({this}{path}){with_}"
def in_sql(self, expression: sqlglot.expressions.In) -> str:
3380    def in_sql(self, expression: exp.In) -> str:
3381        query = expression.args.get("query")
3382        unnest = expression.args.get("unnest")
3383        field = expression.args.get("field")
3384        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3385
3386        if query:
3387            in_sql = self.sql(query)
3388        elif unnest:
3389            in_sql = self.in_unnest_op(unnest)
3390        elif field:
3391            in_sql = self.sql(field)
3392        else:
3393            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3394
3395        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
def in_unnest_op(self, unnest: sqlglot.expressions.Unnest) -> str:
3397    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3398        return f"(SELECT {self.sql(unnest)})"
def interval_sql(self, expression: sqlglot.expressions.Interval) -> str:
3400    def interval_sql(self, expression: exp.Interval) -> str:
3401        unit_expression = expression.args.get("unit")
3402        unit = self.sql(unit_expression) if unit_expression else ""
3403        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3404            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3405        unit = f" {unit}" if unit else ""
3406
3407        if self.SINGLE_STRING_INTERVAL:
3408            this = expression.this.name if expression.this else ""
3409            if this:
3410                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3411                    return f"INTERVAL '{this}'{unit}"
3412                return f"INTERVAL '{this}{unit}'"
3413            return f"INTERVAL{unit}"
3414
3415        this = self.sql(expression, "this")
3416        if this:
3417            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3418            this = f" {this}" if unwrapped else f" ({this})"
3419
3420        return f"INTERVAL{this}{unit}"
def return_sql(self, expression: sqlglot.expressions.Return) -> str:
3422    def return_sql(self, expression: exp.Return) -> str:
3423        return f"RETURN {self.sql(expression, 'this')}"
def reference_sql(self, expression: sqlglot.expressions.Reference) -> str:
3425    def reference_sql(self, expression: exp.Reference) -> str:
3426        this = self.sql(expression, "this")
3427        expressions = self.expressions(expression, flat=True)
3428        expressions = f"({expressions})" if expressions else ""
3429        options = self.expressions(expression, key="options", flat=True, sep=" ")
3430        options = f" {options}" if options else ""
3431        return f"REFERENCES {this}{expressions}{options}"
def anonymous_sql(self, expression: sqlglot.expressions.Anonymous) -> str:
3433    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3434        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3435        parent = expression.parent
3436        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3437        return self.func(
3438            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3439        )
def paren_sql(self, expression: sqlglot.expressions.Paren) -> str:
3441    def paren_sql(self, expression: exp.Paren) -> str:
3442        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3443        return f"({sql}{self.seg(')', sep='')}"
def neg_sql(self, expression: sqlglot.expressions.Neg) -> str:
3445    def neg_sql(self, expression: exp.Neg) -> str:
3446        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3447        this_sql = self.sql(expression, "this")
3448        sep = " " if this_sql[0] == "-" else ""
3449        return f"-{sep}{this_sql}"
def not_sql(self, expression: sqlglot.expressions.Not) -> str:
3451    def not_sql(self, expression: exp.Not) -> str:
3452        return f"NOT {self.sql(expression, 'this')}"
def alias_sql(self, expression: sqlglot.expressions.Alias) -> str:
3454    def alias_sql(self, expression: exp.Alias) -> str:
3455        alias = self.sql(expression, "alias")
3456        alias = f" AS {alias}" if alias else ""
3457        return f"{self.sql(expression, 'this')}{alias}"
def pivotalias_sql(self, expression: sqlglot.expressions.PivotAlias) -> str:
3459    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3460        alias = expression.args["alias"]
3461
3462        parent = expression.parent
3463        pivot = parent and parent.parent
3464
3465        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3466            identifier_alias = isinstance(alias, exp.Identifier)
3467            literal_alias = isinstance(alias, exp.Literal)
3468
3469            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3470                alias.replace(exp.Literal.string(alias.output_name))
3471            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3472                alias.replace(exp.to_identifier(alias.output_name))
3473
3474        return self.alias_sql(expression)
def aliases_sql(self, expression: sqlglot.expressions.Aliases) -> str:
3476    def aliases_sql(self, expression: exp.Aliases) -> str:
3477        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
def atindex_sql(self, expression: sqlglot.expressions.AtTimeZone) -> str:
3479    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3480        this = self.sql(expression, "this")
3481        index = self.sql(expression, "expression")
3482        return f"{this} AT {index}"
def attimezone_sql(self, expression: sqlglot.expressions.AtTimeZone) -> str:
3484    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3485        this = self.sql(expression, "this")
3486        zone = self.sql(expression, "zone")
3487        return f"{this} AT TIME ZONE {zone}"
def fromtimezone_sql(self, expression: sqlglot.expressions.FromTimeZone) -> str:
3489    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3490        this = self.sql(expression, "this")
3491        zone = self.sql(expression, "zone")
3492        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
def add_sql(self, expression: sqlglot.expressions.Add) -> str:
3494    def add_sql(self, expression: exp.Add) -> str:
3495        return self.binary(expression, "+")
def and_sql( self, expression: sqlglot.expressions.And, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3497    def and_sql(
3498        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3499    ) -> str:
3500        return self.connector_sql(expression, "AND", stack)
def or_sql( self, expression: sqlglot.expressions.Or, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3502    def or_sql(
3503        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3504    ) -> str:
3505        return self.connector_sql(expression, "OR", stack)
def xor_sql( self, expression: sqlglot.expressions.Xor, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3507    def xor_sql(
3508        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3509    ) -> str:
3510        return self.connector_sql(expression, "XOR", stack)
def connector_sql( self, expression: sqlglot.expressions.Connector, op: str, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3512    def connector_sql(
3513        self,
3514        expression: exp.Connector,
3515        op: str,
3516        stack: t.Optional[t.List[str | exp.Expression]] = None,
3517    ) -> str:
3518        if stack is not None:
3519            if expression.expressions:
3520                stack.append(self.expressions(expression, sep=f" {op} "))
3521            else:
3522                stack.append(expression.right)
3523                if expression.comments and self.comments:
3524                    for comment in expression.comments:
3525                        if comment:
3526                            op += f" /*{self.sanitize_comment(comment)}*/"
3527                stack.extend((op, expression.left))
3528            return op
3529
3530        stack = [expression]
3531        sqls: t.List[str] = []
3532        ops = set()
3533
3534        while stack:
3535            node = stack.pop()
3536            if isinstance(node, exp.Connector):
3537                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3538            else:
3539                sql = self.sql(node)
3540                if sqls and sqls[-1] in ops:
3541                    sqls[-1] += f" {sql}"
3542                else:
3543                    sqls.append(sql)
3544
3545        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3546        return sep.join(sqls)
def bitwiseand_sql(self, expression: sqlglot.expressions.BitwiseAnd) -> str:
3548    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3549        return self.binary(expression, "&")
def bitwiseleftshift_sql(self, expression: sqlglot.expressions.BitwiseLeftShift) -> str:
3551    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3552        return self.binary(expression, "<<")
def bitwisenot_sql(self, expression: sqlglot.expressions.BitwiseNot) -> str:
3554    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3555        return f"~{self.sql(expression, 'this')}"
def bitwiseor_sql(self, expression: sqlglot.expressions.BitwiseOr) -> str:
3557    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3558        return self.binary(expression, "|")
def bitwiserightshift_sql(self, expression: sqlglot.expressions.BitwiseRightShift) -> str:
3560    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3561        return self.binary(expression, ">>")
def bitwisexor_sql(self, expression: sqlglot.expressions.BitwiseXor) -> str:
3563    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3564        return self.binary(expression, "^")
def cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3566    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3567        format_sql = self.sql(expression, "format")
3568        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3569        to_sql = self.sql(expression, "to")
3570        to_sql = f" {to_sql}" if to_sql else ""
3571        action = self.sql(expression, "action")
3572        action = f" {action}" if action else ""
3573        default = self.sql(expression, "default")
3574        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3575        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
def strtotime_sql(self, expression: sqlglot.expressions.StrToTime) -> str:
3578    def strtotime_sql(self, expression: exp.StrToTime) -> str:
3579        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
def currentdate_sql(self, expression: sqlglot.expressions.CurrentDate) -> str:
3581    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3582        zone = self.sql(expression, "this")
3583        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
def collate_sql(self, expression: sqlglot.expressions.Collate) -> str:
3585    def collate_sql(self, expression: exp.Collate) -> str:
3586        if self.COLLATE_IS_FUNC:
3587            return self.function_fallback_sql(expression)
3588        return self.binary(expression, "COLLATE")
def command_sql(self, expression: sqlglot.expressions.Command) -> str:
3590    def command_sql(self, expression: exp.Command) -> str:
3591        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
def comment_sql(self, expression: sqlglot.expressions.Comment) -> str:
3593    def comment_sql(self, expression: exp.Comment) -> str:
3594        this = self.sql(expression, "this")
3595        kind = expression.args["kind"]
3596        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3597        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3598        expression_sql = self.sql(expression, "expression")
3599        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
def mergetreettlaction_sql(self, expression: sqlglot.expressions.MergeTreeTTLAction) -> str:
3601    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3602        this = self.sql(expression, "this")
3603        delete = " DELETE" if expression.args.get("delete") else ""
3604        recompress = self.sql(expression, "recompress")
3605        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3606        to_disk = self.sql(expression, "to_disk")
3607        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3608        to_volume = self.sql(expression, "to_volume")
3609        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3610        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
def mergetreettl_sql(self, expression: sqlglot.expressions.MergeTreeTTL) -> str:
3612    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3613        where = self.sql(expression, "where")
3614        group = self.sql(expression, "group")
3615        aggregates = self.expressions(expression, key="aggregates")
3616        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3617
3618        if not (where or group or aggregates) and len(expression.expressions) == 1:
3619            return f"TTL {self.expressions(expression, flat=True)}"
3620
3621        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
def transaction_sql(self, expression: sqlglot.expressions.Transaction) -> str:
3623    def transaction_sql(self, expression: exp.Transaction) -> str:
3624        modes = self.expressions(expression, key="modes")
3625        modes = f" {modes}" if modes else ""
3626        return f"BEGIN{modes}"
def commit_sql(self, expression: sqlglot.expressions.Commit) -> str:
3628    def commit_sql(self, expression: exp.Commit) -> str:
3629        chain = expression.args.get("chain")
3630        if chain is not None:
3631            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3632
3633        return f"COMMIT{chain or ''}"
def rollback_sql(self, expression: sqlglot.expressions.Rollback) -> str:
3635    def rollback_sql(self, expression: exp.Rollback) -> str:
3636        savepoint = expression.args.get("savepoint")
3637        savepoint = f" TO {savepoint}" if savepoint else ""
3638        return f"ROLLBACK{savepoint}"
def altercolumn_sql(self, expression: sqlglot.expressions.AlterColumn) -> str:
3640    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3641        this = self.sql(expression, "this")
3642
3643        dtype = self.sql(expression, "dtype")
3644        if dtype:
3645            collate = self.sql(expression, "collate")
3646            collate = f" COLLATE {collate}" if collate else ""
3647            using = self.sql(expression, "using")
3648            using = f" USING {using}" if using else ""
3649            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3650            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3651
3652        default = self.sql(expression, "default")
3653        if default:
3654            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3655
3656        comment = self.sql(expression, "comment")
3657        if comment:
3658            return f"ALTER COLUMN {this} COMMENT {comment}"
3659
3660        visible = expression.args.get("visible")
3661        if visible:
3662            return f"ALTER COLUMN {this} SET {visible}"
3663
3664        allow_null = expression.args.get("allow_null")
3665        drop = expression.args.get("drop")
3666
3667        if not drop and not allow_null:
3668            self.unsupported("Unsupported ALTER COLUMN syntax")
3669
3670        if allow_null is not None:
3671            keyword = "DROP" if drop else "SET"
3672            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3673
3674        return f"ALTER COLUMN {this} DROP DEFAULT"
def alterindex_sql(self, expression: sqlglot.expressions.AlterIndex) -> str:
3676    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3677        this = self.sql(expression, "this")
3678
3679        visible = expression.args.get("visible")
3680        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3681
3682        return f"ALTER INDEX {this} {visible_sql}"
def alterdiststyle_sql(self, expression: sqlglot.expressions.AlterDistStyle) -> str:
3684    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3685        this = self.sql(expression, "this")
3686        if not isinstance(expression.this, exp.Var):
3687            this = f"KEY DISTKEY {this}"
3688        return f"ALTER DISTSTYLE {this}"
def altersortkey_sql(self, expression: sqlglot.expressions.AlterSortKey) -> str:
3690    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3691        compound = " COMPOUND" if expression.args.get("compound") else ""
3692        this = self.sql(expression, "this")
3693        expressions = self.expressions(expression, flat=True)
3694        expressions = f"({expressions})" if expressions else ""
3695        return f"ALTER{compound} SORTKEY {this or expressions}"
def alterrename_sql( self, expression: sqlglot.expressions.AlterRename, include_to: bool = True) -> str:
3697    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
3698        if not self.RENAME_TABLE_WITH_DB:
3699            # Remove db from tables
3700            expression = expression.transform(
3701                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3702            ).assert_is(exp.AlterRename)
3703        this = self.sql(expression, "this")
3704        to_kw = " TO" if include_to else ""
3705        return f"RENAME{to_kw} {this}"
def renamecolumn_sql(self, expression: sqlglot.expressions.RenameColumn) -> str:
3707    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3708        exists = " IF EXISTS" if expression.args.get("exists") else ""
3709        old_column = self.sql(expression, "this")
3710        new_column = self.sql(expression, "to")
3711        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
def alterset_sql(self, expression: sqlglot.expressions.AlterSet) -> str:
3713    def alterset_sql(self, expression: exp.AlterSet) -> str:
3714        exprs = self.expressions(expression, flat=True)
3715        if self.ALTER_SET_WRAPPED:
3716            exprs = f"({exprs})"
3717
3718        return f"SET {exprs}"
def alter_sql(self, expression: sqlglot.expressions.Alter) -> str:
3720    def alter_sql(self, expression: exp.Alter) -> str:
3721        actions = expression.args["actions"]
3722
3723        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3724            actions[0], exp.ColumnDef
3725        ):
3726            actions_sql = self.expressions(expression, key="actions", flat=True)
3727            actions_sql = f"ADD {actions_sql}"
3728        else:
3729            actions_list = []
3730            for action in actions:
3731                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3732                    action_sql = self.add_column_sql(action)
3733                else:
3734                    action_sql = self.sql(action)
3735                    if isinstance(action, exp.Query):
3736                        action_sql = f"AS {action_sql}"
3737
3738                actions_list.append(action_sql)
3739
3740            actions_sql = self.format_args(*actions_list).lstrip("\n")
3741
3742        exists = " IF EXISTS" if expression.args.get("exists") else ""
3743        on_cluster = self.sql(expression, "cluster")
3744        on_cluster = f" {on_cluster}" if on_cluster else ""
3745        only = " ONLY" if expression.args.get("only") else ""
3746        options = self.expressions(expression, key="options")
3747        options = f", {options}" if options else ""
3748        kind = self.sql(expression, "kind")
3749        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3750        check = " WITH CHECK" if expression.args.get("check") else ""
3751        cascade = (
3752            " CASCADE"
3753            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
3754            else ""
3755        )
3756        this = self.sql(expression, "this")
3757        this = f" {this}" if this else ""
3758
3759        return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
def altersession_sql(self, expression: sqlglot.expressions.AlterSession) -> str:
3761    def altersession_sql(self, expression: exp.AlterSession) -> str:
3762        items_sql = self.expressions(expression, flat=True)
3763        keyword = "UNSET" if expression.args.get("unset") else "SET"
3764        return f"{keyword} {items_sql}"
def add_column_sql(self, expression: sqlglot.expressions.Expression) -> str:
3766    def add_column_sql(self, expression: exp.Expression) -> str:
3767        sql = self.sql(expression)
3768        if isinstance(expression, exp.Schema):
3769            column_text = " COLUMNS"
3770        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3771            column_text = " COLUMN"
3772        else:
3773            column_text = ""
3774
3775        return f"ADD{column_text} {sql}"
def droppartition_sql(self, expression: sqlglot.expressions.DropPartition) -> str:
3777    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3778        expressions = self.expressions(expression)
3779        exists = " IF EXISTS " if expression.args.get("exists") else " "
3780        return f"DROP{exists}{expressions}"
def addconstraint_sql(self, expression: sqlglot.expressions.AddConstraint) -> str:
3782    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3783        return f"ADD {self.expressions(expression, indent=False)}"
def addpartition_sql(self, expression: sqlglot.expressions.AddPartition) -> str:
3785    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3786        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3787        location = self.sql(expression, "location")
3788        location = f" {location}" if location else ""
3789        return f"ADD {exists}{self.sql(expression.this)}{location}"
def distinct_sql(self, expression: sqlglot.expressions.Distinct) -> str:
3791    def distinct_sql(self, expression: exp.Distinct) -> str:
3792        this = self.expressions(expression, flat=True)
3793
3794        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3795            case = exp.case()
3796            for arg in expression.expressions:
3797                case = case.when(arg.is_(exp.null()), exp.null())
3798            this = self.sql(case.else_(f"({this})"))
3799
3800        this = f" {this}" if this else ""
3801
3802        on = self.sql(expression, "on")
3803        on = f" ON {on}" if on else ""
3804        return f"DISTINCT{this}{on}"
def ignorenulls_sql(self, expression: sqlglot.expressions.IgnoreNulls) -> str:
3806    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3807        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
def respectnulls_sql(self, expression: sqlglot.expressions.RespectNulls) -> str:
3809    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3810        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
def havingmax_sql(self, expression: sqlglot.expressions.HavingMax) -> str:
3812    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3813        this_sql = self.sql(expression, "this")
3814        expression_sql = self.sql(expression, "expression")
3815        kind = "MAX" if expression.args.get("max") else "MIN"
3816        return f"{this_sql} HAVING {kind} {expression_sql}"
def intdiv_sql(self, expression: sqlglot.expressions.IntDiv) -> str:
3818    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3819        return self.sql(
3820            exp.Cast(
3821                this=exp.Div(this=expression.this, expression=expression.expression),
3822                to=exp.DataType(this=exp.DataType.Type.INT),
3823            )
3824        )
def dpipe_sql(self, expression: sqlglot.expressions.DPipe) -> str:
3826    def dpipe_sql(self, expression: exp.DPipe) -> str:
3827        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3828            return self.func(
3829                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3830            )
3831        return self.binary(expression, "||")
def div_sql(self, expression: sqlglot.expressions.Div) -> str:
3833    def div_sql(self, expression: exp.Div) -> str:
3834        l, r = expression.left, expression.right
3835
3836        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3837            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3838
3839        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3840            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3841                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3842
3843        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3844            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3845                return self.sql(
3846                    exp.cast(
3847                        l / r,
3848                        to=exp.DataType.Type.BIGINT,
3849                    )
3850                )
3851
3852        return self.binary(expression, "/")
def safedivide_sql(self, expression: sqlglot.expressions.SafeDivide) -> str:
3854    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3855        n = exp._wrap(expression.this, exp.Binary)
3856        d = exp._wrap(expression.expression, exp.Binary)
3857        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
def overlaps_sql(self, expression: sqlglot.expressions.Overlaps) -> str:
3859    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3860        return self.binary(expression, "OVERLAPS")
def distance_sql(self, expression: sqlglot.expressions.Distance) -> str:
3862    def distance_sql(self, expression: exp.Distance) -> str:
3863        return self.binary(expression, "<->")
def dot_sql(self, expression: sqlglot.expressions.Dot) -> str:
3865    def dot_sql(self, expression: exp.Dot) -> str:
3866        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
def eq_sql(self, expression: sqlglot.expressions.EQ) -> str:
3868    def eq_sql(self, expression: exp.EQ) -> str:
3869        return self.binary(expression, "=")
def propertyeq_sql(self, expression: sqlglot.expressions.PropertyEQ) -> str:
3871    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3872        return self.binary(expression, ":=")
def escape_sql(self, expression: sqlglot.expressions.Escape) -> str:
3874    def escape_sql(self, expression: exp.Escape) -> str:
3875        return self.binary(expression, "ESCAPE")
def glob_sql(self, expression: sqlglot.expressions.Glob) -> str:
3877    def glob_sql(self, expression: exp.Glob) -> str:
3878        return self.binary(expression, "GLOB")
def gt_sql(self, expression: sqlglot.expressions.GT) -> str:
3880    def gt_sql(self, expression: exp.GT) -> str:
3881        return self.binary(expression, ">")
def gte_sql(self, expression: sqlglot.expressions.GTE) -> str:
3883    def gte_sql(self, expression: exp.GTE) -> str:
3884        return self.binary(expression, ">=")
def is_sql(self, expression: sqlglot.expressions.Is) -> str:
3886    def is_sql(self, expression: exp.Is) -> str:
3887        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
3888            return self.sql(
3889                expression.this if expression.expression.this else exp.not_(expression.this)
3890            )
3891        return self.binary(expression, "IS")
def like_sql(self, expression: sqlglot.expressions.Like) -> str:
3924    def like_sql(self, expression: exp.Like) -> str:
3925        return self._like_sql(expression)
def ilike_sql(self, expression: sqlglot.expressions.ILike) -> str:
3927    def ilike_sql(self, expression: exp.ILike) -> str:
3928        return self._like_sql(expression)
def match_sql(self, expression: sqlglot.expressions.Match) -> str:
3930    def match_sql(self, expression: exp.Match) -> str:
3931        return self.binary(expression, "MATCH")
def similarto_sql(self, expression: sqlglot.expressions.SimilarTo) -> str:
3933    def similarto_sql(self, expression: exp.SimilarTo) -> str:
3934        return self.binary(expression, "SIMILAR TO")
def lt_sql(self, expression: sqlglot.expressions.LT) -> str:
3936    def lt_sql(self, expression: exp.LT) -> str:
3937        return self.binary(expression, "<")
def lte_sql(self, expression: sqlglot.expressions.LTE) -> str:
3939    def lte_sql(self, expression: exp.LTE) -> str:
3940        return self.binary(expression, "<=")
def mod_sql(self, expression: sqlglot.expressions.Mod) -> str:
3942    def mod_sql(self, expression: exp.Mod) -> str:
3943        return self.binary(expression, "%")
def mul_sql(self, expression: sqlglot.expressions.Mul) -> str:
3945    def mul_sql(self, expression: exp.Mul) -> str:
3946        return self.binary(expression, "*")
def neq_sql(self, expression: sqlglot.expressions.NEQ) -> str:
3948    def neq_sql(self, expression: exp.NEQ) -> str:
3949        return self.binary(expression, "<>")
def nullsafeeq_sql(self, expression: sqlglot.expressions.NullSafeEQ) -> str:
3951    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
3952        return self.binary(expression, "IS NOT DISTINCT FROM")
def nullsafeneq_sql(self, expression: sqlglot.expressions.NullSafeNEQ) -> str:
3954    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3955        return self.binary(expression, "IS DISTINCT FROM")
def sub_sql(self, expression: sqlglot.expressions.Sub) -> str:
3957    def sub_sql(self, expression: exp.Sub) -> str:
3958        return self.binary(expression, "-")
def trycast_sql(self, expression: sqlglot.expressions.TryCast) -> str:
3960    def trycast_sql(self, expression: exp.TryCast) -> str:
3961        return self.cast_sql(expression, safe_prefix="TRY_")
def jsoncast_sql(self, expression: sqlglot.expressions.JSONCast) -> str:
3963    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
3964        return self.cast_sql(expression)
def try_sql(self, expression: sqlglot.expressions.Try) -> str:
3966    def try_sql(self, expression: exp.Try) -> str:
3967        if not self.TRY_SUPPORTED:
3968            self.unsupported("Unsupported TRY function")
3969            return self.sql(expression, "this")
3970
3971        return self.func("TRY", expression.this)
def log_sql(self, expression: sqlglot.expressions.Log) -> str:
3973    def log_sql(self, expression: exp.Log) -> str:
3974        this = expression.this
3975        expr = expression.expression
3976
3977        if self.dialect.LOG_BASE_FIRST is False:
3978            this, expr = expr, this
3979        elif self.dialect.LOG_BASE_FIRST is None and expr:
3980            if this.name in ("2", "10"):
3981                return self.func(f"LOG{this.name}", expr)
3982
3983            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
3984
3985        return self.func("LOG", this, expr)
def use_sql(self, expression: sqlglot.expressions.Use) -> str:
3987    def use_sql(self, expression: exp.Use) -> str:
3988        kind = self.sql(expression, "kind")
3989        kind = f" {kind}" if kind else ""
3990        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
3991        this = f" {this}" if this else ""
3992        return f"USE{kind}{this}"
def binary(self, expression: sqlglot.expressions.Binary, op: str) -> str:
3994    def binary(self, expression: exp.Binary, op: str) -> str:
3995        sqls: t.List[str] = []
3996        stack: t.List[t.Union[str, exp.Expression]] = [expression]
3997        binary_type = type(expression)
3998
3999        while stack:
4000            node = stack.pop()
4001
4002            if type(node) is binary_type:
4003                op_func = node.args.get("operator")
4004                if op_func:
4005                    op = f"OPERATOR({self.sql(op_func)})"
4006
4007                stack.append(node.right)
4008                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4009                stack.append(node.left)
4010            else:
4011                sqls.append(self.sql(node))
4012
4013        return "".join(sqls)
def ceil_floor( self, expression: sqlglot.expressions.Ceil | sqlglot.expressions.Floor) -> str:
4015    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4016        to_clause = self.sql(expression, "to")
4017        if to_clause:
4018            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4019
4020        return self.function_fallback_sql(expression)
def function_fallback_sql(self, expression: sqlglot.expressions.Func) -> str:
4022    def function_fallback_sql(self, expression: exp.Func) -> str:
4023        args = []
4024
4025        for key in expression.arg_types:
4026            arg_value = expression.args.get(key)
4027
4028            if isinstance(arg_value, list):
4029                for value in arg_value:
4030                    args.append(value)
4031            elif arg_value is not None:
4032                args.append(arg_value)
4033
4034        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4035            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
4036        else:
4037            name = expression.sql_name()
4038
4039        return self.func(name, *args)
def func( self, name: str, *args: Union[str, sqlglot.expressions.Expression, NoneType], prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
4041    def func(
4042        self,
4043        name: str,
4044        *args: t.Optional[exp.Expression | str],
4045        prefix: str = "(",
4046        suffix: str = ")",
4047        normalize: bool = True,
4048    ) -> str:
4049        name = self.normalize_func(name) if normalize else name
4050        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
4052    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
4053        arg_sqls = tuple(
4054            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4055        )
4056        if self.pretty and self.too_wide(arg_sqls):
4057            return self.indent(
4058                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4059            )
4060        return sep.join(arg_sqls)
def too_wide(self, args: Iterable) -> bool:
4062    def too_wide(self, args: t.Iterable) -> bool:
4063        return sum(len(arg) for arg in args) > self.max_text_width
def format_time( self, expression: sqlglot.expressions.Expression, inverse_time_mapping: Optional[Dict[str, str]] = None, inverse_time_trie: Optional[Dict] = None) -> Optional[str]:
4065    def format_time(
4066        self,
4067        expression: exp.Expression,
4068        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
4069        inverse_time_trie: t.Optional[t.Dict] = None,
4070    ) -> t.Optional[str]:
4071        return format_time(
4072            self.sql(expression, "format"),
4073            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4074            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4075        )
def expressions( self, expression: Optional[sqlglot.expressions.Expression] = None, key: Optional[str] = None, sqls: Optional[Collection[Union[str, sqlglot.expressions.Expression]]] = 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:
4077    def expressions(
4078        self,
4079        expression: t.Optional[exp.Expression] = None,
4080        key: t.Optional[str] = None,
4081        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
4082        flat: bool = False,
4083        indent: bool = True,
4084        skip_first: bool = False,
4085        skip_last: bool = False,
4086        sep: str = ", ",
4087        prefix: str = "",
4088        dynamic: bool = False,
4089        new_line: bool = False,
4090    ) -> str:
4091        expressions = expression.args.get(key or "expressions") if expression else sqls
4092
4093        if not expressions:
4094            return ""
4095
4096        if flat:
4097            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4098
4099        num_sqls = len(expressions)
4100        result_sqls = []
4101
4102        for i, e in enumerate(expressions):
4103            sql = self.sql(e, comment=False)
4104            if not sql:
4105                continue
4106
4107            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
4108
4109            if self.pretty:
4110                if self.leading_comma:
4111                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4112                else:
4113                    result_sqls.append(
4114                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4115                    )
4116            else:
4117                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4118
4119        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4120            if new_line:
4121                result_sqls.insert(0, "")
4122                result_sqls.append("")
4123            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4124        else:
4125            result_sql = "".join(result_sqls)
4126
4127        return (
4128            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4129            if indent
4130            else result_sql
4131        )
def op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
4133    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
4134        flat = flat or isinstance(expression.parent, exp.Properties)
4135        expressions_sql = self.expressions(expression, flat=flat)
4136        if flat:
4137            return f"{op} {expressions_sql}"
4138        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
def naked_property(self, expression: sqlglot.expressions.Property) -> str:
4140    def naked_property(self, expression: exp.Property) -> str:
4141        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4142        if not property_name:
4143            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4144        return f"{property_name} {self.sql(expression, 'this')}"
def tag_sql(self, expression: sqlglot.expressions.Tag) -> str:
4146    def tag_sql(self, expression: exp.Tag) -> str:
4147        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
def token_sql(self, token_type: sqlglot.tokens.TokenType) -> str:
4149    def token_sql(self, token_type: TokenType) -> str:
4150        return self.TOKEN_MAPPING.get(token_type, token_type.name)
def userdefinedfunction_sql(self, expression: sqlglot.expressions.UserDefinedFunction) -> str:
4152    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4153        this = self.sql(expression, "this")
4154        expressions = self.no_identify(self.expressions, expression)
4155        expressions = (
4156            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4157        )
4158        return f"{this}{expressions}" if expressions.strip() != "" else this
def joinhint_sql(self, expression: sqlglot.expressions.JoinHint) -> str:
4160    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4161        this = self.sql(expression, "this")
4162        expressions = self.expressions(expression, flat=True)
4163        return f"{this}({expressions})"
def kwarg_sql(self, expression: sqlglot.expressions.Kwarg) -> str:
4165    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4166        return self.binary(expression, "=>")
def when_sql(self, expression: sqlglot.expressions.When) -> str:
4168    def when_sql(self, expression: exp.When) -> str:
4169        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4170        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4171        condition = self.sql(expression, "condition")
4172        condition = f" AND {condition}" if condition else ""
4173
4174        then_expression = expression.args.get("then")
4175        if isinstance(then_expression, exp.Insert):
4176            this = self.sql(then_expression, "this")
4177            this = f"INSERT {this}" if this else "INSERT"
4178            then = self.sql(then_expression, "expression")
4179            then = f"{this} VALUES {then}" if then else this
4180        elif isinstance(then_expression, exp.Update):
4181            if isinstance(then_expression.args.get("expressions"), exp.Star):
4182                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4183            else:
4184                expressions_sql = self.expressions(then_expression)
4185                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4186
4187        else:
4188            then = self.sql(then_expression)
4189        return f"WHEN {matched}{source}{condition} THEN {then}"
def whens_sql(self, expression: sqlglot.expressions.Whens) -> str:
4191    def whens_sql(self, expression: exp.Whens) -> str:
4192        return self.expressions(expression, sep=" ", indent=False)
def merge_sql(self, expression: sqlglot.expressions.Merge) -> str:
4194    def merge_sql(self, expression: exp.Merge) -> str:
4195        table = expression.this
4196        table_alias = ""
4197
4198        hints = table.args.get("hints")
4199        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4200            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4201            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4202
4203        this = self.sql(table)
4204        using = f"USING {self.sql(expression, 'using')}"
4205        whens = self.sql(expression, "whens")
4206
4207        on = self.sql(expression, "on")
4208        on = f"ON {on}" if on else ""
4209
4210        if not on:
4211            on = self.expressions(expression, key="using_cond")
4212            on = f"USING ({on})" if on else ""
4213
4214        returning = self.sql(expression, "returning")
4215        if returning:
4216            whens = f"{whens}{returning}"
4217
4218        sep = self.sep()
4219
4220        return self.prepend_ctes(
4221            expression,
4222            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4223        )
@unsupported_args('format')
def tochar_sql(self, expression: sqlglot.expressions.ToChar) -> str:
4225    @unsupported_args("format")
4226    def tochar_sql(self, expression: exp.ToChar) -> str:
4227        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
def tonumber_sql(self, expression: sqlglot.expressions.ToNumber) -> str:
4229    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4230        if not self.SUPPORTS_TO_NUMBER:
4231            self.unsupported("Unsupported TO_NUMBER function")
4232            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4233
4234        fmt = expression.args.get("format")
4235        if not fmt:
4236            self.unsupported("Conversion format is required for TO_NUMBER")
4237            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4238
4239        return self.func("TO_NUMBER", expression.this, fmt)
def dictproperty_sql(self, expression: sqlglot.expressions.DictProperty) -> str:
4241    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4242        this = self.sql(expression, "this")
4243        kind = self.sql(expression, "kind")
4244        settings_sql = self.expressions(expression, key="settings", sep=" ")
4245        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4246        return f"{this}({kind}{args})"
def dictrange_sql(self, expression: sqlglot.expressions.DictRange) -> str:
4248    def dictrange_sql(self, expression: exp.DictRange) -> str:
4249        this = self.sql(expression, "this")
4250        max = self.sql(expression, "max")
4251        min = self.sql(expression, "min")
4252        return f"{this}(MIN {min} MAX {max})"
def dictsubproperty_sql(self, expression: sqlglot.expressions.DictSubProperty) -> str:
4254    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4255        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
def duplicatekeyproperty_sql(self, expression: sqlglot.expressions.DuplicateKeyProperty) -> str:
4257    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4258        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
def uniquekeyproperty_sql( self, expression: sqlglot.expressions.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
4261    def uniquekeyproperty_sql(
4262        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4263    ) -> str:
4264        return f"{prefix} ({self.expressions(expression, flat=True)})"
def distributedbyproperty_sql(self, expression: sqlglot.expressions.DistributedByProperty) -> str:
4267    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4268        expressions = self.expressions(expression, flat=True)
4269        expressions = f" {self.wrap(expressions)}" if expressions else ""
4270        buckets = self.sql(expression, "buckets")
4271        kind = self.sql(expression, "kind")
4272        buckets = f" BUCKETS {buckets}" if buckets else ""
4273        order = self.sql(expression, "order")
4274        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def oncluster_sql(self, expression: sqlglot.expressions.OnCluster) -> str:
4276    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4277        return ""
def clusteredbyproperty_sql(self, expression: sqlglot.expressions.ClusteredByProperty) -> str:
4279    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4280        expressions = self.expressions(expression, key="expressions", flat=True)
4281        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4282        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4283        buckets = self.sql(expression, "buckets")
4284        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
def anyvalue_sql(self, expression: sqlglot.expressions.AnyValue) -> str:
4286    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4287        this = self.sql(expression, "this")
4288        having = self.sql(expression, "having")
4289
4290        if having:
4291            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4292
4293        return self.func("ANY_VALUE", this)
def querytransform_sql(self, expression: sqlglot.expressions.QueryTransform) -> str:
4295    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4296        transform = self.func("TRANSFORM", *expression.expressions)
4297        row_format_before = self.sql(expression, "row_format_before")
4298        row_format_before = f" {row_format_before}" if row_format_before else ""
4299        record_writer = self.sql(expression, "record_writer")
4300        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4301        using = f" USING {self.sql(expression, 'command_script')}"
4302        schema = self.sql(expression, "schema")
4303        schema = f" AS {schema}" if schema else ""
4304        row_format_after = self.sql(expression, "row_format_after")
4305        row_format_after = f" {row_format_after}" if row_format_after else ""
4306        record_reader = self.sql(expression, "record_reader")
4307        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4308        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
def indexconstraintoption_sql(self, expression: sqlglot.expressions.IndexConstraintOption) -> str:
4310    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4311        key_block_size = self.sql(expression, "key_block_size")
4312        if key_block_size:
4313            return f"KEY_BLOCK_SIZE = {key_block_size}"
4314
4315        using = self.sql(expression, "using")
4316        if using:
4317            return f"USING {using}"
4318
4319        parser = self.sql(expression, "parser")
4320        if parser:
4321            return f"WITH PARSER {parser}"
4322
4323        comment = self.sql(expression, "comment")
4324        if comment:
4325            return f"COMMENT {comment}"
4326
4327        visible = expression.args.get("visible")
4328        if visible is not None:
4329            return "VISIBLE" if visible else "INVISIBLE"
4330
4331        engine_attr = self.sql(expression, "engine_attr")
4332        if engine_attr:
4333            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4334
4335        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4336        if secondary_engine_attr:
4337            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4338
4339        self.unsupported("Unsupported index constraint option.")
4340        return ""
def checkcolumnconstraint_sql(self, expression: sqlglot.expressions.CheckColumnConstraint) -> str:
4342    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4343        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4344        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
def indexcolumnconstraint_sql(self, expression: sqlglot.expressions.IndexColumnConstraint) -> str:
4346    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4347        kind = self.sql(expression, "kind")
4348        kind = f"{kind} INDEX" if kind else "INDEX"
4349        this = self.sql(expression, "this")
4350        this = f" {this}" if this else ""
4351        index_type = self.sql(expression, "index_type")
4352        index_type = f" USING {index_type}" if index_type else ""
4353        expressions = self.expressions(expression, flat=True)
4354        expressions = f" ({expressions})" if expressions else ""
4355        options = self.expressions(expression, key="options", sep=" ")
4356        options = f" {options}" if options else ""
4357        return f"{kind}{this}{index_type}{expressions}{options}"
def nvl2_sql(self, expression: sqlglot.expressions.Nvl2) -> str:
4359    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4360        if self.NVL2_SUPPORTED:
4361            return self.function_fallback_sql(expression)
4362
4363        case = exp.Case().when(
4364            expression.this.is_(exp.null()).not_(copy=False),
4365            expression.args["true"],
4366            copy=False,
4367        )
4368        else_cond = expression.args.get("false")
4369        if else_cond:
4370            case.else_(else_cond, copy=False)
4371
4372        return self.sql(case)
def comprehension_sql(self, expression: sqlglot.expressions.Comprehension) -> str:
4374    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4375        this = self.sql(expression, "this")
4376        expr = self.sql(expression, "expression")
4377        position = self.sql(expression, "position")
4378        position = f", {position}" if position else ""
4379        iterator = self.sql(expression, "iterator")
4380        condition = self.sql(expression, "condition")
4381        condition = f" IF {condition}" if condition else ""
4382        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def columnprefix_sql(self, expression: sqlglot.expressions.ColumnPrefix) -> str:
4384    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4385        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
def opclass_sql(self, expression: sqlglot.expressions.Opclass) -> str:
4387    def opclass_sql(self, expression: exp.Opclass) -> str:
4388        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def predict_sql(self, expression: sqlglot.expressions.Predict) -> str:
4404    def predict_sql(self, expression: exp.Predict) -> str:
4405        return self._ml_sql(expression, "PREDICT")
def generateembedding_sql(self, expression: sqlglot.expressions.GenerateEmbedding) -> str:
4407    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4408        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4409        return self._ml_sql(expression, name)
def mltranslate_sql(self, expression: sqlglot.expressions.MLTranslate) -> str:
4411    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
4412        return self._ml_sql(expression, "TRANSLATE")
def mlforecast_sql(self, expression: sqlglot.expressions.MLForecast) -> str:
4414    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
4415        return self._ml_sql(expression, "FORECAST")
def featuresattime_sql(self, expression: sqlglot.expressions.FeaturesAtTime) -> str:
4417    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4418        this_sql = self.sql(expression, "this")
4419        if isinstance(expression.this, exp.Table):
4420            this_sql = f"TABLE {this_sql}"
4421
4422        return self.func(
4423            "FEATURES_AT_TIME",
4424            this_sql,
4425            expression.args.get("time"),
4426            expression.args.get("num_rows"),
4427            expression.args.get("ignore_feature_nulls"),
4428        )
def vectorsearch_sql(self, expression: sqlglot.expressions.VectorSearch) -> str:
4430    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
4431        this_sql = self.sql(expression, "this")
4432        if isinstance(expression.this, exp.Table):
4433            this_sql = f"TABLE {this_sql}"
4434
4435        query_table = self.sql(expression, "query_table")
4436        if isinstance(expression.args["query_table"], exp.Table):
4437            query_table = f"TABLE {query_table}"
4438
4439        return self.func(
4440            "VECTOR_SEARCH",
4441            this_sql,
4442            expression.args.get("column_to_search"),
4443            query_table,
4444            expression.args.get("query_column_to_search"),
4445            expression.args.get("top_k"),
4446            expression.args.get("distance_type"),
4447            expression.args.get("options"),
4448        )
def forin_sql(self, expression: sqlglot.expressions.ForIn) -> str:
4450    def forin_sql(self, expression: exp.ForIn) -> str:
4451        this = self.sql(expression, "this")
4452        expression_sql = self.sql(expression, "expression")
4453        return f"FOR {this} DO {expression_sql}"
def refresh_sql(self, expression: sqlglot.expressions.Refresh) -> str:
4455    def refresh_sql(self, expression: exp.Refresh) -> str:
4456        this = self.sql(expression, "this")
4457        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
4458        return f"REFRESH {kind}{this}"
def toarray_sql(self, expression: sqlglot.expressions.ToArray) -> str:
4460    def toarray_sql(self, expression: exp.ToArray) -> str:
4461        arg = expression.this
4462        if not arg.type:
4463            from sqlglot.optimizer.annotate_types import annotate_types
4464
4465            arg = annotate_types(arg, dialect=self.dialect)
4466
4467        if arg.is_type(exp.DataType.Type.ARRAY):
4468            return self.sql(arg)
4469
4470        cond_for_null = arg.is_(exp.null())
4471        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
def tsordstotime_sql(self, expression: sqlglot.expressions.TsOrDsToTime) -> str:
4473    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4474        this = expression.this
4475        time_format = self.format_time(expression)
4476
4477        if time_format:
4478            return self.sql(
4479                exp.cast(
4480                    exp.StrToTime(this=this, format=expression.args["format"]),
4481                    exp.DataType.Type.TIME,
4482                )
4483            )
4484
4485        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4486            return self.sql(this)
4487
4488        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
def tsordstotimestamp_sql(self, expression: sqlglot.expressions.TsOrDsToTimestamp) -> str:
4490    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4491        this = expression.this
4492        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4493            return self.sql(this)
4494
4495        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
def tsordstodatetime_sql(self, expression: sqlglot.expressions.TsOrDsToDatetime) -> str:
4497    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4498        this = expression.this
4499        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4500            return self.sql(this)
4501
4502        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
def tsordstodate_sql(self, expression: sqlglot.expressions.TsOrDsToDate) -> str:
4504    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4505        this = expression.this
4506        time_format = self.format_time(expression)
4507
4508        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4509            return self.sql(
4510                exp.cast(
4511                    exp.StrToTime(this=this, format=expression.args["format"]),
4512                    exp.DataType.Type.DATE,
4513                )
4514            )
4515
4516        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4517            return self.sql(this)
4518
4519        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
def unixdate_sql(self, expression: sqlglot.expressions.UnixDate) -> str:
4521    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4522        return self.sql(
4523            exp.func(
4524                "DATEDIFF",
4525                expression.this,
4526                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4527                "day",
4528            )
4529        )
def lastday_sql(self, expression: sqlglot.expressions.LastDay) -> str:
4531    def lastday_sql(self, expression: exp.LastDay) -> str:
4532        if self.LAST_DAY_SUPPORTS_DATE_PART:
4533            return self.function_fallback_sql(expression)
4534
4535        unit = expression.text("unit")
4536        if unit and unit != "MONTH":
4537            self.unsupported("Date parts are not supported in LAST_DAY.")
4538
4539        return self.func("LAST_DAY", expression.this)
def dateadd_sql(self, expression: sqlglot.expressions.DateAdd) -> str:
4541    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4542        from sqlglot.dialects.dialect import unit_to_str
4543
4544        return self.func(
4545            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4546        )
def arrayany_sql(self, expression: sqlglot.expressions.ArrayAny) -> str:
4548    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4549        if self.CAN_IMPLEMENT_ARRAY_ANY:
4550            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4551            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4552            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4553            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4554
4555        from sqlglot.dialects import Dialect
4556
4557        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4558        if self.dialect.__class__ != Dialect:
4559            self.unsupported("ARRAY_ANY is unsupported")
4560
4561        return self.function_fallback_sql(expression)
def struct_sql(self, expression: sqlglot.expressions.Struct) -> str:
4563    def struct_sql(self, expression: exp.Struct) -> str:
4564        expression.set(
4565            "expressions",
4566            [
4567                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4568                if isinstance(e, exp.PropertyEQ)
4569                else e
4570                for e in expression.expressions
4571            ],
4572        )
4573
4574        return self.function_fallback_sql(expression)
def partitionrange_sql(self, expression: sqlglot.expressions.PartitionRange) -> str:
4576    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4577        low = self.sql(expression, "this")
4578        high = self.sql(expression, "expression")
4579
4580        return f"{low} TO {high}"
def truncatetable_sql(self, expression: sqlglot.expressions.TruncateTable) -> str:
4582    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4583        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4584        tables = f" {self.expressions(expression)}"
4585
4586        exists = " IF EXISTS" if expression.args.get("exists") else ""
4587
4588        on_cluster = self.sql(expression, "cluster")
4589        on_cluster = f" {on_cluster}" if on_cluster else ""
4590
4591        identity = self.sql(expression, "identity")
4592        identity = f" {identity} IDENTITY" if identity else ""
4593
4594        option = self.sql(expression, "option")
4595        option = f" {option}" if option else ""
4596
4597        partition = self.sql(expression, "partition")
4598        partition = f" {partition}" if partition else ""
4599
4600        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
def convert_sql(self, expression: sqlglot.expressions.Convert) -> str:
4604    def convert_sql(self, expression: exp.Convert) -> str:
4605        to = expression.this
4606        value = expression.expression
4607        style = expression.args.get("style")
4608        safe = expression.args.get("safe")
4609        strict = expression.args.get("strict")
4610
4611        if not to or not value:
4612            return ""
4613
4614        # Retrieve length of datatype and override to default if not specified
4615        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4616            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4617
4618        transformed: t.Optional[exp.Expression] = None
4619        cast = exp.Cast if strict else exp.TryCast
4620
4621        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4622        if isinstance(style, exp.Literal) and style.is_int:
4623            from sqlglot.dialects.tsql import TSQL
4624
4625            style_value = style.name
4626            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4627            if not converted_style:
4628                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4629
4630            fmt = exp.Literal.string(converted_style)
4631
4632            if to.this == exp.DataType.Type.DATE:
4633                transformed = exp.StrToDate(this=value, format=fmt)
4634            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4635                transformed = exp.StrToTime(this=value, format=fmt)
4636            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4637                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4638            elif to.this == exp.DataType.Type.TEXT:
4639                transformed = exp.TimeToStr(this=value, format=fmt)
4640
4641        if not transformed:
4642            transformed = cast(this=value, to=to, safe=safe)
4643
4644        return self.sql(transformed)
def copyparameter_sql(self, expression: sqlglot.expressions.CopyParameter) -> str:
4712    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4713        option = self.sql(expression, "this")
4714
4715        if expression.expressions:
4716            upper = option.upper()
4717
4718            # Snowflake FILE_FORMAT options are separated by whitespace
4719            sep = " " if upper == "FILE_FORMAT" else ", "
4720
4721            # Databricks copy/format options do not set their list of values with EQ
4722            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4723            values = self.expressions(expression, flat=True, sep=sep)
4724            return f"{option}{op}({values})"
4725
4726        value = self.sql(expression, "expression")
4727
4728        if not value:
4729            return option
4730
4731        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4732
4733        return f"{option}{op}{value}"
def credentials_sql(self, expression: sqlglot.expressions.Credentials) -> str:
4735    def credentials_sql(self, expression: exp.Credentials) -> str:
4736        cred_expr = expression.args.get("credentials")
4737        if isinstance(cred_expr, exp.Literal):
4738            # Redshift case: CREDENTIALS <string>
4739            credentials = self.sql(expression, "credentials")
4740            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4741        else:
4742            # Snowflake case: CREDENTIALS = (...)
4743            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4744            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4745
4746        storage = self.sql(expression, "storage")
4747        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4748
4749        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4750        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4751
4752        iam_role = self.sql(expression, "iam_role")
4753        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4754
4755        region = self.sql(expression, "region")
4756        region = f" REGION {region}" if region else ""
4757
4758        return f"{credentials}{storage}{encryption}{iam_role}{region}"
def copy_sql(self, expression: sqlglot.expressions.Copy) -> str:
4760    def copy_sql(self, expression: exp.Copy) -> str:
4761        this = self.sql(expression, "this")
4762        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4763
4764        credentials = self.sql(expression, "credentials")
4765        credentials = self.seg(credentials) if credentials else ""
4766        files = self.expressions(expression, key="files", flat=True)
4767        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
4768
4769        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4770        params = self.expressions(
4771            expression,
4772            key="params",
4773            sep=sep,
4774            new_line=True,
4775            skip_last=True,
4776            skip_first=True,
4777            indent=self.COPY_PARAMS_ARE_WRAPPED,
4778        )
4779
4780        if params:
4781            if self.COPY_PARAMS_ARE_WRAPPED:
4782                params = f" WITH ({params})"
4783            elif not self.pretty and (files or credentials):
4784                params = f" {params}"
4785
4786        return f"COPY{this}{kind} {files}{credentials}{params}"
def semicolon_sql(self, expression: sqlglot.expressions.Semicolon) -> str:
4788    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4789        return ""
def datadeletionproperty_sql(self, expression: sqlglot.expressions.DataDeletionProperty) -> str:
4791    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4792        on_sql = "ON" if expression.args.get("on") else "OFF"
4793        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4794        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4795        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4796        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4797
4798        if filter_col or retention_period:
4799            on_sql = self.func("ON", filter_col, retention_period)
4800
4801        return f"DATA_DELETION={on_sql}"
def maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4803    def maskingpolicycolumnconstraint_sql(
4804        self, expression: exp.MaskingPolicyColumnConstraint
4805    ) -> str:
4806        this = self.sql(expression, "this")
4807        expressions = self.expressions(expression, flat=True)
4808        expressions = f" USING ({expressions})" if expressions else ""
4809        return f"MASKING POLICY {this}{expressions}"
def gapfill_sql(self, expression: sqlglot.expressions.GapFill) -> str:
4811    def gapfill_sql(self, expression: exp.GapFill) -> str:
4812        this = self.sql(expression, "this")
4813        this = f"TABLE {this}"
4814        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:
4816    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4817        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
def scoperesolution_sql(self, expression: sqlglot.expressions.ScopeResolution) -> str:
4819    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4820        this = self.sql(expression, "this")
4821        expr = expression.expression
4822
4823        if isinstance(expr, exp.Func):
4824            # T-SQL's CLR functions are case sensitive
4825            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4826        else:
4827            expr = self.sql(expression, "expression")
4828
4829        return self.scope_resolution(expr, this)
def parsejson_sql(self, expression: sqlglot.expressions.ParseJSON) -> str:
4831    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4832        if self.PARSE_JSON_NAME is None:
4833            return self.sql(expression.this)
4834
4835        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
def rand_sql(self, expression: sqlglot.expressions.Rand) -> str:
4837    def rand_sql(self, expression: exp.Rand) -> str:
4838        lower = self.sql(expression, "lower")
4839        upper = self.sql(expression, "upper")
4840
4841        if lower and upper:
4842            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4843        return self.func("RAND", expression.this)
def changes_sql(self, expression: sqlglot.expressions.Changes) -> str:
4845    def changes_sql(self, expression: exp.Changes) -> str:
4846        information = self.sql(expression, "information")
4847        information = f"INFORMATION => {information}"
4848        at_before = self.sql(expression, "at_before")
4849        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4850        end = self.sql(expression, "end")
4851        end = f"{self.seg('')}{end}" if end else ""
4852
4853        return f"CHANGES ({information}){at_before}{end}"
def pad_sql(self, expression: sqlglot.expressions.Pad) -> str:
4855    def pad_sql(self, expression: exp.Pad) -> str:
4856        prefix = "L" if expression.args.get("is_left") else "R"
4857
4858        fill_pattern = self.sql(expression, "fill_pattern") or None
4859        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4860            fill_pattern = "' '"
4861
4862        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def summarize_sql(self, expression: sqlglot.expressions.Summarize) -> str:
4864    def summarize_sql(self, expression: exp.Summarize) -> str:
4865        table = " TABLE" if expression.args.get("table") else ""
4866        return f"SUMMARIZE{table} {self.sql(expression.this)}"
def explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4868    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4869        generate_series = exp.GenerateSeries(**expression.args)
4870
4871        parent = expression.parent
4872        if isinstance(parent, (exp.Alias, exp.TableAlias)):
4873            parent = parent.parent
4874
4875        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
4876            return self.sql(exp.Unnest(expressions=[generate_series]))
4877
4878        if isinstance(parent, exp.Select):
4879            self.unsupported("GenerateSeries projection unnesting is not supported.")
4880
4881        return self.sql(generate_series)
def arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4883    def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str:
4884        exprs = expression.expressions
4885        if not self.ARRAY_CONCAT_IS_VAR_LEN:
4886            if len(exprs) == 0:
4887                rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[])
4888            else:
4889                rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs)
4890        else:
4891            rhs = self.expressions(expression)  # type: ignore
4892
4893        return self.func(name, expression.this, rhs or None)
def converttimezone_sql(self, expression: sqlglot.expressions.ConvertTimezone) -> str:
4895    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
4896        if self.SUPPORTS_CONVERT_TIMEZONE:
4897            return self.function_fallback_sql(expression)
4898
4899        source_tz = expression.args.get("source_tz")
4900        target_tz = expression.args.get("target_tz")
4901        timestamp = expression.args.get("timestamp")
4902
4903        if source_tz and timestamp:
4904            timestamp = exp.AtTimeZone(
4905                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
4906            )
4907
4908        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
4909
4910        return self.sql(expr)
def json_sql(self, expression: sqlglot.expressions.JSON) -> str:
4912    def json_sql(self, expression: exp.JSON) -> str:
4913        this = self.sql(expression, "this")
4914        this = f" {this}" if this else ""
4915
4916        _with = expression.args.get("with_")
4917
4918        if _with is None:
4919            with_sql = ""
4920        elif not _with:
4921            with_sql = " WITHOUT"
4922        else:
4923            with_sql = " WITH"
4924
4925        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
4926
4927        return f"JSON{this}{with_sql}{unique_sql}"
def jsonvalue_sql(self, expression: sqlglot.expressions.JSONValue) -> str:
4929    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
4930        def _generate_on_options(arg: t.Any) -> str:
4931            return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}"
4932
4933        path = self.sql(expression, "path")
4934        returning = self.sql(expression, "returning")
4935        returning = f" RETURNING {returning}" if returning else ""
4936
4937        on_condition = self.sql(expression, "on_condition")
4938        on_condition = f" {on_condition}" if on_condition else ""
4939
4940        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
def conditionalinsert_sql(self, expression: sqlglot.expressions.ConditionalInsert) -> str:
4942    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
4943        else_ = "ELSE " if expression.args.get("else_") else ""
4944        condition = self.sql(expression, "expression")
4945        condition = f"WHEN {condition} THEN " if condition else else_
4946        insert = self.sql(expression, "this")[len("INSERT") :].strip()
4947        return f"{condition}{insert}"
def multitableinserts_sql(self, expression: sqlglot.expressions.MultitableInserts) -> str:
4949    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
4950        kind = self.sql(expression, "kind")
4951        expressions = self.seg(self.expressions(expression, sep=" "))
4952        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
4953        return res
def oncondition_sql(self, expression: sqlglot.expressions.OnCondition) -> str:
4955    def oncondition_sql(self, expression: exp.OnCondition) -> str:
4956        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
4957        empty = expression.args.get("empty")
4958        empty = (
4959            f"DEFAULT {empty} ON EMPTY"
4960            if isinstance(empty, exp.Expression)
4961            else self.sql(expression, "empty")
4962        )
4963
4964        error = expression.args.get("error")
4965        error = (
4966            f"DEFAULT {error} ON ERROR"
4967            if isinstance(error, exp.Expression)
4968            else self.sql(expression, "error")
4969        )
4970
4971        if error and empty:
4972            error = (
4973                f"{empty} {error}"
4974                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
4975                else f"{error} {empty}"
4976            )
4977            empty = ""
4978
4979        null = self.sql(expression, "null")
4980
4981        return f"{empty}{error}{null}"
def jsonextractquote_sql(self, expression: sqlglot.expressions.JSONExtractQuote) -> str:
4983    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
4984        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
4985        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
def jsonexists_sql(self, expression: sqlglot.expressions.JSONExists) -> str:
4987    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
4988        this = self.sql(expression, "this")
4989        path = self.sql(expression, "path")
4990
4991        passing = self.expressions(expression, "passing")
4992        passing = f" PASSING {passing}" if passing else ""
4993
4994        on_condition = self.sql(expression, "on_condition")
4995        on_condition = f" {on_condition}" if on_condition else ""
4996
4997        path = f"{path}{passing}{on_condition}"
4998
4999        return self.func("JSON_EXISTS", this, path)
def arrayagg_sql(self, expression: sqlglot.expressions.ArrayAgg) -> str:
5041    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5042        array_agg = self.function_fallback_sql(expression)
5043        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
def slice_sql(self, expression: sqlglot.expressions.Slice) -> str:
5045    def slice_sql(self, expression: exp.Slice) -> str:
5046        step = self.sql(expression, "step")
5047        end = self.sql(expression.expression)
5048        begin = self.sql(expression.this)
5049
5050        sql = f"{end}:{step}" if step else end
5051        return f"{begin}:{sql}" if sql else f"{begin}:"
def apply_sql(self, expression: sqlglot.expressions.Apply) -> str:
5053    def apply_sql(self, expression: exp.Apply) -> str:
5054        this = self.sql(expression, "this")
5055        expr = self.sql(expression, "expression")
5056
5057        return f"{this} APPLY({expr})"
def grant_sql(self, expression: sqlglot.expressions.Grant) -> str:
5086    def grant_sql(self, expression: exp.Grant) -> str:
5087        return self._grant_or_revoke_sql(
5088            expression,
5089            keyword="GRANT",
5090            preposition="TO",
5091            grant_option_suffix=" WITH GRANT OPTION",
5092        )
def revoke_sql(self, expression: sqlglot.expressions.Revoke) -> str:
5094    def revoke_sql(self, expression: exp.Revoke) -> str:
5095        return self._grant_or_revoke_sql(
5096            expression,
5097            keyword="REVOKE",
5098            preposition="FROM",
5099            grant_option_prefix="GRANT OPTION FOR ",
5100        )
def grantprivilege_sql(self, expression: sqlglot.expressions.GrantPrivilege):
5102    def grantprivilege_sql(self, expression: exp.GrantPrivilege):
5103        this = self.sql(expression, "this")
5104        columns = self.expressions(expression, flat=True)
5105        columns = f"({columns})" if columns else ""
5106
5107        return f"{this}{columns}"
def grantprincipal_sql(self, expression: sqlglot.expressions.GrantPrincipal):
5109    def grantprincipal_sql(self, expression: exp.GrantPrincipal):
5110        this = self.sql(expression, "this")
5111
5112        kind = self.sql(expression, "kind")
5113        kind = f"{kind} " if kind else ""
5114
5115        return f"{kind}{this}"
def columns_sql(self, expression: sqlglot.expressions.Columns):
5117    def columns_sql(self, expression: exp.Columns):
5118        func = self.function_fallback_sql(expression)
5119        if expression.args.get("unpack"):
5120            func = f"*{func}"
5121
5122        return func
def overlay_sql(self, expression: sqlglot.expressions.Overlay):
5124    def overlay_sql(self, expression: exp.Overlay):
5125        this = self.sql(expression, "this")
5126        expr = self.sql(expression, "expression")
5127        from_sql = self.sql(expression, "from_")
5128        for_sql = self.sql(expression, "for_")
5129        for_sql = f" FOR {for_sql}" if for_sql else ""
5130
5131        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
5133    @unsupported_args("format")
5134    def todouble_sql(self, expression: exp.ToDouble) -> str:
5135        return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
def string_sql(self, expression: sqlglot.expressions.String) -> str:
5137    def string_sql(self, expression: exp.String) -> str:
5138        this = expression.this
5139        zone = expression.args.get("zone")
5140
5141        if zone:
5142            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5143            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5144            # set for source_tz to transpile the time conversion before the STRING cast
5145            this = exp.ConvertTimezone(
5146                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5147            )
5148
5149        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def median_sql(self, expression: sqlglot.expressions.Median):
5151    def median_sql(self, expression: exp.Median):
5152        if not self.SUPPORTS_MEDIAN:
5153            return self.sql(
5154                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5155            )
5156
5157        return self.function_fallback_sql(expression)
def overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
5159    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5160        filler = self.sql(expression, "this")
5161        filler = f" {filler}" if filler else ""
5162        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5163        return f"TRUNCATE{filler} {with_count}"
def unixseconds_sql(self, expression: sqlglot.expressions.UnixSeconds) -> str:
5165    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5166        if self.SUPPORTS_UNIX_SECONDS:
5167            return self.function_fallback_sql(expression)
5168
5169        start_ts = exp.cast(
5170            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
5171        )
5172
5173        return self.sql(
5174            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5175        )
def arraysize_sql(self, expression: sqlglot.expressions.ArraySize) -> str:
5177    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5178        dim = expression.expression
5179
5180        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5181        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5182            if not (dim.is_int and dim.name == "1"):
5183                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5184            dim = None
5185
5186        # If dimension is required but not specified, default initialize it
5187        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5188            dim = exp.Literal.number(1)
5189
5190        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
def attach_sql(self, expression: sqlglot.expressions.Attach) -> str:
5192    def attach_sql(self, expression: exp.Attach) -> str:
5193        this = self.sql(expression, "this")
5194        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5195        expressions = self.expressions(expression)
5196        expressions = f" ({expressions})" if expressions else ""
5197
5198        return f"ATTACH{exists_sql} {this}{expressions}"
def detach_sql(self, expression: sqlglot.expressions.Detach) -> str:
5200    def detach_sql(self, expression: exp.Detach) -> str:
5201        this = self.sql(expression, "this")
5202        # the DATABASE keyword is required if IF EXISTS is set
5203        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
5204        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5205        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
5206
5207        return f"DETACH{exists_sql} {this}"
def attachoption_sql(self, expression: sqlglot.expressions.AttachOption) -> str:
5209    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5210        this = self.sql(expression, "this")
5211        value = self.sql(expression, "expression")
5212        value = f" {value}" if value else ""
5213        return f"{this}{value}"
def watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
5215    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5216        return (
5217            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5218        )
def encodeproperty_sql(self, expression: sqlglot.expressions.EncodeProperty) -> str:
5220    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5221        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5222        encode = f"{encode} {self.sql(expression, 'this')}"
5223
5224        properties = expression.args.get("properties")
5225        if properties:
5226            encode = f"{encode} {self.properties(properties)}"
5227
5228        return encode
def includeproperty_sql(self, expression: sqlglot.expressions.IncludeProperty) -> str:
5230    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5231        this = self.sql(expression, "this")
5232        include = f"INCLUDE {this}"
5233
5234        column_def = self.sql(expression, "column_def")
5235        if column_def:
5236            include = f"{include} {column_def}"
5237
5238        alias = self.sql(expression, "alias")
5239        if alias:
5240            include = f"{include} AS {alias}"
5241
5242        return include
def xmlelement_sql(self, expression: sqlglot.expressions.XMLElement) -> str:
5244    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5245        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
5246        name = f"{prefix} {self.sql(expression, 'this')}"
5247        return self.func("XMLELEMENT", name, *expression.expressions)
def xmlkeyvalueoption_sql(self, expression: sqlglot.expressions.XMLKeyValueOption) -> str:
5249    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5250        this = self.sql(expression, "this")
5251        expr = self.sql(expression, "expression")
5252        expr = f"({expr})" if expr else ""
5253        return f"{this}{expr}"
def partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
5255    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5256        partitions = self.expressions(expression, "partition_expressions")
5257        create = self.expressions(expression, "create_expressions")
5258        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
5260    def partitionbyrangepropertydynamic_sql(
5261        self, expression: exp.PartitionByRangePropertyDynamic
5262    ) -> str:
5263        start = self.sql(expression, "start")
5264        end = self.sql(expression, "end")
5265
5266        every = expression.args["every"]
5267        if isinstance(every, exp.Interval) and every.this.is_string:
5268            every.this.replace(exp.Literal.number(every.name))
5269
5270        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
def unpivotcolumns_sql(self, expression: sqlglot.expressions.UnpivotColumns) -> str:
5272    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5273        name = self.sql(expression, "this")
5274        values = self.expressions(expression, flat=True)
5275
5276        return f"NAME {name} VALUE {values}"
def analyzesample_sql(self, expression: sqlglot.expressions.AnalyzeSample) -> str:
5278    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5279        kind = self.sql(expression, "kind")
5280        sample = self.sql(expression, "sample")
5281        return f"SAMPLE {sample} {kind}"
def analyzestatistics_sql(self, expression: sqlglot.expressions.AnalyzeStatistics) -> str:
5283    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5284        kind = self.sql(expression, "kind")
5285        option = self.sql(expression, "option")
5286        option = f" {option}" if option else ""
5287        this = self.sql(expression, "this")
5288        this = f" {this}" if this else ""
5289        columns = self.expressions(expression)
5290        columns = f" {columns}" if columns else ""
5291        return f"{kind}{option} STATISTICS{this}{columns}"
def analyzehistogram_sql(self, expression: sqlglot.expressions.AnalyzeHistogram) -> str:
5293    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5294        this = self.sql(expression, "this")
5295        columns = self.expressions(expression)
5296        inner_expression = self.sql(expression, "expression")
5297        inner_expression = f" {inner_expression}" if inner_expression else ""
5298        update_options = self.sql(expression, "update_options")
5299        update_options = f" {update_options} UPDATE" if update_options else ""
5300        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def analyzedelete_sql(self, expression: sqlglot.expressions.AnalyzeDelete) -> str:
5302    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5303        kind = self.sql(expression, "kind")
5304        kind = f" {kind}" if kind else ""
5305        return f"DELETE{kind} STATISTICS"
def analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
5307    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5308        inner_expression = self.sql(expression, "expression")
5309        return f"LIST CHAINED ROWS{inner_expression}"
def analyzevalidate_sql(self, expression: sqlglot.expressions.AnalyzeValidate) -> str:
5311    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5312        kind = self.sql(expression, "kind")
5313        this = self.sql(expression, "this")
5314        this = f" {this}" if this else ""
5315        inner_expression = self.sql(expression, "expression")
5316        return f"VALIDATE {kind}{this}{inner_expression}"
def analyze_sql(self, expression: sqlglot.expressions.Analyze) -> str:
5318    def analyze_sql(self, expression: exp.Analyze) -> str:
5319        options = self.expressions(expression, key="options", sep=" ")
5320        options = f" {options}" if options else ""
5321        kind = self.sql(expression, "kind")
5322        kind = f" {kind}" if kind else ""
5323        this = self.sql(expression, "this")
5324        this = f" {this}" if this else ""
5325        mode = self.sql(expression, "mode")
5326        mode = f" {mode}" if mode else ""
5327        properties = self.sql(expression, "properties")
5328        properties = f" {properties}" if properties else ""
5329        partition = self.sql(expression, "partition")
5330        partition = f" {partition}" if partition else ""
5331        inner_expression = self.sql(expression, "expression")
5332        inner_expression = f" {inner_expression}" if inner_expression else ""
5333        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
def xmltable_sql(self, expression: sqlglot.expressions.XMLTable) -> str:
5335    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5336        this = self.sql(expression, "this")
5337        namespaces = self.expressions(expression, key="namespaces")
5338        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5339        passing = self.expressions(expression, key="passing")
5340        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5341        columns = self.expressions(expression, key="columns")
5342        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5343        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5344        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
def xmlnamespace_sql(self, expression: sqlglot.expressions.XMLNamespace) -> str:
5346    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5347        this = self.sql(expression, "this")
5348        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
def export_sql(self, expression: sqlglot.expressions.Export) -> str:
5350    def export_sql(self, expression: exp.Export) -> str:
5351        this = self.sql(expression, "this")
5352        connection = self.sql(expression, "connection")
5353        connection = f"WITH CONNECTION {connection} " if connection else ""
5354        options = self.sql(expression, "options")
5355        return f"EXPORT DATA {connection}{options} AS {this}"
def declare_sql(self, expression: sqlglot.expressions.Declare) -> str:
5357    def declare_sql(self, expression: exp.Declare) -> str:
5358        return f"DECLARE {self.expressions(expression, flat=True)}"
def declareitem_sql(self, expression: sqlglot.expressions.DeclareItem) -> str:
5360    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5361        variable = self.sql(expression, "this")
5362        default = self.sql(expression, "default")
5363        default = f" = {default}" if default else ""
5364
5365        kind = self.sql(expression, "kind")
5366        if isinstance(expression.args.get("kind"), exp.Schema):
5367            kind = f"TABLE {kind}"
5368
5369        return f"{variable} AS {kind}{default}"
def recursivewithsearch_sql(self, expression: sqlglot.expressions.RecursiveWithSearch) -> str:
5371    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5372        kind = self.sql(expression, "kind")
5373        this = self.sql(expression, "this")
5374        set = self.sql(expression, "expression")
5375        using = self.sql(expression, "using")
5376        using = f" USING {using}" if using else ""
5377
5378        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5379
5380        return f"{kind_sql} {this} SET {set}{using}"
def parameterizedagg_sql(self, expression: sqlglot.expressions.ParameterizedAgg) -> str:
5382    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5383        params = self.expressions(expression, key="params", flat=True)
5384        return self.func(expression.name, *expression.expressions) + f"({params})"
def anonymousaggfunc_sql(self, expression: sqlglot.expressions.AnonymousAggFunc) -> str:
5386    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5387        return self.func(expression.name, *expression.expressions)
def combinedaggfunc_sql(self, expression: sqlglot.expressions.CombinedAggFunc) -> str:
5389    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5390        return self.anonymousaggfunc_sql(expression)
def combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5392    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5393        return self.parameterizedagg_sql(expression)
def show_sql(self, expression: sqlglot.expressions.Show) -> str:
5395    def show_sql(self, expression: exp.Show) -> str:
5396        self.unsupported("Unsupported SHOW statement")
5397        return ""
def install_sql(self, expression: sqlglot.expressions.Install) -> str:
5399    def install_sql(self, expression: exp.Install) -> str:
5400        self.unsupported("Unsupported INSTALL statement")
5401        return ""
def get_put_sql( self, expression: sqlglot.expressions.Put | sqlglot.expressions.Get) -> str:
5403    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5404        # Snowflake GET/PUT statements:
5405        #   PUT <file> <internalStage> <properties>
5406        #   GET <internalStage> <file> <properties>
5407        props = expression.args.get("properties")
5408        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5409        this = self.sql(expression, "this")
5410        target = self.sql(expression, "target")
5411
5412        if isinstance(expression, exp.Put):
5413            return f"PUT {this} {target}{props_sql}"
5414        else:
5415            return f"GET {target} {this}{props_sql}"
def translatecharacters_sql(self, expression: sqlglot.expressions.TranslateCharacters):
5417    def translatecharacters_sql(self, expression: exp.TranslateCharacters):
5418        this = self.sql(expression, "this")
5419        expr = self.sql(expression, "expression")
5420        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5421        return f"TRANSLATE({this} USING {expr}{with_error})"
def decodecase_sql(self, expression: sqlglot.expressions.DecodeCase) -> str:
5423    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5424        if self.SUPPORTS_DECODE_CASE:
5425            return self.func("DECODE", *expression.expressions)
5426
5427        expression, *expressions = expression.expressions
5428
5429        ifs = []
5430        for search, result in zip(expressions[::2], expressions[1::2]):
5431            if isinstance(search, exp.Literal):
5432                ifs.append(exp.If(this=expression.eq(search), true=result))
5433            elif isinstance(search, exp.Null):
5434                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5435            else:
5436                if isinstance(search, exp.Binary):
5437                    search = exp.paren(search)
5438
5439                cond = exp.or_(
5440                    expression.eq(search),
5441                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5442                    copy=False,
5443                )
5444                ifs.append(exp.If(this=cond, true=result))
5445
5446        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5447        return self.sql(case)
def semanticview_sql(self, expression: sqlglot.expressions.SemanticView) -> str:
5449    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5450        this = self.sql(expression, "this")
5451        this = self.seg(this, sep="")
5452        dimensions = self.expressions(
5453            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5454        )
5455        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5456        metrics = self.expressions(
5457            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5458        )
5459        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5460        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
5461        facts = self.seg(f"FACTS {facts}") if facts else ""
5462        where = self.sql(expression, "where")
5463        where = self.seg(f"WHERE {where}") if where else ""
5464        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
5465        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
def getextract_sql(self, expression: sqlglot.expressions.GetExtract) -> str:
5467    def getextract_sql(self, expression: exp.GetExtract) -> str:
5468        this = expression.this
5469        expr = expression.expression
5470
5471        if not this.type or not expression.type:
5472            from sqlglot.optimizer.annotate_types import annotate_types
5473
5474            this = annotate_types(this, dialect=self.dialect)
5475
5476        if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)):
5477            return self.sql(exp.Bracket(this=this, expressions=[expr]))
5478
5479        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def datefromunixdate_sql(self, expression: sqlglot.expressions.DateFromUnixDate) -> str:
5481    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
5482        return self.sql(
5483            exp.DateAdd(
5484                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
5485                expression=expression.this,
5486                unit=exp.var("DAY"),
5487            )
5488        )
def space_sql( self: Generator, expression: sqlglot.expressions.Space) -> str:
5490    def space_sql(self: Generator, expression: exp.Space) -> str:
5491        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
def buildproperty_sql(self, expression: sqlglot.expressions.BuildProperty) -> str:
5493    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
5494        return f"BUILD {self.sql(expression, 'this')}"
def refreshtriggerproperty_sql(self, expression: sqlglot.expressions.RefreshTriggerProperty) -> str:
5496    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
5497        method = self.sql(expression, "method")
5498        kind = expression.args.get("kind")
5499        if not kind:
5500            return f"REFRESH {method}"
5501
5502        every = self.sql(expression, "every")
5503        unit = self.sql(expression, "unit")
5504        every = f" EVERY {every} {unit}" if every else ""
5505        starts = self.sql(expression, "starts")
5506        starts = f" STARTS {starts}" if starts else ""
5507
5508        return f"REFRESH {method} ON {kind}{every}{starts}"
def modelattribute_sql(self, expression: sqlglot.expressions.ModelAttribute) -> str:
5510    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
5511        self.unsupported("The model!attribute syntax is not supported")
5512        return ""
def directorystage_sql(self, expression: sqlglot.expressions.DirectoryStage) -> str:
5514    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
5515        return self.func("DIRECTORY", expression.this)
def uuid_sql(self, expression: sqlglot.expressions.Uuid) -> str:
5517    def uuid_sql(self, expression: exp.Uuid) -> str:
5518        is_string = expression.args.get("is_string", False)
5519        uuid_func_sql = self.func("UUID")
5520
5521        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
5522            return self.sql(
5523                exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect)
5524            )
5525
5526        return uuid_func_sql
def initcap_sql(self, expression: sqlglot.expressions.Initcap) -> str:
5528    def initcap_sql(self, expression: exp.Initcap) -> str:
5529        delimiters = expression.expression
5530
5531        if delimiters:
5532            # do not generate delimiters arg if we are round-tripping from default delimiters
5533            if (
5534                delimiters.is_string
5535                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
5536            ):
5537                delimiters = None
5538            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
5539                self.unsupported("INITCAP does not support custom delimiters")
5540                delimiters = None
5541
5542        return self.func("INITCAP", expression.this, delimiters)
def localtime_sql(self, expression: sqlglot.expressions.Localtime) -> str:
5544    def localtime_sql(self, expression: exp.Localtime) -> str:
5545        this = expression.this
5546        return self.func("LOCALTIME", this) if this else "LOCALTIME"
def localtimestamp_sql(self, expression: sqlglot.expressions.Localtime) -> str:
5548    def localtimestamp_sql(self, expression: exp.Localtime) -> str:
5549        this = expression.this
5550        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
def weekstart_sql(self, expression: sqlglot.expressions.WeekStart) -> str:
5552    def weekstart_sql(self, expression: exp.WeekStart) -> str:
5553        this = expression.this.name.upper()
5554        if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
5555            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
5556            return "WEEK"
5557
5558        return self.func("WEEK", expression.this)
def chr_sql(self, expression: sqlglot.expressions.Chr, name: str = 'CHR') -> str:
5560    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
5561        this = self.expressions(expression)
5562        charset = self.sql(expression, "charset")
5563        using = f" USING {charset}" if charset else ""
5564        return self.func(name, this + using)