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 or 'always': Always quote.
  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.AllowedValuesProperty: lambda self,
 116        e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}",
 117        exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
 118        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
 119        exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
 120        exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
 121        exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
 122        exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
 123        exp.CaseSpecificColumnConstraint: lambda _,
 124        e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC",
 125        exp.Ceil: lambda self, e: self.ceil_floor(e),
 126        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
 127        exp.CharacterSetProperty: lambda self,
 128        e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}",
 129        exp.ClusteredColumnConstraint: lambda self,
 130        e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})",
 131        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
 132        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
 133        exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}",
 134        exp.ConvertToCharset: lambda self, e: self.func(
 135            "CONVERT", e.this, e.args["dest"], e.args.get("source")
 136        ),
 137        exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
 138        exp.CredentialsProperty: lambda self,
 139        e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})",
 140        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 141        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 142        exp.DynamicProperty: lambda *_: "DYNAMIC",
 143        exp.EmptyProperty: lambda *_: "EMPTY",
 144        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 145        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 146        exp.EphemeralColumnConstraint: lambda self,
 147        e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}",
 148        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 149        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 150        exp.Except: lambda self, e: self.set_operations(e),
 151        exp.ExternalProperty: lambda *_: "EXTERNAL",
 152        exp.Floor: lambda self, e: self.ceil_floor(e),
 153        exp.Get: lambda self, e: self.get_put_sql(e),
 154        exp.GlobalProperty: lambda *_: "GLOBAL",
 155        exp.HeapProperty: lambda *_: "HEAP",
 156        exp.IcebergProperty: lambda *_: "ICEBERG",
 157        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 158        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 159        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 160        exp.Intersect: lambda self, e: self.set_operations(e),
 161        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 162        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)),
 163        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 164        exp.LocationProperty: lambda self, e: self.naked_property(e),
 165        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 166        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 167        exp.NonClusteredColumnConstraint: lambda self,
 168        e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})",
 169        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 170        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 171        exp.OnCommitProperty: lambda _,
 172        e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS",
 173        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 174        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 175        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 176        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 177        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 178        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 179        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 180        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 181        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 182        exp.ProjectionPolicyColumnConstraint: lambda self,
 183        e: f"PROJECTION POLICY {self.sql(e, 'this')}",
 184        exp.Put: lambda self, e: self.get_put_sql(e),
 185        exp.RemoteWithConnectionModelProperty: lambda self,
 186        e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
 187        exp.ReturnsProperty: lambda self, e: (
 188            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 189        ),
 190        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 191        exp.SecureProperty: lambda *_: "SECURE",
 192        exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}",
 193        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 194        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 195        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 196        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 197        exp.SqlReadWriteProperty: lambda _, e: e.name,
 198        exp.SqlSecurityProperty: lambda _,
 199        e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}",
 200        exp.StabilityProperty: lambda _, e: e.name,
 201        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 202        exp.StreamingTableProperty: lambda *_: "STREAMING",
 203        exp.StrictProperty: lambda *_: "STRICT",
 204        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 205        exp.TableColumn: lambda self, e: self.sql(e.this),
 206        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 207        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 208        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 209        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 210        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 211        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 212        exp.TransientProperty: lambda *_: "TRANSIENT",
 213        exp.Union: lambda self, e: self.set_operations(e),
 214        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 215        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 216        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 217        exp.Uuid: lambda *_: "UUID()",
 218        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 219        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 220        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 221        exp.VolatileProperty: lambda *_: "VOLATILE",
 222        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 223        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 224        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 225        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 226        exp.ForceProperty: lambda *_: "FORCE",
 227    }
 228
 229    # Whether null ordering is supported in order by
 230    # True: Full Support, None: No support, False: No support for certain cases
 231    # such as window specifications, aggregate functions etc
 232    NULL_ORDERING_SUPPORTED: t.Optional[bool] = True
 233
 234    # Whether ignore nulls is inside the agg or outside.
 235    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 236    IGNORE_NULLS_IN_FUNC = False
 237
 238    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 239    LOCKING_READS_SUPPORTED = False
 240
 241    # Whether the EXCEPT and INTERSECT operations can return duplicates
 242    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 243
 244    # Wrap derived values in parens, usually standard but spark doesn't support it
 245    WRAP_DERIVED_VALUES = True
 246
 247    # Whether create function uses an AS before the RETURN
 248    CREATE_FUNCTION_RETURN_AS = True
 249
 250    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 251    MATCHED_BY_SOURCE = True
 252
 253    # Whether the INTERVAL expression works only with values like '1 day'
 254    SINGLE_STRING_INTERVAL = False
 255
 256    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 257    INTERVAL_ALLOWS_PLURAL_FORM = True
 258
 259    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 260    LIMIT_FETCH = "ALL"
 261
 262    # Whether limit and fetch allows expresions or just limits
 263    LIMIT_ONLY_LITERALS = False
 264
 265    # Whether a table is allowed to be renamed with a db
 266    RENAME_TABLE_WITH_DB = True
 267
 268    # The separator for grouping sets and rollups
 269    GROUPINGS_SEP = ","
 270
 271    # The string used for creating an index on a table
 272    INDEX_ON = "ON"
 273
 274    # Whether join hints should be generated
 275    JOIN_HINTS = True
 276
 277    # Whether table hints should be generated
 278    TABLE_HINTS = True
 279
 280    # Whether query hints should be generated
 281    QUERY_HINTS = True
 282
 283    # What kind of separator to use for query hints
 284    QUERY_HINT_SEP = ", "
 285
 286    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 287    IS_BOOL_ALLOWED = True
 288
 289    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 290    DUPLICATE_KEY_UPDATE_WITH_SET = True
 291
 292    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 293    LIMIT_IS_TOP = False
 294
 295    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 296    RETURNING_END = True
 297
 298    # Whether to generate an unquoted value for EXTRACT's date part argument
 299    EXTRACT_ALLOWS_QUOTES = True
 300
 301    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 302    TZ_TO_WITH_TIME_ZONE = False
 303
 304    # Whether the NVL2 function is supported
 305    NVL2_SUPPORTED = True
 306
 307    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 308    SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE")
 309
 310    # Whether VALUES statements can be used as derived tables.
 311    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 312    # SELECT * VALUES into SELECT UNION
 313    VALUES_AS_TABLE = True
 314
 315    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 316    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 317
 318    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 319    UNNEST_WITH_ORDINALITY = True
 320
 321    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 322    AGGREGATE_FILTER_SUPPORTED = True
 323
 324    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 325    SEMI_ANTI_JOIN_WITH_SIDE = True
 326
 327    # Whether to include the type of a computed column in the CREATE DDL
 328    COMPUTED_COLUMN_WITH_TYPE = True
 329
 330    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 331    SUPPORTS_TABLE_COPY = True
 332
 333    # Whether parentheses are required around the table sample's expression
 334    TABLESAMPLE_REQUIRES_PARENS = True
 335
 336    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 337    TABLESAMPLE_SIZE_IS_ROWS = True
 338
 339    # The keyword(s) to use when generating a sample clause
 340    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 341
 342    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 343    TABLESAMPLE_WITH_METHOD = True
 344
 345    # The keyword to use when specifying the seed of a sample clause
 346    TABLESAMPLE_SEED_KEYWORD = "SEED"
 347
 348    # Whether COLLATE is a function instead of a binary operator
 349    COLLATE_IS_FUNC = False
 350
 351    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 352    DATA_TYPE_SPECIFIERS_ALLOWED = False
 353
 354    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 355    ENSURE_BOOLS = False
 356
 357    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 358    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 359
 360    # Whether CONCAT requires >1 arguments
 361    SUPPORTS_SINGLE_ARG_CONCAT = True
 362
 363    # Whether LAST_DAY function supports a date part argument
 364    LAST_DAY_SUPPORTS_DATE_PART = True
 365
 366    # Whether named columns are allowed in table aliases
 367    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 368
 369    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 370    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 371
 372    # What delimiter to use for separating JSON key/value pairs
 373    JSON_KEY_VALUE_PAIR_SEP = ":"
 374
 375    # INSERT OVERWRITE TABLE x override
 376    INSERT_OVERWRITE = " OVERWRITE TABLE"
 377
 378    # Whether the SELECT .. INTO syntax is used instead of CTAS
 379    SUPPORTS_SELECT_INTO = False
 380
 381    # Whether UNLOGGED tables can be created
 382    SUPPORTS_UNLOGGED_TABLES = False
 383
 384    # Whether the CREATE TABLE LIKE statement is supported
 385    SUPPORTS_CREATE_TABLE_LIKE = True
 386
 387    # Whether the LikeProperty needs to be specified inside of the schema clause
 388    LIKE_PROPERTY_INSIDE_SCHEMA = False
 389
 390    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 391    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 392    MULTI_ARG_DISTINCT = True
 393
 394    # Whether the JSON extraction operators expect a value of type JSON
 395    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 396
 397    # Whether bracketed keys like ["foo"] are supported in JSON paths
 398    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 399
 400    # Whether to escape keys using single quotes in JSON paths
 401    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 402
 403    # The JSONPathPart expressions supported by this dialect
 404    SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy()
 405
 406    # Whether any(f(x) for x in array) can be implemented by this dialect
 407    CAN_IMPLEMENT_ARRAY_ANY = False
 408
 409    # Whether the function TO_NUMBER is supported
 410    SUPPORTS_TO_NUMBER = True
 411
 412    # Whether EXCLUDE in window specification is supported
 413    SUPPORTS_WINDOW_EXCLUDE = False
 414
 415    # Whether or not set op modifiers apply to the outer set op or select.
 416    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 417    # True means limit 1 happens after the set op, False means it it happens on y.
 418    SET_OP_MODIFIERS = True
 419
 420    # Whether parameters from COPY statement are wrapped in parentheses
 421    COPY_PARAMS_ARE_WRAPPED = True
 422
 423    # Whether values of params are set with "=" token or empty space
 424    COPY_PARAMS_EQ_REQUIRED = False
 425
 426    # Whether COPY statement has INTO keyword
 427    COPY_HAS_INTO_KEYWORD = True
 428
 429    # Whether the conditional TRY(expression) function is supported
 430    TRY_SUPPORTED = True
 431
 432    # Whether the UESCAPE syntax in unicode strings is supported
 433    SUPPORTS_UESCAPE = True
 434
 435    # The keyword to use when generating a star projection with excluded columns
 436    STAR_EXCEPT = "EXCEPT"
 437
 438    # The HEX function name
 439    HEX_FUNC = "HEX"
 440
 441    # The keywords to use when prefixing & separating WITH based properties
 442    WITH_PROPERTIES_PREFIX = "WITH"
 443
 444    # Whether to quote the generated expression of exp.JsonPath
 445    QUOTE_JSON_PATH = True
 446
 447    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 448    PAD_FILL_PATTERN_IS_REQUIRED = False
 449
 450    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 451    SUPPORTS_EXPLODING_PROJECTIONS = True
 452
 453    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 454    ARRAY_CONCAT_IS_VAR_LEN = True
 455
 456    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 457    SUPPORTS_CONVERT_TIMEZONE = False
 458
 459    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 460    SUPPORTS_MEDIAN = True
 461
 462    # Whether UNIX_SECONDS(timestamp) is supported
 463    SUPPORTS_UNIX_SECONDS = False
 464
 465    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 466    ALTER_SET_WRAPPED = False
 467
 468    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 469    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 470    # TODO: The normalization should be done by default once we've tested it across all dialects.
 471    NORMALIZE_EXTRACT_DATE_PARTS = False
 472
 473    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 474    PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
 475
 476    # The function name of the exp.ArraySize expression
 477    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 478
 479    # The syntax to use when altering the type of a column
 480    ALTER_SET_TYPE = "SET DATA TYPE"
 481
 482    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 483    # None -> Doesn't support it at all
 484    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 485    # True (Postgres) -> Explicitly requires it
 486    ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None
 487
 488    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 489    SUPPORTS_DECODE_CASE = True
 490
 491    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 492    SUPPORTS_BETWEEN_FLAGS = False
 493
 494    TYPE_MAPPING = {
 495        exp.DataType.Type.DATETIME2: "TIMESTAMP",
 496        exp.DataType.Type.NCHAR: "CHAR",
 497        exp.DataType.Type.NVARCHAR: "VARCHAR",
 498        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 499        exp.DataType.Type.LONGTEXT: "TEXT",
 500        exp.DataType.Type.TINYTEXT: "TEXT",
 501        exp.DataType.Type.BLOB: "VARBINARY",
 502        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 503        exp.DataType.Type.LONGBLOB: "BLOB",
 504        exp.DataType.Type.TINYBLOB: "BLOB",
 505        exp.DataType.Type.INET: "INET",
 506        exp.DataType.Type.ROWVERSION: "VARBINARY",
 507        exp.DataType.Type.SMALLDATETIME: "TIMESTAMP",
 508    }
 509
 510    TIME_PART_SINGULARS = {
 511        "MICROSECONDS": "MICROSECOND",
 512        "SECONDS": "SECOND",
 513        "MINUTES": "MINUTE",
 514        "HOURS": "HOUR",
 515        "DAYS": "DAY",
 516        "WEEKS": "WEEK",
 517        "MONTHS": "MONTH",
 518        "QUARTERS": "QUARTER",
 519        "YEARS": "YEAR",
 520    }
 521
 522    AFTER_HAVING_MODIFIER_TRANSFORMS = {
 523        "cluster": lambda self, e: self.sql(e, "cluster"),
 524        "distribute": lambda self, e: self.sql(e, "distribute"),
 525        "sort": lambda self, e: self.sql(e, "sort"),
 526        "windows": lambda self, e: (
 527            self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
 528            if e.args.get("windows")
 529            else ""
 530        ),
 531        "qualify": lambda self, e: self.sql(e, "qualify"),
 532    }
 533
 534    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 535
 536    STRUCT_DELIMITER = ("<", ">")
 537
 538    PARAMETER_TOKEN = "@"
 539    NAMED_PLACEHOLDER_TOKEN = ":"
 540
 541    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set()
 542
 543    PROPERTIES_LOCATION = {
 544        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 545        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 546        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 547        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 548        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 549        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 550        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 551        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 552        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 553        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 554        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 555        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 556        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 557        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 558        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 559        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 560        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 561        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 562        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 563        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 564        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 565        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 566        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 567        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 568        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 569        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 570        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 571        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 572        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 573        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 574        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 575        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 576        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 577        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 578        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 579        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 580        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 581        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 582        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 583        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 584        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 585        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 586        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 587        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 588        exp.LogProperty: exp.Properties.Location.POST_NAME,
 589        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 590        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 591        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 592        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 593        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 594        exp.Order: exp.Properties.Location.POST_SCHEMA,
 595        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 596        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 597        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 598        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 599        exp.Property: exp.Properties.Location.POST_WITH,
 600        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 601        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 602        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 603        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 604        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 605        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 606        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 607        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 608        exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA,
 609        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 610        exp.Set: exp.Properties.Location.POST_SCHEMA,
 611        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 612        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 613        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 614        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 615        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 616        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 617        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 618        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 619        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 620        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 621        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 622        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 623        exp.Tags: exp.Properties.Location.POST_WITH,
 624        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 625        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 626        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 627        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 628        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 629        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 630        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 631        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 632        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 633        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 634        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 635        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 636        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 637        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 638        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 639    }
 640
 641    # Keywords that can't be used as unquoted identifier names
 642    RESERVED_KEYWORDS: t.Set[str] = set()
 643
 644    # Expressions whose comments are separated from them for better formatting
 645    WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 646        exp.Command,
 647        exp.Create,
 648        exp.Describe,
 649        exp.Delete,
 650        exp.Drop,
 651        exp.From,
 652        exp.Insert,
 653        exp.Join,
 654        exp.MultitableInserts,
 655        exp.Order,
 656        exp.Group,
 657        exp.Having,
 658        exp.Select,
 659        exp.SetOperation,
 660        exp.Update,
 661        exp.Where,
 662        exp.With,
 663    )
 664
 665    # Expressions that should not have their comments generated in maybe_comment
 666    EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 667        exp.Binary,
 668        exp.SetOperation,
 669    )
 670
 671    # Expressions that can remain unwrapped when appearing in the context of an INTERVAL
 672    UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = (
 673        exp.Column,
 674        exp.Literal,
 675        exp.Neg,
 676        exp.Paren,
 677    )
 678
 679    PARAMETERIZABLE_TEXT_TYPES = {
 680        exp.DataType.Type.NVARCHAR,
 681        exp.DataType.Type.VARCHAR,
 682        exp.DataType.Type.CHAR,
 683        exp.DataType.Type.NCHAR,
 684    }
 685
 686    # Expressions that need to have all CTEs under them bubbled up to them
 687    EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set()
 688
 689    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = ()
 690
 691    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 692
 693    __slots__ = (
 694        "pretty",
 695        "identify",
 696        "normalize",
 697        "pad",
 698        "_indent",
 699        "normalize_functions",
 700        "unsupported_level",
 701        "max_unsupported",
 702        "leading_comma",
 703        "max_text_width",
 704        "comments",
 705        "dialect",
 706        "unsupported_messages",
 707        "_escaped_quote_end",
 708        "_escaped_identifier_end",
 709        "_next_name",
 710        "_identifier_start",
 711        "_identifier_end",
 712        "_quote_json_path_key_using_brackets",
 713    )
 714
 715    def __init__(
 716        self,
 717        pretty: t.Optional[bool] = None,
 718        identify: str | bool = False,
 719        normalize: bool = False,
 720        pad: int = 2,
 721        indent: int = 2,
 722        normalize_functions: t.Optional[str | bool] = None,
 723        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 724        max_unsupported: int = 3,
 725        leading_comma: bool = False,
 726        max_text_width: int = 80,
 727        comments: bool = True,
 728        dialect: DialectType = None,
 729    ):
 730        import sqlglot
 731        from sqlglot.dialects import Dialect
 732
 733        self.pretty = pretty if pretty is not None else sqlglot.pretty
 734        self.identify = identify
 735        self.normalize = normalize
 736        self.pad = pad
 737        self._indent = indent
 738        self.unsupported_level = unsupported_level
 739        self.max_unsupported = max_unsupported
 740        self.leading_comma = leading_comma
 741        self.max_text_width = max_text_width
 742        self.comments = comments
 743        self.dialect = Dialect.get_or_raise(dialect)
 744
 745        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 746        self.normalize_functions = (
 747            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 748        )
 749
 750        self.unsupported_messages: t.List[str] = []
 751        self._escaped_quote_end: str = (
 752            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 753        )
 754        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 755
 756        self._next_name = name_sequence("_t")
 757
 758        self._identifier_start = self.dialect.IDENTIFIER_START
 759        self._identifier_end = self.dialect.IDENTIFIER_END
 760
 761        self._quote_json_path_key_using_brackets = True
 762
 763    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
 764        """
 765        Generates the SQL string corresponding to the given syntax tree.
 766
 767        Args:
 768            expression: The syntax tree.
 769            copy: Whether to copy the expression. The generator performs mutations so
 770                it is safer to copy.
 771
 772        Returns:
 773            The SQL string corresponding to `expression`.
 774        """
 775        if copy:
 776            expression = expression.copy()
 777
 778        expression = self.preprocess(expression)
 779
 780        self.unsupported_messages = []
 781        sql = self.sql(expression).strip()
 782
 783        if self.pretty:
 784            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 785
 786        if self.unsupported_level == ErrorLevel.IGNORE:
 787            return sql
 788
 789        if self.unsupported_level == ErrorLevel.WARN:
 790            for msg in self.unsupported_messages:
 791                logger.warning(msg)
 792        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 793            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 794
 795        return sql
 796
 797    def preprocess(self, expression: exp.Expression) -> exp.Expression:
 798        """Apply generic preprocessing transformations to a given expression."""
 799        expression = self._move_ctes_to_top_level(expression)
 800
 801        if self.ENSURE_BOOLS:
 802            from sqlglot.transforms import ensure_bools
 803
 804            expression = ensure_bools(expression)
 805
 806        return expression
 807
 808    def _move_ctes_to_top_level(self, expression: E) -> E:
 809        if (
 810            not expression.parent
 811            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 812            and any(node.parent is not expression for node in expression.find_all(exp.With))
 813        ):
 814            from sqlglot.transforms import move_ctes_to_top_level
 815
 816            expression = move_ctes_to_top_level(expression)
 817        return expression
 818
 819    def unsupported(self, message: str) -> None:
 820        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 821            raise UnsupportedError(message)
 822        self.unsupported_messages.append(message)
 823
 824    def sep(self, sep: str = " ") -> str:
 825        return f"{sep.strip()}\n" if self.pretty else sep
 826
 827    def seg(self, sql: str, sep: str = " ") -> str:
 828        return f"{self.sep(sep)}{sql}"
 829
 830    def sanitize_comment(self, comment: str) -> str:
 831        comment = " " + comment if comment[0].strip() else comment
 832        comment = comment + " " if comment[-1].strip() else comment
 833
 834        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
 835            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
 836            comment = comment.replace("*/", "* /")
 837
 838        return comment
 839
 840    def maybe_comment(
 841        self,
 842        sql: str,
 843        expression: t.Optional[exp.Expression] = None,
 844        comments: t.Optional[t.List[str]] = None,
 845        separated: bool = False,
 846    ) -> str:
 847        comments = (
 848            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 849            if self.comments
 850            else None
 851        )
 852
 853        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 854            return sql
 855
 856        comments_sql = " ".join(
 857            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
 858        )
 859
 860        if not comments_sql:
 861            return sql
 862
 863        comments_sql = self._replace_line_breaks(comments_sql)
 864
 865        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 866            return (
 867                f"{self.sep()}{comments_sql}{sql}"
 868                if not sql or sql[0].isspace()
 869                else f"{comments_sql}{self.sep()}{sql}"
 870            )
 871
 872        return f"{sql} {comments_sql}"
 873
 874    def wrap(self, expression: exp.Expression | str) -> str:
 875        this_sql = (
 876            self.sql(expression)
 877            if isinstance(expression, exp.UNWRAPPED_QUERIES)
 878            else self.sql(expression, "this")
 879        )
 880        if not this_sql:
 881            return "()"
 882
 883        this_sql = self.indent(this_sql, level=1, pad=0)
 884        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 885
 886    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 887        original = self.identify
 888        self.identify = False
 889        result = func(*args, **kwargs)
 890        self.identify = original
 891        return result
 892
 893    def normalize_func(self, name: str) -> str:
 894        if self.normalize_functions == "upper" or self.normalize_functions is True:
 895            return name.upper()
 896        if self.normalize_functions == "lower":
 897            return name.lower()
 898        return name
 899
 900    def indent(
 901        self,
 902        sql: str,
 903        level: int = 0,
 904        pad: t.Optional[int] = None,
 905        skip_first: bool = False,
 906        skip_last: bool = False,
 907    ) -> str:
 908        if not self.pretty or not sql:
 909            return sql
 910
 911        pad = self.pad if pad is None else pad
 912        lines = sql.split("\n")
 913
 914        return "\n".join(
 915            (
 916                line
 917                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 918                else f"{' ' * (level * self._indent + pad)}{line}"
 919            )
 920            for i, line in enumerate(lines)
 921        )
 922
 923    def sql(
 924        self,
 925        expression: t.Optional[str | exp.Expression],
 926        key: t.Optional[str] = None,
 927        comment: bool = True,
 928    ) -> str:
 929        if not expression:
 930            return ""
 931
 932        if isinstance(expression, str):
 933            return expression
 934
 935        if key:
 936            value = expression.args.get(key)
 937            if value:
 938                return self.sql(value)
 939            return ""
 940
 941        transform = self.TRANSFORMS.get(expression.__class__)
 942
 943        if callable(transform):
 944            sql = transform(self, expression)
 945        elif isinstance(expression, exp.Expression):
 946            exp_handler_name = f"{expression.key}_sql"
 947
 948            if hasattr(self, exp_handler_name):
 949                sql = getattr(self, exp_handler_name)(expression)
 950            elif isinstance(expression, exp.Func):
 951                sql = self.function_fallback_sql(expression)
 952            elif isinstance(expression, exp.Property):
 953                sql = self.property_sql(expression)
 954            else:
 955                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
 956        else:
 957            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
 958
 959        return self.maybe_comment(sql, expression) if self.comments and comment else sql
 960
 961    def uncache_sql(self, expression: exp.Uncache) -> str:
 962        table = self.sql(expression, "this")
 963        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
 964        return f"UNCACHE TABLE{exists_sql} {table}"
 965
 966    def cache_sql(self, expression: exp.Cache) -> str:
 967        lazy = " LAZY" if expression.args.get("lazy") else ""
 968        table = self.sql(expression, "this")
 969        options = expression.args.get("options")
 970        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
 971        sql = self.sql(expression, "expression")
 972        sql = f" AS{self.sep()}{sql}" if sql else ""
 973        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
 974        return self.prepend_ctes(expression, sql)
 975
 976    def characterset_sql(self, expression: exp.CharacterSet) -> str:
 977        if isinstance(expression.parent, exp.Cast):
 978            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
 979        default = "DEFAULT " if expression.args.get("default") else ""
 980        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
 981
 982    def column_parts(self, expression: exp.Column) -> str:
 983        return ".".join(
 984            self.sql(part)
 985            for part in (
 986                expression.args.get("catalog"),
 987                expression.args.get("db"),
 988                expression.args.get("table"),
 989                expression.args.get("this"),
 990            )
 991            if part
 992        )
 993
 994    def column_sql(self, expression: exp.Column) -> str:
 995        join_mark = " (+)" if expression.args.get("join_mark") else ""
 996
 997        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
 998            join_mark = ""
 999            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1000
1001        return f"{self.column_parts(expression)}{join_mark}"
1002
1003    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1004        this = self.sql(expression, "this")
1005        this = f" {this}" if this else ""
1006        position = self.sql(expression, "position")
1007        return f"{position}{this}"
1008
1009    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1010        column = self.sql(expression, "this")
1011        kind = self.sql(expression, "kind")
1012        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1013        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1014        kind = f"{sep}{kind}" if kind else ""
1015        constraints = f" {constraints}" if constraints else ""
1016        position = self.sql(expression, "position")
1017        position = f" {position}" if position else ""
1018
1019        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1020            kind = ""
1021
1022        return f"{exists}{column}{kind}{constraints}{position}"
1023
1024    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1025        this = self.sql(expression, "this")
1026        kind_sql = self.sql(expression, "kind").strip()
1027        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1028
1029    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1030        this = self.sql(expression, "this")
1031        if expression.args.get("not_null"):
1032            persisted = " PERSISTED NOT NULL"
1033        elif expression.args.get("persisted"):
1034            persisted = " PERSISTED"
1035        else:
1036            persisted = ""
1037
1038        return f"AS {this}{persisted}"
1039
1040    def autoincrementcolumnconstraint_sql(self, _) -> str:
1041        return self.token_sql(TokenType.AUTO_INCREMENT)
1042
1043    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1044        if isinstance(expression.this, list):
1045            this = self.wrap(self.expressions(expression, key="this", flat=True))
1046        else:
1047            this = self.sql(expression, "this")
1048
1049        return f"COMPRESS {this}"
1050
1051    def generatedasidentitycolumnconstraint_sql(
1052        self, expression: exp.GeneratedAsIdentityColumnConstraint
1053    ) -> str:
1054        this = ""
1055        if expression.this is not None:
1056            on_null = " ON NULL" if expression.args.get("on_null") else ""
1057            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1058
1059        start = expression.args.get("start")
1060        start = f"START WITH {start}" if start else ""
1061        increment = expression.args.get("increment")
1062        increment = f" INCREMENT BY {increment}" if increment else ""
1063        minvalue = expression.args.get("minvalue")
1064        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1065        maxvalue = expression.args.get("maxvalue")
1066        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1067        cycle = expression.args.get("cycle")
1068        cycle_sql = ""
1069
1070        if cycle is not None:
1071            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1072            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1073
1074        sequence_opts = ""
1075        if start or increment or cycle_sql:
1076            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1077            sequence_opts = f" ({sequence_opts.strip()})"
1078
1079        expr = self.sql(expression, "expression")
1080        expr = f"({expr})" if expr else "IDENTITY"
1081
1082        return f"GENERATED{this} AS {expr}{sequence_opts}"
1083
1084    def generatedasrowcolumnconstraint_sql(
1085        self, expression: exp.GeneratedAsRowColumnConstraint
1086    ) -> str:
1087        start = "START" if expression.args.get("start") else "END"
1088        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1089        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1090
1091    def periodforsystemtimeconstraint_sql(
1092        self, expression: exp.PeriodForSystemTimeConstraint
1093    ) -> str:
1094        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1095
1096    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1097        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1098
1099    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1100        desc = expression.args.get("desc")
1101        if desc is not None:
1102            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1103        options = self.expressions(expression, key="options", flat=True, sep=" ")
1104        options = f" {options}" if options else ""
1105        return f"PRIMARY KEY{options}"
1106
1107    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1108        this = self.sql(expression, "this")
1109        this = f" {this}" if this else ""
1110        index_type = expression.args.get("index_type")
1111        index_type = f" USING {index_type}" if index_type else ""
1112        on_conflict = self.sql(expression, "on_conflict")
1113        on_conflict = f" {on_conflict}" if on_conflict else ""
1114        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1115        options = self.expressions(expression, key="options", flat=True, sep=" ")
1116        options = f" {options}" if options else ""
1117        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1118
1119    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1120        return self.sql(expression, "this")
1121
1122    def create_sql(self, expression: exp.Create) -> str:
1123        kind = self.sql(expression, "kind")
1124        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1125        properties = expression.args.get("properties")
1126        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1127
1128        this = self.createable_sql(expression, properties_locs)
1129
1130        properties_sql = ""
1131        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1132            exp.Properties.Location.POST_WITH
1133        ):
1134            properties_sql = self.sql(
1135                exp.Properties(
1136                    expressions=[
1137                        *properties_locs[exp.Properties.Location.POST_SCHEMA],
1138                        *properties_locs[exp.Properties.Location.POST_WITH],
1139                    ]
1140                )
1141            )
1142
1143            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1144                properties_sql = self.sep() + properties_sql
1145            elif not self.pretty:
1146                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1147                properties_sql = f" {properties_sql}"
1148
1149        begin = " BEGIN" if expression.args.get("begin") else ""
1150        end = " END" if expression.args.get("end") else ""
1151
1152        expression_sql = self.sql(expression, "expression")
1153        if expression_sql:
1154            expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
1155
1156            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1157                postalias_props_sql = ""
1158                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1159                    postalias_props_sql = self.properties(
1160                        exp.Properties(
1161                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1162                        ),
1163                        wrapped=False,
1164                    )
1165                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1166                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1167
1168        postindex_props_sql = ""
1169        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1170            postindex_props_sql = self.properties(
1171                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1172                wrapped=False,
1173                prefix=" ",
1174            )
1175
1176        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1177        indexes = f" {indexes}" if indexes else ""
1178        index_sql = indexes + postindex_props_sql
1179
1180        replace = " OR REPLACE" if expression.args.get("replace") else ""
1181        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1182        unique = " UNIQUE" if expression.args.get("unique") else ""
1183
1184        clustered = expression.args.get("clustered")
1185        if clustered is None:
1186            clustered_sql = ""
1187        elif clustered:
1188            clustered_sql = " CLUSTERED COLUMNSTORE"
1189        else:
1190            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1191
1192        postcreate_props_sql = ""
1193        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1194            postcreate_props_sql = self.properties(
1195                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1196                sep=" ",
1197                prefix=" ",
1198                wrapped=False,
1199            )
1200
1201        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1202
1203        postexpression_props_sql = ""
1204        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1205            postexpression_props_sql = self.properties(
1206                exp.Properties(
1207                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1208                ),
1209                sep=" ",
1210                prefix=" ",
1211                wrapped=False,
1212            )
1213
1214        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1215        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1216        no_schema_binding = (
1217            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1218        )
1219
1220        clone = self.sql(expression, "clone")
1221        clone = f" {clone}" if clone else ""
1222
1223        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1224            properties_expression = f"{expression_sql}{properties_sql}"
1225        else:
1226            properties_expression = f"{properties_sql}{expression_sql}"
1227
1228        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1229        return self.prepend_ctes(expression, expression_sql)
1230
1231    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1232        start = self.sql(expression, "start")
1233        start = f"START WITH {start}" if start else ""
1234        increment = self.sql(expression, "increment")
1235        increment = f" INCREMENT BY {increment}" if increment else ""
1236        minvalue = self.sql(expression, "minvalue")
1237        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1238        maxvalue = self.sql(expression, "maxvalue")
1239        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1240        owned = self.sql(expression, "owned")
1241        owned = f" OWNED BY {owned}" if owned else ""
1242
1243        cache = expression.args.get("cache")
1244        if cache is None:
1245            cache_str = ""
1246        elif cache is True:
1247            cache_str = " CACHE"
1248        else:
1249            cache_str = f" CACHE {cache}"
1250
1251        options = self.expressions(expression, key="options", flat=True, sep=" ")
1252        options = f" {options}" if options else ""
1253
1254        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1255
1256    def clone_sql(self, expression: exp.Clone) -> str:
1257        this = self.sql(expression, "this")
1258        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1259        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1260        return f"{shallow}{keyword} {this}"
1261
1262    def describe_sql(self, expression: exp.Describe) -> str:
1263        style = expression.args.get("style")
1264        style = f" {style}" if style else ""
1265        partition = self.sql(expression, "partition")
1266        partition = f" {partition}" if partition else ""
1267        format = self.sql(expression, "format")
1268        format = f" {format}" if format else ""
1269
1270        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1271
1272    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1273        tag = self.sql(expression, "tag")
1274        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1275
1276    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1277        with_ = self.sql(expression, "with")
1278        if with_:
1279            sql = f"{with_}{self.sep()}{sql}"
1280        return sql
1281
1282    def with_sql(self, expression: exp.With) -> str:
1283        sql = self.expressions(expression, flat=True)
1284        recursive = (
1285            "RECURSIVE "
1286            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1287            else ""
1288        )
1289        search = self.sql(expression, "search")
1290        search = f" {search}" if search else ""
1291
1292        return f"WITH {recursive}{sql}{search}"
1293
1294    def cte_sql(self, expression: exp.CTE) -> str:
1295        alias = expression.args.get("alias")
1296        if alias:
1297            alias.add_comments(expression.pop_comments())
1298
1299        alias_sql = self.sql(expression, "alias")
1300
1301        materialized = expression.args.get("materialized")
1302        if materialized is False:
1303            materialized = "NOT MATERIALIZED "
1304        elif materialized:
1305            materialized = "MATERIALIZED "
1306
1307        return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1308
1309    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1310        alias = self.sql(expression, "this")
1311        columns = self.expressions(expression, key="columns", flat=True)
1312        columns = f"({columns})" if columns else ""
1313
1314        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1315            columns = ""
1316            self.unsupported("Named columns are not supported in table alias.")
1317
1318        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1319            alias = self._next_name()
1320
1321        return f"{alias}{columns}"
1322
1323    def bitstring_sql(self, expression: exp.BitString) -> str:
1324        this = self.sql(expression, "this")
1325        if self.dialect.BIT_START:
1326            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1327        return f"{int(this, 2)}"
1328
1329    def hexstring_sql(
1330        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1331    ) -> str:
1332        this = self.sql(expression, "this")
1333        is_integer_type = expression.args.get("is_integer")
1334
1335        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1336            not self.dialect.HEX_START and not binary_function_repr
1337        ):
1338            # Integer representation will be returned if:
1339            # - The read dialect treats the hex value as integer literal but not the write
1340            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1341            return f"{int(this, 16)}"
1342
1343        if not is_integer_type:
1344            # Read dialect treats the hex value as BINARY/BLOB
1345            if binary_function_repr:
1346                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1347                return self.func(binary_function_repr, exp.Literal.string(this))
1348            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1349                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1350                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1351
1352        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1353
1354    def bytestring_sql(self, expression: exp.ByteString) -> str:
1355        this = self.sql(expression, "this")
1356        if self.dialect.BYTE_START:
1357            return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}"
1358        return this
1359
1360    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1361        this = self.sql(expression, "this")
1362        escape = expression.args.get("escape")
1363
1364        if self.dialect.UNICODE_START:
1365            escape_substitute = r"\\\1"
1366            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1367        else:
1368            escape_substitute = r"\\u\1"
1369            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1370
1371        if escape:
1372            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1373            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1374        else:
1375            escape_pattern = ESCAPED_UNICODE_RE
1376            escape_sql = ""
1377
1378        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1379            this = escape_pattern.sub(escape_substitute, this)
1380
1381        return f"{left_quote}{this}{right_quote}{escape_sql}"
1382
1383    def rawstring_sql(self, expression: exp.RawString) -> str:
1384        string = expression.this
1385        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1386            string = string.replace("\\", "\\\\")
1387
1388        string = self.escape_str(string, escape_backslash=False)
1389        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1390
1391    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1392        this = self.sql(expression, "this")
1393        specifier = self.sql(expression, "expression")
1394        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1395        return f"{this}{specifier}"
1396
1397    def datatype_sql(self, expression: exp.DataType) -> str:
1398        nested = ""
1399        values = ""
1400        interior = self.expressions(expression, flat=True)
1401
1402        type_value = expression.this
1403        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1404            type_sql = self.sql(expression, "kind")
1405        else:
1406            type_sql = (
1407                self.TYPE_MAPPING.get(type_value, type_value.value)
1408                if isinstance(type_value, exp.DataType.Type)
1409                else type_value
1410            )
1411
1412        if interior:
1413            if expression.args.get("nested"):
1414                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1415                if expression.args.get("values") is not None:
1416                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1417                    values = self.expressions(expression, key="values", flat=True)
1418                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1419            elif type_value == exp.DataType.Type.INTERVAL:
1420                nested = f" {interior}"
1421            else:
1422                nested = f"({interior})"
1423
1424        type_sql = f"{type_sql}{nested}{values}"
1425        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1426            exp.DataType.Type.TIMETZ,
1427            exp.DataType.Type.TIMESTAMPTZ,
1428        ):
1429            type_sql = f"{type_sql} WITH TIME ZONE"
1430
1431        return type_sql
1432
1433    def directory_sql(self, expression: exp.Directory) -> str:
1434        local = "LOCAL " if expression.args.get("local") else ""
1435        row_format = self.sql(expression, "row_format")
1436        row_format = f" {row_format}" if row_format else ""
1437        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1438
1439    def delete_sql(self, expression: exp.Delete) -> str:
1440        this = self.sql(expression, "this")
1441        this = f" FROM {this}" if this else ""
1442        using = self.sql(expression, "using")
1443        using = f" USING {using}" if using else ""
1444        cluster = self.sql(expression, "cluster")
1445        cluster = f" {cluster}" if cluster else ""
1446        where = self.sql(expression, "where")
1447        returning = self.sql(expression, "returning")
1448        limit = self.sql(expression, "limit")
1449        tables = self.expressions(expression, key="tables")
1450        tables = f" {tables}" if tables else ""
1451        if self.RETURNING_END:
1452            expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}"
1453        else:
1454            expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}"
1455        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1456
1457    def drop_sql(self, expression: exp.Drop) -> str:
1458        this = self.sql(expression, "this")
1459        expressions = self.expressions(expression, flat=True)
1460        expressions = f" ({expressions})" if expressions else ""
1461        kind = expression.args["kind"]
1462        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1463        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1464        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1465        on_cluster = self.sql(expression, "cluster")
1466        on_cluster = f" {on_cluster}" if on_cluster else ""
1467        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1468        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1469        cascade = " CASCADE" if expression.args.get("cascade") else ""
1470        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1471        purge = " PURGE" if expression.args.get("purge") else ""
1472        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1473
1474    def set_operation(self, expression: exp.SetOperation) -> str:
1475        op_type = type(expression)
1476        op_name = op_type.key.upper()
1477
1478        distinct = expression.args.get("distinct")
1479        if (
1480            distinct is False
1481            and op_type in (exp.Except, exp.Intersect)
1482            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1483        ):
1484            self.unsupported(f"{op_name} ALL is not supported")
1485
1486        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1487
1488        if distinct is None:
1489            distinct = default_distinct
1490            if distinct is None:
1491                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1492
1493        if distinct is default_distinct:
1494            distinct_or_all = ""
1495        else:
1496            distinct_or_all = " DISTINCT" if distinct else " ALL"
1497
1498        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1499        side_kind = f"{side_kind} " if side_kind else ""
1500
1501        by_name = " BY NAME" if expression.args.get("by_name") else ""
1502        on = self.expressions(expression, key="on", flat=True)
1503        on = f" ON ({on})" if on else ""
1504
1505        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1506
1507    def set_operations(self, expression: exp.SetOperation) -> str:
1508        if not self.SET_OP_MODIFIERS:
1509            limit = expression.args.get("limit")
1510            order = expression.args.get("order")
1511
1512            if limit or order:
1513                select = self._move_ctes_to_top_level(
1514                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1515                )
1516
1517                if limit:
1518                    select = select.limit(limit.pop(), copy=False)
1519                if order:
1520                    select = select.order_by(order.pop(), copy=False)
1521                return self.sql(select)
1522
1523        sqls: t.List[str] = []
1524        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1525
1526        while stack:
1527            node = stack.pop()
1528
1529            if isinstance(node, exp.SetOperation):
1530                stack.append(node.expression)
1531                stack.append(
1532                    self.maybe_comment(
1533                        self.set_operation(node), comments=node.comments, separated=True
1534                    )
1535                )
1536                stack.append(node.this)
1537            else:
1538                sqls.append(self.sql(node))
1539
1540        this = self.sep().join(sqls)
1541        this = self.query_modifiers(expression, this)
1542        return self.prepend_ctes(expression, this)
1543
1544    def fetch_sql(self, expression: exp.Fetch) -> str:
1545        direction = expression.args.get("direction")
1546        direction = f" {direction}" if direction else ""
1547        count = self.sql(expression, "count")
1548        count = f" {count}" if count else ""
1549        limit_options = self.sql(expression, "limit_options")
1550        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1551        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1552
1553    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1554        percent = " PERCENT" if expression.args.get("percent") else ""
1555        rows = " ROWS" if expression.args.get("rows") else ""
1556        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1557        if not with_ties and rows:
1558            with_ties = " ONLY"
1559        return f"{percent}{rows}{with_ties}"
1560
1561    def filter_sql(self, expression: exp.Filter) -> str:
1562        if self.AGGREGATE_FILTER_SUPPORTED:
1563            this = self.sql(expression, "this")
1564            where = self.sql(expression, "expression").strip()
1565            return f"{this} FILTER({where})"
1566
1567        agg = expression.this
1568        agg_arg = agg.this
1569        cond = expression.expression.this
1570        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1571        return self.sql(agg)
1572
1573    def hint_sql(self, expression: exp.Hint) -> str:
1574        if not self.QUERY_HINTS:
1575            self.unsupported("Hints are not supported")
1576            return ""
1577
1578        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1579
1580    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1581        using = self.sql(expression, "using")
1582        using = f" USING {using}" if using else ""
1583        columns = self.expressions(expression, key="columns", flat=True)
1584        columns = f"({columns})" if columns else ""
1585        partition_by = self.expressions(expression, key="partition_by", flat=True)
1586        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1587        where = self.sql(expression, "where")
1588        include = self.expressions(expression, key="include", flat=True)
1589        if include:
1590            include = f" INCLUDE ({include})"
1591        with_storage = self.expressions(expression, key="with_storage", flat=True)
1592        with_storage = f" WITH ({with_storage})" if with_storage else ""
1593        tablespace = self.sql(expression, "tablespace")
1594        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1595        on = self.sql(expression, "on")
1596        on = f" ON {on}" if on else ""
1597
1598        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1599
1600    def index_sql(self, expression: exp.Index) -> str:
1601        unique = "UNIQUE " if expression.args.get("unique") else ""
1602        primary = "PRIMARY " if expression.args.get("primary") else ""
1603        amp = "AMP " if expression.args.get("amp") else ""
1604        name = self.sql(expression, "this")
1605        name = f"{name} " if name else ""
1606        table = self.sql(expression, "table")
1607        table = f"{self.INDEX_ON} {table}" if table else ""
1608
1609        index = "INDEX " if not table else ""
1610
1611        params = self.sql(expression, "params")
1612        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1613
1614    def identifier_sql(self, expression: exp.Identifier) -> str:
1615        text = expression.name
1616        lower = text.lower()
1617        text = lower if self.normalize and not expression.quoted else text
1618        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1619        if (
1620            expression.quoted
1621            or self.dialect.can_identify(text, self.identify)
1622            or lower in self.RESERVED_KEYWORDS
1623            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1624        ):
1625            text = f"{self._identifier_start}{text}{self._identifier_end}"
1626        return text
1627
1628    def hex_sql(self, expression: exp.Hex) -> str:
1629        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1630        if self.dialect.HEX_LOWERCASE:
1631            text = self.func("LOWER", text)
1632
1633        return text
1634
1635    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1636        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1637        if not self.dialect.HEX_LOWERCASE:
1638            text = self.func("LOWER", text)
1639        return text
1640
1641    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1642        input_format = self.sql(expression, "input_format")
1643        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1644        output_format = self.sql(expression, "output_format")
1645        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1646        return self.sep().join((input_format, output_format))
1647
1648    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1649        string = self.sql(exp.Literal.string(expression.name))
1650        return f"{prefix}{string}"
1651
1652    def partition_sql(self, expression: exp.Partition) -> str:
1653        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1654        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1655
1656    def properties_sql(self, expression: exp.Properties) -> str:
1657        root_properties = []
1658        with_properties = []
1659
1660        for p in expression.expressions:
1661            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1662            if p_loc == exp.Properties.Location.POST_WITH:
1663                with_properties.append(p)
1664            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1665                root_properties.append(p)
1666
1667        root_props = self.root_properties(exp.Properties(expressions=root_properties))
1668        with_props = self.with_properties(exp.Properties(expressions=with_properties))
1669
1670        if root_props and with_props and not self.pretty:
1671            with_props = " " + with_props
1672
1673        return root_props + with_props
1674
1675    def root_properties(self, properties: exp.Properties) -> str:
1676        if properties.expressions:
1677            return self.expressions(properties, indent=False, sep=" ")
1678        return ""
1679
1680    def properties(
1681        self,
1682        properties: exp.Properties,
1683        prefix: str = "",
1684        sep: str = ", ",
1685        suffix: str = "",
1686        wrapped: bool = True,
1687    ) -> str:
1688        if properties.expressions:
1689            expressions = self.expressions(properties, sep=sep, indent=False)
1690            if expressions:
1691                expressions = self.wrap(expressions) if wrapped else expressions
1692                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1693        return ""
1694
1695    def with_properties(self, properties: exp.Properties) -> str:
1696        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1697
1698    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1699        properties_locs = defaultdict(list)
1700        for p in properties.expressions:
1701            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1702            if p_loc != exp.Properties.Location.UNSUPPORTED:
1703                properties_locs[p_loc].append(p)
1704            else:
1705                self.unsupported(f"Unsupported property {p.key}")
1706
1707        return properties_locs
1708
1709    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1710        if isinstance(expression.this, exp.Dot):
1711            return self.sql(expression, "this")
1712        return f"'{expression.name}'" if string_key else expression.name
1713
1714    def property_sql(self, expression: exp.Property) -> str:
1715        property_cls = expression.__class__
1716        if property_cls == exp.Property:
1717            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1718
1719        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1720        if not property_name:
1721            self.unsupported(f"Unsupported property {expression.key}")
1722
1723        return f"{property_name}={self.sql(expression, 'this')}"
1724
1725    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1726        if self.SUPPORTS_CREATE_TABLE_LIKE:
1727            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1728            options = f" {options}" if options else ""
1729
1730            like = f"LIKE {self.sql(expression, 'this')}{options}"
1731            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1732                like = f"({like})"
1733
1734            return like
1735
1736        if expression.expressions:
1737            self.unsupported("Transpilation of LIKE property options is unsupported")
1738
1739        select = exp.select("*").from_(expression.this).limit(0)
1740        return f"AS {self.sql(select)}"
1741
1742    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1743        no = "NO " if expression.args.get("no") else ""
1744        protection = " PROTECTION" if expression.args.get("protection") else ""
1745        return f"{no}FALLBACK{protection}"
1746
1747    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1748        no = "NO " if expression.args.get("no") else ""
1749        local = expression.args.get("local")
1750        local = f"{local} " if local else ""
1751        dual = "DUAL " if expression.args.get("dual") else ""
1752        before = "BEFORE " if expression.args.get("before") else ""
1753        after = "AFTER " if expression.args.get("after") else ""
1754        return f"{no}{local}{dual}{before}{after}JOURNAL"
1755
1756    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1757        freespace = self.sql(expression, "this")
1758        percent = " PERCENT" if expression.args.get("percent") else ""
1759        return f"FREESPACE={freespace}{percent}"
1760
1761    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1762        if expression.args.get("default"):
1763            property = "DEFAULT"
1764        elif expression.args.get("on"):
1765            property = "ON"
1766        else:
1767            property = "OFF"
1768        return f"CHECKSUM={property}"
1769
1770    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1771        if expression.args.get("no"):
1772            return "NO MERGEBLOCKRATIO"
1773        if expression.args.get("default"):
1774            return "DEFAULT MERGEBLOCKRATIO"
1775
1776        percent = " PERCENT" if expression.args.get("percent") else ""
1777        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1778
1779    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1780        default = expression.args.get("default")
1781        minimum = expression.args.get("minimum")
1782        maximum = expression.args.get("maximum")
1783        if default or minimum or maximum:
1784            if default:
1785                prop = "DEFAULT"
1786            elif minimum:
1787                prop = "MINIMUM"
1788            else:
1789                prop = "MAXIMUM"
1790            return f"{prop} DATABLOCKSIZE"
1791        units = expression.args.get("units")
1792        units = f" {units}" if units else ""
1793        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
1794
1795    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1796        autotemp = expression.args.get("autotemp")
1797        always = expression.args.get("always")
1798        default = expression.args.get("default")
1799        manual = expression.args.get("manual")
1800        never = expression.args.get("never")
1801
1802        if autotemp is not None:
1803            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1804        elif always:
1805            prop = "ALWAYS"
1806        elif default:
1807            prop = "DEFAULT"
1808        elif manual:
1809            prop = "MANUAL"
1810        elif never:
1811            prop = "NEVER"
1812        return f"BLOCKCOMPRESSION={prop}"
1813
1814    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1815        no = expression.args.get("no")
1816        no = " NO" if no else ""
1817        concurrent = expression.args.get("concurrent")
1818        concurrent = " CONCURRENT" if concurrent else ""
1819        target = self.sql(expression, "target")
1820        target = f" {target}" if target else ""
1821        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1822
1823    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1824        if isinstance(expression.this, list):
1825            return f"IN ({self.expressions(expression, key='this', flat=True)})"
1826        if expression.this:
1827            modulus = self.sql(expression, "this")
1828            remainder = self.sql(expression, "expression")
1829            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1830
1831        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1832        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1833        return f"FROM ({from_expressions}) TO ({to_expressions})"
1834
1835    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1836        this = self.sql(expression, "this")
1837
1838        for_values_or_default = expression.expression
1839        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1840            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1841        else:
1842            for_values_or_default = " DEFAULT"
1843
1844        return f"PARTITION OF {this}{for_values_or_default}"
1845
1846    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1847        kind = expression.args.get("kind")
1848        this = f" {self.sql(expression, 'this')}" if expression.this else ""
1849        for_or_in = expression.args.get("for_or_in")
1850        for_or_in = f" {for_or_in}" if for_or_in else ""
1851        lock_type = expression.args.get("lock_type")
1852        override = " OVERRIDE" if expression.args.get("override") else ""
1853        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1854
1855    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1856        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1857        statistics = expression.args.get("statistics")
1858        statistics_sql = ""
1859        if statistics is not None:
1860            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1861        return f"{data_sql}{statistics_sql}"
1862
1863    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1864        this = self.sql(expression, "this")
1865        this = f"HISTORY_TABLE={this}" if this else ""
1866        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
1867        data_consistency = (
1868            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
1869        )
1870        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
1871        retention_period = (
1872            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
1873        )
1874
1875        if this:
1876            on_sql = self.func("ON", this, data_consistency, retention_period)
1877        else:
1878            on_sql = "ON" if expression.args.get("on") else "OFF"
1879
1880        sql = f"SYSTEM_VERSIONING={on_sql}"
1881
1882        return f"WITH({sql})" if expression.args.get("with") else sql
1883
1884    def insert_sql(self, expression: exp.Insert) -> str:
1885        hint = self.sql(expression, "hint")
1886        overwrite = expression.args.get("overwrite")
1887
1888        if isinstance(expression.this, exp.Directory):
1889            this = " OVERWRITE" if overwrite else " INTO"
1890        else:
1891            this = self.INSERT_OVERWRITE if overwrite else " INTO"
1892
1893        stored = self.sql(expression, "stored")
1894        stored = f" {stored}" if stored else ""
1895        alternative = expression.args.get("alternative")
1896        alternative = f" OR {alternative}" if alternative else ""
1897        ignore = " IGNORE" if expression.args.get("ignore") else ""
1898        is_function = expression.args.get("is_function")
1899        if is_function:
1900            this = f"{this} FUNCTION"
1901        this = f"{this} {self.sql(expression, 'this')}"
1902
1903        exists = " IF EXISTS" if expression.args.get("exists") else ""
1904        where = self.sql(expression, "where")
1905        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
1906        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
1907        on_conflict = self.sql(expression, "conflict")
1908        on_conflict = f" {on_conflict}" if on_conflict else ""
1909        by_name = " BY NAME" if expression.args.get("by_name") else ""
1910        returning = self.sql(expression, "returning")
1911
1912        if self.RETURNING_END:
1913            expression_sql = f"{expression_sql}{on_conflict}{returning}"
1914        else:
1915            expression_sql = f"{returning}{expression_sql}{on_conflict}"
1916
1917        partition_by = self.sql(expression, "partition")
1918        partition_by = f" {partition_by}" if partition_by else ""
1919        settings = self.sql(expression, "settings")
1920        settings = f" {settings}" if settings else ""
1921
1922        source = self.sql(expression, "source")
1923        source = f"TABLE {source}" if source else ""
1924
1925        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
1926        return self.prepend_ctes(expression, sql)
1927
1928    def introducer_sql(self, expression: exp.Introducer) -> str:
1929        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
1930
1931    def kill_sql(self, expression: exp.Kill) -> str:
1932        kind = self.sql(expression, "kind")
1933        kind = f" {kind}" if kind else ""
1934        this = self.sql(expression, "this")
1935        this = f" {this}" if this else ""
1936        return f"KILL{kind}{this}"
1937
1938    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
1939        return expression.name
1940
1941    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
1942        return expression.name
1943
1944    def onconflict_sql(self, expression: exp.OnConflict) -> str:
1945        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
1946
1947        constraint = self.sql(expression, "constraint")
1948        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
1949
1950        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
1951        conflict_keys = f"({conflict_keys}) " if conflict_keys else " "
1952        action = self.sql(expression, "action")
1953
1954        expressions = self.expressions(expression, flat=True)
1955        if expressions:
1956            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
1957            expressions = f" {set_keyword}{expressions}"
1958
1959        where = self.sql(expression, "where")
1960        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
1961
1962    def returning_sql(self, expression: exp.Returning) -> str:
1963        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
1964
1965    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
1966        fields = self.sql(expression, "fields")
1967        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
1968        escaped = self.sql(expression, "escaped")
1969        escaped = f" ESCAPED BY {escaped}" if escaped else ""
1970        items = self.sql(expression, "collection_items")
1971        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
1972        keys = self.sql(expression, "map_keys")
1973        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
1974        lines = self.sql(expression, "lines")
1975        lines = f" LINES TERMINATED BY {lines}" if lines else ""
1976        null = self.sql(expression, "null")
1977        null = f" NULL DEFINED AS {null}" if null else ""
1978        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
1979
1980    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
1981        return f"WITH ({self.expressions(expression, flat=True)})"
1982
1983    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
1984        this = f"{self.sql(expression, 'this')} INDEX"
1985        target = self.sql(expression, "target")
1986        target = f" FOR {target}" if target else ""
1987        return f"{this}{target} ({self.expressions(expression, flat=True)})"
1988
1989    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
1990        this = self.sql(expression, "this")
1991        kind = self.sql(expression, "kind")
1992        expr = self.sql(expression, "expression")
1993        return f"{this} ({kind} => {expr})"
1994
1995    def table_parts(self, expression: exp.Table) -> str:
1996        return ".".join(
1997            self.sql(part)
1998            for part in (
1999                expression.args.get("catalog"),
2000                expression.args.get("db"),
2001                expression.args.get("this"),
2002            )
2003            if part is not None
2004        )
2005
2006    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2007        table = self.table_parts(expression)
2008        only = "ONLY " if expression.args.get("only") else ""
2009        partition = self.sql(expression, "partition")
2010        partition = f" {partition}" if partition else ""
2011        version = self.sql(expression, "version")
2012        version = f" {version}" if version else ""
2013        alias = self.sql(expression, "alias")
2014        alias = f"{sep}{alias}" if alias else ""
2015
2016        sample = self.sql(expression, "sample")
2017        if self.dialect.ALIAS_POST_TABLESAMPLE:
2018            sample_pre_alias = sample
2019            sample_post_alias = ""
2020        else:
2021            sample_pre_alias = ""
2022            sample_post_alias = sample
2023
2024        hints = self.expressions(expression, key="hints", sep=" ")
2025        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2026        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2027        joins = self.indent(
2028            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2029        )
2030        laterals = self.expressions(expression, key="laterals", sep="")
2031
2032        file_format = self.sql(expression, "format")
2033        if file_format:
2034            pattern = self.sql(expression, "pattern")
2035            pattern = f", PATTERN => {pattern}" if pattern else ""
2036            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2037
2038        ordinality = expression.args.get("ordinality") or ""
2039        if ordinality:
2040            ordinality = f" WITH ORDINALITY{alias}"
2041            alias = ""
2042
2043        when = self.sql(expression, "when")
2044        if when:
2045            table = f"{table} {when}"
2046
2047        changes = self.sql(expression, "changes")
2048        changes = f" {changes}" if changes else ""
2049
2050        rows_from = self.expressions(expression, key="rows_from")
2051        if rows_from:
2052            table = f"ROWS FROM {self.wrap(rows_from)}"
2053
2054        return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2055
2056    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2057        table = self.func("TABLE", expression.this)
2058        alias = self.sql(expression, "alias")
2059        alias = f" AS {alias}" if alias else ""
2060        sample = self.sql(expression, "sample")
2061        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2062        joins = self.indent(
2063            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2064        )
2065        return f"{table}{alias}{pivots}{sample}{joins}"
2066
2067    def tablesample_sql(
2068        self,
2069        expression: exp.TableSample,
2070        tablesample_keyword: t.Optional[str] = None,
2071    ) -> str:
2072        method = self.sql(expression, "method")
2073        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2074        numerator = self.sql(expression, "bucket_numerator")
2075        denominator = self.sql(expression, "bucket_denominator")
2076        field = self.sql(expression, "bucket_field")
2077        field = f" ON {field}" if field else ""
2078        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2079        seed = self.sql(expression, "seed")
2080        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2081
2082        size = self.sql(expression, "size")
2083        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2084            size = f"{size} ROWS"
2085
2086        percent = self.sql(expression, "percent")
2087        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2088            percent = f"{percent} PERCENT"
2089
2090        expr = f"{bucket}{percent}{size}"
2091        if self.TABLESAMPLE_REQUIRES_PARENS:
2092            expr = f"({expr})"
2093
2094        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2095
2096    def pivot_sql(self, expression: exp.Pivot) -> str:
2097        expressions = self.expressions(expression, flat=True)
2098        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2099
2100        group = self.sql(expression, "group")
2101
2102        if expression.this:
2103            this = self.sql(expression, "this")
2104            if not expressions:
2105                return f"UNPIVOT {this}"
2106
2107            on = f"{self.seg('ON')} {expressions}"
2108            into = self.sql(expression, "into")
2109            into = f"{self.seg('INTO')} {into}" if into else ""
2110            using = self.expressions(expression, key="using", flat=True)
2111            using = f"{self.seg('USING')} {using}" if using else ""
2112            return f"{direction} {this}{on}{into}{using}{group}"
2113
2114        alias = self.sql(expression, "alias")
2115        alias = f" AS {alias}" if alias else ""
2116
2117        fields = self.expressions(
2118            expression,
2119            "fields",
2120            sep=" ",
2121            dynamic=True,
2122            new_line=True,
2123            skip_first=True,
2124            skip_last=True,
2125        )
2126
2127        include_nulls = expression.args.get("include_nulls")
2128        if include_nulls is not None:
2129            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2130        else:
2131            nulls = ""
2132
2133        default_on_null = self.sql(expression, "default_on_null")
2134        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2135        return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2136
2137    def version_sql(self, expression: exp.Version) -> str:
2138        this = f"FOR {expression.name}"
2139        kind = expression.text("kind")
2140        expr = self.sql(expression, "expression")
2141        return f"{this} {kind} {expr}"
2142
2143    def tuple_sql(self, expression: exp.Tuple) -> str:
2144        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2145
2146    def update_sql(self, expression: exp.Update) -> str:
2147        this = self.sql(expression, "this")
2148        set_sql = self.expressions(expression, flat=True)
2149        from_sql = self.sql(expression, "from")
2150        where_sql = self.sql(expression, "where")
2151        returning = self.sql(expression, "returning")
2152        order = self.sql(expression, "order")
2153        limit = self.sql(expression, "limit")
2154        if self.RETURNING_END:
2155            expression_sql = f"{from_sql}{where_sql}{returning}"
2156        else:
2157            expression_sql = f"{returning}{from_sql}{where_sql}"
2158        sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}"
2159        return self.prepend_ctes(expression, sql)
2160
2161    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2162        values_as_table = values_as_table and self.VALUES_AS_TABLE
2163
2164        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2165        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2166            args = self.expressions(expression)
2167            alias = self.sql(expression, "alias")
2168            values = f"VALUES{self.seg('')}{args}"
2169            values = (
2170                f"({values})"
2171                if self.WRAP_DERIVED_VALUES
2172                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2173                else values
2174            )
2175            return f"{values} AS {alias}" if alias else values
2176
2177        # Converts `VALUES...` expression into a series of select unions.
2178        alias_node = expression.args.get("alias")
2179        column_names = alias_node and alias_node.columns
2180
2181        selects: t.List[exp.Query] = []
2182
2183        for i, tup in enumerate(expression.expressions):
2184            row = tup.expressions
2185
2186            if i == 0 and column_names:
2187                row = [
2188                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2189                ]
2190
2191            selects.append(exp.Select(expressions=row))
2192
2193        if self.pretty:
2194            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2195            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2196            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2197            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2198            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2199
2200        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2201        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2202        return f"({unions}){alias}"
2203
2204    def var_sql(self, expression: exp.Var) -> str:
2205        return self.sql(expression, "this")
2206
2207    @unsupported_args("expressions")
2208    def into_sql(self, expression: exp.Into) -> str:
2209        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2210        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2211        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2212
2213    def from_sql(self, expression: exp.From) -> str:
2214        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2215
2216    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2217        grouping_sets = self.expressions(expression, indent=False)
2218        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2219
2220    def rollup_sql(self, expression: exp.Rollup) -> str:
2221        expressions = self.expressions(expression, indent=False)
2222        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2223
2224    def cube_sql(self, expression: exp.Cube) -> str:
2225        expressions = self.expressions(expression, indent=False)
2226        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2227
2228    def group_sql(self, expression: exp.Group) -> str:
2229        group_by_all = expression.args.get("all")
2230        if group_by_all is True:
2231            modifier = " ALL"
2232        elif group_by_all is False:
2233            modifier = " DISTINCT"
2234        else:
2235            modifier = ""
2236
2237        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2238
2239        grouping_sets = self.expressions(expression, key="grouping_sets")
2240        cube = self.expressions(expression, key="cube")
2241        rollup = self.expressions(expression, key="rollup")
2242
2243        groupings = csv(
2244            self.seg(grouping_sets) if grouping_sets else "",
2245            self.seg(cube) if cube else "",
2246            self.seg(rollup) if rollup else "",
2247            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2248            sep=self.GROUPINGS_SEP,
2249        )
2250
2251        if (
2252            expression.expressions
2253            and groupings
2254            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2255        ):
2256            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2257
2258        return f"{group_by}{groupings}"
2259
2260    def having_sql(self, expression: exp.Having) -> str:
2261        this = self.indent(self.sql(expression, "this"))
2262        return f"{self.seg('HAVING')}{self.sep()}{this}"
2263
2264    def connect_sql(self, expression: exp.Connect) -> str:
2265        start = self.sql(expression, "start")
2266        start = self.seg(f"START WITH {start}") if start else ""
2267        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2268        connect = self.sql(expression, "connect")
2269        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2270        return start + connect
2271
2272    def prior_sql(self, expression: exp.Prior) -> str:
2273        return f"PRIOR {self.sql(expression, 'this')}"
2274
2275    def join_sql(self, expression: exp.Join) -> str:
2276        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2277            side = None
2278        else:
2279            side = expression.side
2280
2281        op_sql = " ".join(
2282            op
2283            for op in (
2284                expression.method,
2285                "GLOBAL" if expression.args.get("global") else None,
2286                side,
2287                expression.kind,
2288                expression.hint if self.JOIN_HINTS else None,
2289            )
2290            if op
2291        )
2292        match_cond = self.sql(expression, "match_condition")
2293        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2294        on_sql = self.sql(expression, "on")
2295        using = expression.args.get("using")
2296
2297        if not on_sql and using:
2298            on_sql = csv(*(self.sql(column) for column in using))
2299
2300        this = expression.this
2301        this_sql = self.sql(this)
2302
2303        exprs = self.expressions(expression)
2304        if exprs:
2305            this_sql = f"{this_sql},{self.seg(exprs)}"
2306
2307        if on_sql:
2308            on_sql = self.indent(on_sql, skip_first=True)
2309            space = self.seg(" " * self.pad) if self.pretty else " "
2310            if using:
2311                on_sql = f"{space}USING ({on_sql})"
2312            else:
2313                on_sql = f"{space}ON {on_sql}"
2314        elif not op_sql:
2315            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2316                return f" {this_sql}"
2317
2318            return f", {this_sql}"
2319
2320        if op_sql != "STRAIGHT_JOIN":
2321            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2322
2323        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2324        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2325
2326    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2327        args = self.expressions(expression, flat=True)
2328        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2329        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2330
2331    def lateral_op(self, expression: exp.Lateral) -> str:
2332        cross_apply = expression.args.get("cross_apply")
2333
2334        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2335        if cross_apply is True:
2336            op = "INNER JOIN "
2337        elif cross_apply is False:
2338            op = "LEFT JOIN "
2339        else:
2340            op = ""
2341
2342        return f"{op}LATERAL"
2343
2344    def lateral_sql(self, expression: exp.Lateral) -> str:
2345        this = self.sql(expression, "this")
2346
2347        if expression.args.get("view"):
2348            alias = expression.args["alias"]
2349            columns = self.expressions(alias, key="columns", flat=True)
2350            table = f" {alias.name}" if alias.name else ""
2351            columns = f" AS {columns}" if columns else ""
2352            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2353            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2354
2355        alias = self.sql(expression, "alias")
2356        alias = f" AS {alias}" if alias else ""
2357
2358        ordinality = expression.args.get("ordinality") or ""
2359        if ordinality:
2360            ordinality = f" WITH ORDINALITY{alias}"
2361            alias = ""
2362
2363        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2364
2365    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2366        this = self.sql(expression, "this")
2367
2368        args = [
2369            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2370            for e in (expression.args.get(k) for k in ("offset", "expression"))
2371            if e
2372        ]
2373
2374        args_sql = ", ".join(self.sql(e) for e in args)
2375        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2376        expressions = self.expressions(expression, flat=True)
2377        limit_options = self.sql(expression, "limit_options")
2378        expressions = f" BY {expressions}" if expressions else ""
2379
2380        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2381
2382    def offset_sql(self, expression: exp.Offset) -> str:
2383        this = self.sql(expression, "this")
2384        value = expression.expression
2385        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2386        expressions = self.expressions(expression, flat=True)
2387        expressions = f" BY {expressions}" if expressions else ""
2388        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2389
2390    def setitem_sql(self, expression: exp.SetItem) -> str:
2391        kind = self.sql(expression, "kind")
2392        kind = f"{kind} " if kind else ""
2393        this = self.sql(expression, "this")
2394        expressions = self.expressions(expression)
2395        collate = self.sql(expression, "collate")
2396        collate = f" COLLATE {collate}" if collate else ""
2397        global_ = "GLOBAL " if expression.args.get("global") else ""
2398        return f"{global_}{kind}{this}{expressions}{collate}"
2399
2400    def set_sql(self, expression: exp.Set) -> str:
2401        expressions = f" {self.expressions(expression, flat=True)}"
2402        tag = " TAG" if expression.args.get("tag") else ""
2403        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2404
2405    def pragma_sql(self, expression: exp.Pragma) -> str:
2406        return f"PRAGMA {self.sql(expression, 'this')}"
2407
2408    def lock_sql(self, expression: exp.Lock) -> str:
2409        if not self.LOCKING_READS_SUPPORTED:
2410            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2411            return ""
2412
2413        update = expression.args["update"]
2414        key = expression.args.get("key")
2415        if update:
2416            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2417        else:
2418            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2419        expressions = self.expressions(expression, flat=True)
2420        expressions = f" OF {expressions}" if expressions else ""
2421        wait = expression.args.get("wait")
2422
2423        if wait is not None:
2424            if isinstance(wait, exp.Literal):
2425                wait = f" WAIT {self.sql(wait)}"
2426            else:
2427                wait = " NOWAIT" if wait else " SKIP LOCKED"
2428
2429        return f"{lock_type}{expressions}{wait or ''}"
2430
2431    def literal_sql(self, expression: exp.Literal) -> str:
2432        text = expression.this or ""
2433        if expression.is_string:
2434            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2435        return text
2436
2437    def escape_str(self, text: str, escape_backslash: bool = True) -> str:
2438        if self.dialect.ESCAPED_SEQUENCES:
2439            to_escaped = self.dialect.ESCAPED_SEQUENCES
2440            text = "".join(
2441                to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text
2442            )
2443
2444        return self._replace_line_breaks(text).replace(
2445            self.dialect.QUOTE_END, self._escaped_quote_end
2446        )
2447
2448    def loaddata_sql(self, expression: exp.LoadData) -> str:
2449        local = " LOCAL" if expression.args.get("local") else ""
2450        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2451        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2452        this = f" INTO TABLE {self.sql(expression, 'this')}"
2453        partition = self.sql(expression, "partition")
2454        partition = f" {partition}" if partition else ""
2455        input_format = self.sql(expression, "input_format")
2456        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2457        serde = self.sql(expression, "serde")
2458        serde = f" SERDE {serde}" if serde else ""
2459        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2460
2461    def null_sql(self, *_) -> str:
2462        return "NULL"
2463
2464    def boolean_sql(self, expression: exp.Boolean) -> str:
2465        return "TRUE" if expression.this else "FALSE"
2466
2467    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2468        this = self.sql(expression, "this")
2469        this = f"{this} " if this else this
2470        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2471        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
2472
2473    def withfill_sql(self, expression: exp.WithFill) -> str:
2474        from_sql = self.sql(expression, "from")
2475        from_sql = f" FROM {from_sql}" if from_sql else ""
2476        to_sql = self.sql(expression, "to")
2477        to_sql = f" TO {to_sql}" if to_sql else ""
2478        step_sql = self.sql(expression, "step")
2479        step_sql = f" STEP {step_sql}" if step_sql else ""
2480        interpolated_values = [
2481            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2482            if isinstance(e, exp.Alias)
2483            else self.sql(e, "this")
2484            for e in expression.args.get("interpolate") or []
2485        ]
2486        interpolate = (
2487            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2488        )
2489        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2490
2491    def cluster_sql(self, expression: exp.Cluster) -> str:
2492        return self.op_expressions("CLUSTER BY", expression)
2493
2494    def distribute_sql(self, expression: exp.Distribute) -> str:
2495        return self.op_expressions("DISTRIBUTE BY", expression)
2496
2497    def sort_sql(self, expression: exp.Sort) -> str:
2498        return self.op_expressions("SORT BY", expression)
2499
2500    def ordered_sql(self, expression: exp.Ordered) -> str:
2501        desc = expression.args.get("desc")
2502        asc = not desc
2503
2504        nulls_first = expression.args.get("nulls_first")
2505        nulls_last = not nulls_first
2506        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2507        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2508        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2509
2510        this = self.sql(expression, "this")
2511
2512        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2513        nulls_sort_change = ""
2514        if nulls_first and (
2515            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2516        ):
2517            nulls_sort_change = " NULLS FIRST"
2518        elif (
2519            nulls_last
2520            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2521            and not nulls_are_last
2522        ):
2523            nulls_sort_change = " NULLS LAST"
2524
2525        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2526        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2527            window = expression.find_ancestor(exp.Window, exp.Select)
2528            if isinstance(window, exp.Window) and window.args.get("spec"):
2529                self.unsupported(
2530                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2531                )
2532                nulls_sort_change = ""
2533            elif self.NULL_ORDERING_SUPPORTED is False and (
2534                (asc and nulls_sort_change == " NULLS LAST")
2535                or (desc and nulls_sort_change == " NULLS FIRST")
2536            ):
2537                # BigQuery does not allow these ordering/nulls combinations when used under
2538                # an aggregation func or under a window containing one
2539                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2540
2541                if isinstance(ancestor, exp.Window):
2542                    ancestor = ancestor.this
2543                if isinstance(ancestor, exp.AggFunc):
2544                    self.unsupported(
2545                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2546                    )
2547                    nulls_sort_change = ""
2548            elif self.NULL_ORDERING_SUPPORTED is None:
2549                if expression.this.is_int:
2550                    self.unsupported(
2551                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2552                    )
2553                elif not isinstance(expression.this, exp.Rand):
2554                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2555                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2556                nulls_sort_change = ""
2557
2558        with_fill = self.sql(expression, "with_fill")
2559        with_fill = f" {with_fill}" if with_fill else ""
2560
2561        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2562
2563    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2564        window_frame = self.sql(expression, "window_frame")
2565        window_frame = f"{window_frame} " if window_frame else ""
2566
2567        this = self.sql(expression, "this")
2568
2569        return f"{window_frame}{this}"
2570
2571    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2572        partition = self.partition_by_sql(expression)
2573        order = self.sql(expression, "order")
2574        measures = self.expressions(expression, key="measures")
2575        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2576        rows = self.sql(expression, "rows")
2577        rows = self.seg(rows) if rows else ""
2578        after = self.sql(expression, "after")
2579        after = self.seg(after) if after else ""
2580        pattern = self.sql(expression, "pattern")
2581        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2582        definition_sqls = [
2583            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2584            for definition in expression.args.get("define", [])
2585        ]
2586        definitions = self.expressions(sqls=definition_sqls)
2587        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2588        body = "".join(
2589            (
2590                partition,
2591                order,
2592                measures,
2593                rows,
2594                after,
2595                pattern,
2596                define,
2597            )
2598        )
2599        alias = self.sql(expression, "alias")
2600        alias = f" {alias}" if alias else ""
2601        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2602
2603    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2604        limit = expression.args.get("limit")
2605
2606        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2607            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2608        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2609            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2610
2611        return csv(
2612            *sqls,
2613            *[self.sql(join) for join in expression.args.get("joins") or []],
2614            self.sql(expression, "match"),
2615            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2616            self.sql(expression, "prewhere"),
2617            self.sql(expression, "where"),
2618            self.sql(expression, "connect"),
2619            self.sql(expression, "group"),
2620            self.sql(expression, "having"),
2621            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2622            self.sql(expression, "order"),
2623            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2624            *self.after_limit_modifiers(expression),
2625            self.options_modifier(expression),
2626            self.for_modifiers(expression),
2627            sep="",
2628        )
2629
2630    def options_modifier(self, expression: exp.Expression) -> str:
2631        options = self.expressions(expression, key="options")
2632        return f" {options}" if options else ""
2633
2634    def for_modifiers(self, expression: exp.Expression) -> str:
2635        for_modifiers = self.expressions(expression, key="for")
2636        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
2637
2638    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2639        self.unsupported("Unsupported query option.")
2640        return ""
2641
2642    def offset_limit_modifiers(
2643        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2644    ) -> t.List[str]:
2645        return [
2646            self.sql(expression, "offset") if fetch else self.sql(limit),
2647            self.sql(limit) if fetch else self.sql(expression, "offset"),
2648        ]
2649
2650    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2651        locks = self.expressions(expression, key="locks", sep=" ")
2652        locks = f" {locks}" if locks else ""
2653        return [locks, self.sql(expression, "sample")]
2654
2655    def select_sql(self, expression: exp.Select) -> str:
2656        into = expression.args.get("into")
2657        if not self.SUPPORTS_SELECT_INTO and into:
2658            into.pop()
2659
2660        hint = self.sql(expression, "hint")
2661        distinct = self.sql(expression, "distinct")
2662        distinct = f" {distinct}" if distinct else ""
2663        kind = self.sql(expression, "kind")
2664
2665        limit = expression.args.get("limit")
2666        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2667            top = self.limit_sql(limit, top=True)
2668            limit.pop()
2669        else:
2670            top = ""
2671
2672        expressions = self.expressions(expression)
2673
2674        if kind:
2675            if kind in self.SELECT_KINDS:
2676                kind = f" AS {kind}"
2677            else:
2678                if kind == "STRUCT":
2679                    expressions = self.expressions(
2680                        sqls=[
2681                            self.sql(
2682                                exp.Struct(
2683                                    expressions=[
2684                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2685                                        if isinstance(e, exp.Alias)
2686                                        else e
2687                                        for e in expression.expressions
2688                                    ]
2689                                )
2690                            )
2691                        ]
2692                    )
2693                kind = ""
2694
2695        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2696        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2697
2698        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2699        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2700        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2701        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2702        sql = self.query_modifiers(
2703            expression,
2704            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2705            self.sql(expression, "into", comment=False),
2706            self.sql(expression, "from", comment=False),
2707        )
2708
2709        # If both the CTE and SELECT clauses have comments, generate the latter earlier
2710        if expression.args.get("with"):
2711            sql = self.maybe_comment(sql, expression)
2712            expression.pop_comments()
2713
2714        sql = self.prepend_ctes(expression, sql)
2715
2716        if not self.SUPPORTS_SELECT_INTO and into:
2717            if into.args.get("temporary"):
2718                table_kind = " TEMPORARY"
2719            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
2720                table_kind = " UNLOGGED"
2721            else:
2722                table_kind = ""
2723            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
2724
2725        return sql
2726
2727    def schema_sql(self, expression: exp.Schema) -> str:
2728        this = self.sql(expression, "this")
2729        sql = self.schema_columns_sql(expression)
2730        return f"{this} {sql}" if this and sql else this or sql
2731
2732    def schema_columns_sql(self, expression: exp.Schema) -> str:
2733        if expression.expressions:
2734            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2735        return ""
2736
2737    def star_sql(self, expression: exp.Star) -> str:
2738        except_ = self.expressions(expression, key="except", flat=True)
2739        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2740        replace = self.expressions(expression, key="replace", flat=True)
2741        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
2742        rename = self.expressions(expression, key="rename", flat=True)
2743        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
2744        return f"*{except_}{replace}{rename}"
2745
2746    def parameter_sql(self, expression: exp.Parameter) -> str:
2747        this = self.sql(expression, "this")
2748        return f"{self.PARAMETER_TOKEN}{this}"
2749
2750    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2751        this = self.sql(expression, "this")
2752        kind = expression.text("kind")
2753        if kind:
2754            kind = f"{kind}."
2755        return f"@@{kind}{this}"
2756
2757    def placeholder_sql(self, expression: exp.Placeholder) -> str:
2758        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
2759
2760    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2761        alias = self.sql(expression, "alias")
2762        alias = f"{sep}{alias}" if alias else ""
2763        sample = self.sql(expression, "sample")
2764        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
2765            alias = f"{sample}{alias}"
2766
2767            # Set to None so it's not generated again by self.query_modifiers()
2768            expression.set("sample", None)
2769
2770        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2771        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2772        return self.prepend_ctes(expression, sql)
2773
2774    def qualify_sql(self, expression: exp.Qualify) -> str:
2775        this = self.indent(self.sql(expression, "this"))
2776        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
2777
2778    def unnest_sql(self, expression: exp.Unnest) -> str:
2779        args = self.expressions(expression, flat=True)
2780
2781        alias = expression.args.get("alias")
2782        offset = expression.args.get("offset")
2783
2784        if self.UNNEST_WITH_ORDINALITY:
2785            if alias and isinstance(offset, exp.Expression):
2786                alias.append("columns", offset)
2787
2788        if alias and self.dialect.UNNEST_COLUMN_ONLY:
2789            columns = alias.columns
2790            alias = self.sql(columns[0]) if columns else ""
2791        else:
2792            alias = self.sql(alias)
2793
2794        alias = f" AS {alias}" if alias else alias
2795        if self.UNNEST_WITH_ORDINALITY:
2796            suffix = f" WITH ORDINALITY{alias}" if offset else alias
2797        else:
2798            if isinstance(offset, exp.Expression):
2799                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
2800            elif offset:
2801                suffix = f"{alias} WITH OFFSET"
2802            else:
2803                suffix = alias
2804
2805        return f"UNNEST({args}){suffix}"
2806
2807    def prewhere_sql(self, expression: exp.PreWhere) -> str:
2808        return ""
2809
2810    def where_sql(self, expression: exp.Where) -> str:
2811        this = self.indent(self.sql(expression, "this"))
2812        return f"{self.seg('WHERE')}{self.sep()}{this}"
2813
2814    def window_sql(self, expression: exp.Window) -> str:
2815        this = self.sql(expression, "this")
2816        partition = self.partition_by_sql(expression)
2817        order = expression.args.get("order")
2818        order = self.order_sql(order, flat=True) if order else ""
2819        spec = self.sql(expression, "spec")
2820        alias = self.sql(expression, "alias")
2821        over = self.sql(expression, "over") or "OVER"
2822
2823        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
2824
2825        first = expression.args.get("first")
2826        if first is None:
2827            first = ""
2828        else:
2829            first = "FIRST" if first else "LAST"
2830
2831        if not partition and not order and not spec and alias:
2832            return f"{this} {alias}"
2833
2834        args = self.format_args(
2835            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
2836        )
2837        return f"{this} ({args})"
2838
2839    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
2840        partition = self.expressions(expression, key="partition_by", flat=True)
2841        return f"PARTITION BY {partition}" if partition else ""
2842
2843    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
2844        kind = self.sql(expression, "kind")
2845        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
2846        end = (
2847            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
2848            or "CURRENT ROW"
2849        )
2850
2851        window_spec = f"{kind} BETWEEN {start} AND {end}"
2852
2853        exclude = self.sql(expression, "exclude")
2854        if exclude:
2855            if self.SUPPORTS_WINDOW_EXCLUDE:
2856                window_spec += f" EXCLUDE {exclude}"
2857            else:
2858                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
2859
2860        return window_spec
2861
2862    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
2863        this = self.sql(expression, "this")
2864        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
2865        return f"{this} WITHIN GROUP ({expression_sql})"
2866
2867    def between_sql(self, expression: exp.Between) -> str:
2868        this = self.sql(expression, "this")
2869        low = self.sql(expression, "low")
2870        high = self.sql(expression, "high")
2871        symmetric = expression.args.get("symmetric")
2872
2873        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
2874            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
2875
2876        flag = (
2877            " SYMMETRIC"
2878            if symmetric
2879            else " ASYMMETRIC"
2880            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
2881            else ""  # silently drop ASYMMETRIC – semantics identical
2882        )
2883        return f"{this} BETWEEN{flag} {low} AND {high}"
2884
2885    def bracket_offset_expressions(
2886        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
2887    ) -> t.List[exp.Expression]:
2888        return apply_index_offset(
2889            expression.this,
2890            expression.expressions,
2891            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
2892            dialect=self.dialect,
2893        )
2894
2895    def bracket_sql(self, expression: exp.Bracket) -> str:
2896        expressions = self.bracket_offset_expressions(expression)
2897        expressions_sql = ", ".join(self.sql(e) for e in expressions)
2898        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
2899
2900    def all_sql(self, expression: exp.All) -> str:
2901        return f"ALL {self.wrap(expression)}"
2902
2903    def any_sql(self, expression: exp.Any) -> str:
2904        this = self.sql(expression, "this")
2905        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
2906            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
2907                this = self.wrap(this)
2908            return f"ANY{this}"
2909        return f"ANY {this}"
2910
2911    def exists_sql(self, expression: exp.Exists) -> str:
2912        return f"EXISTS{self.wrap(expression)}"
2913
2914    def case_sql(self, expression: exp.Case) -> str:
2915        this = self.sql(expression, "this")
2916        statements = [f"CASE {this}" if this else "CASE"]
2917
2918        for e in expression.args["ifs"]:
2919            statements.append(f"WHEN {self.sql(e, 'this')}")
2920            statements.append(f"THEN {self.sql(e, 'true')}")
2921
2922        default = self.sql(expression, "default")
2923
2924        if default:
2925            statements.append(f"ELSE {default}")
2926
2927        statements.append("END")
2928
2929        if self.pretty and self.too_wide(statements):
2930            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
2931
2932        return " ".join(statements)
2933
2934    def constraint_sql(self, expression: exp.Constraint) -> str:
2935        this = self.sql(expression, "this")
2936        expressions = self.expressions(expression, flat=True)
2937        return f"CONSTRAINT {this} {expressions}"
2938
2939    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
2940        order = expression.args.get("order")
2941        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
2942        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
2943
2944    def extract_sql(self, expression: exp.Extract) -> str:
2945        from sqlglot.dialects.dialect import map_date_part
2946
2947        this = (
2948            map_date_part(expression.this, self.dialect)
2949            if self.NORMALIZE_EXTRACT_DATE_PARTS
2950            else expression.this
2951        )
2952        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
2953        expression_sql = self.sql(expression, "expression")
2954
2955        return f"EXTRACT({this_sql} FROM {expression_sql})"
2956
2957    def trim_sql(self, expression: exp.Trim) -> str:
2958        trim_type = self.sql(expression, "position")
2959
2960        if trim_type == "LEADING":
2961            func_name = "LTRIM"
2962        elif trim_type == "TRAILING":
2963            func_name = "RTRIM"
2964        else:
2965            func_name = "TRIM"
2966
2967        return self.func(func_name, expression.this, expression.expression)
2968
2969    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
2970        args = expression.expressions
2971        if isinstance(expression, exp.ConcatWs):
2972            args = args[1:]  # Skip the delimiter
2973
2974        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
2975            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
2976
2977        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
2978            args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args]
2979
2980        return args
2981
2982    def concat_sql(self, expression: exp.Concat) -> str:
2983        expressions = self.convert_concat_args(expression)
2984
2985        # Some dialects don't allow a single-argument CONCAT call
2986        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
2987            return self.sql(expressions[0])
2988
2989        return self.func("CONCAT", *expressions)
2990
2991    def concatws_sql(self, expression: exp.ConcatWs) -> str:
2992        return self.func(
2993            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
2994        )
2995
2996    def check_sql(self, expression: exp.Check) -> str:
2997        this = self.sql(expression, key="this")
2998        return f"CHECK ({this})"
2999
3000    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3001        expressions = self.expressions(expression, flat=True)
3002        expressions = f" ({expressions})" if expressions else ""
3003        reference = self.sql(expression, "reference")
3004        reference = f" {reference}" if reference else ""
3005        delete = self.sql(expression, "delete")
3006        delete = f" ON DELETE {delete}" if delete else ""
3007        update = self.sql(expression, "update")
3008        update = f" ON UPDATE {update}" if update else ""
3009        options = self.expressions(expression, key="options", flat=True, sep=" ")
3010        options = f" {options}" if options else ""
3011        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3012
3013    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3014        expressions = self.expressions(expression, flat=True)
3015        include = self.sql(expression, "include")
3016        options = self.expressions(expression, key="options", flat=True, sep=" ")
3017        options = f" {options}" if options else ""
3018        return f"PRIMARY KEY ({expressions}){include}{options}"
3019
3020    def if_sql(self, expression: exp.If) -> str:
3021        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3022
3023    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3024        modifier = expression.args.get("modifier")
3025        modifier = f" {modifier}" if modifier else ""
3026        return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3027
3028    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3029        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3030
3031    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3032        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3033
3034        if expression.args.get("escape"):
3035            path = self.escape_str(path)
3036
3037        if self.QUOTE_JSON_PATH:
3038            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3039
3040        return path
3041
3042    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3043        if isinstance(expression, exp.JSONPathPart):
3044            transform = self.TRANSFORMS.get(expression.__class__)
3045            if not callable(transform):
3046                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3047                return ""
3048
3049            return transform(self, expression)
3050
3051        if isinstance(expression, int):
3052            return str(expression)
3053
3054        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3055            escaped = expression.replace("'", "\\'")
3056            escaped = f"\\'{expression}\\'"
3057        else:
3058            escaped = expression.replace('"', '\\"')
3059            escaped = f'"{escaped}"'
3060
3061        return escaped
3062
3063    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3064        return f"{self.sql(expression, 'this')} FORMAT JSON"
3065
3066    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3067        # Output the Teradata column FORMAT override.
3068        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3069        this = self.sql(expression, "this")
3070        fmt = self.sql(expression, "format")
3071        return f"{this} (FORMAT {fmt})"
3072
3073    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3074        null_handling = expression.args.get("null_handling")
3075        null_handling = f" {null_handling}" if null_handling else ""
3076
3077        unique_keys = expression.args.get("unique_keys")
3078        if unique_keys is not None:
3079            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3080        else:
3081            unique_keys = ""
3082
3083        return_type = self.sql(expression, "return_type")
3084        return_type = f" RETURNING {return_type}" if return_type else ""
3085        encoding = self.sql(expression, "encoding")
3086        encoding = f" ENCODING {encoding}" if encoding else ""
3087
3088        return self.func(
3089            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3090            *expression.expressions,
3091            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3092        )
3093
3094    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3095        return self.jsonobject_sql(expression)
3096
3097    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3098        null_handling = expression.args.get("null_handling")
3099        null_handling = f" {null_handling}" if null_handling else ""
3100        return_type = self.sql(expression, "return_type")
3101        return_type = f" RETURNING {return_type}" if return_type else ""
3102        strict = " STRICT" if expression.args.get("strict") else ""
3103        return self.func(
3104            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3105        )
3106
3107    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3108        this = self.sql(expression, "this")
3109        order = self.sql(expression, "order")
3110        null_handling = expression.args.get("null_handling")
3111        null_handling = f" {null_handling}" if null_handling else ""
3112        return_type = self.sql(expression, "return_type")
3113        return_type = f" RETURNING {return_type}" if return_type else ""
3114        strict = " STRICT" if expression.args.get("strict") else ""
3115        return self.func(
3116            "JSON_ARRAYAGG",
3117            this,
3118            suffix=f"{order}{null_handling}{return_type}{strict})",
3119        )
3120
3121    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3122        path = self.sql(expression, "path")
3123        path = f" PATH {path}" if path else ""
3124        nested_schema = self.sql(expression, "nested_schema")
3125
3126        if nested_schema:
3127            return f"NESTED{path} {nested_schema}"
3128
3129        this = self.sql(expression, "this")
3130        kind = self.sql(expression, "kind")
3131        kind = f" {kind}" if kind else ""
3132        return f"{this}{kind}{path}"
3133
3134    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3135        return self.func("COLUMNS", *expression.expressions)
3136
3137    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3138        this = self.sql(expression, "this")
3139        path = self.sql(expression, "path")
3140        path = f", {path}" if path else ""
3141        error_handling = expression.args.get("error_handling")
3142        error_handling = f" {error_handling}" if error_handling else ""
3143        empty_handling = expression.args.get("empty_handling")
3144        empty_handling = f" {empty_handling}" if empty_handling else ""
3145        schema = self.sql(expression, "schema")
3146        return self.func(
3147            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3148        )
3149
3150    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3151        this = self.sql(expression, "this")
3152        kind = self.sql(expression, "kind")
3153        path = self.sql(expression, "path")
3154        path = f" {path}" if path else ""
3155        as_json = " AS JSON" if expression.args.get("as_json") else ""
3156        return f"{this} {kind}{path}{as_json}"
3157
3158    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3159        this = self.sql(expression, "this")
3160        path = self.sql(expression, "path")
3161        path = f", {path}" if path else ""
3162        expressions = self.expressions(expression)
3163        with_ = (
3164            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3165            if expressions
3166            else ""
3167        )
3168        return f"OPENJSON({this}{path}){with_}"
3169
3170    def in_sql(self, expression: exp.In) -> str:
3171        query = expression.args.get("query")
3172        unnest = expression.args.get("unnest")
3173        field = expression.args.get("field")
3174        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3175
3176        if query:
3177            in_sql = self.sql(query)
3178        elif unnest:
3179            in_sql = self.in_unnest_op(unnest)
3180        elif field:
3181            in_sql = self.sql(field)
3182        else:
3183            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3184
3185        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3186
3187    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3188        return f"(SELECT {self.sql(unnest)})"
3189
3190    def interval_sql(self, expression: exp.Interval) -> str:
3191        unit = self.sql(expression, "unit")
3192        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3193            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3194        unit = f" {unit}" if unit else ""
3195
3196        if self.SINGLE_STRING_INTERVAL:
3197            this = expression.this.name if expression.this else ""
3198            return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}"
3199
3200        this = self.sql(expression, "this")
3201        if this:
3202            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3203            this = f" {this}" if unwrapped else f" ({this})"
3204
3205        return f"INTERVAL{this}{unit}"
3206
3207    def return_sql(self, expression: exp.Return) -> str:
3208        return f"RETURN {self.sql(expression, 'this')}"
3209
3210    def reference_sql(self, expression: exp.Reference) -> str:
3211        this = self.sql(expression, "this")
3212        expressions = self.expressions(expression, flat=True)
3213        expressions = f"({expressions})" if expressions else ""
3214        options = self.expressions(expression, key="options", flat=True, sep=" ")
3215        options = f" {options}" if options else ""
3216        return f"REFERENCES {this}{expressions}{options}"
3217
3218    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3219        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3220        parent = expression.parent
3221        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3222        return self.func(
3223            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3224        )
3225
3226    def paren_sql(self, expression: exp.Paren) -> str:
3227        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3228        return f"({sql}{self.seg(')', sep='')}"
3229
3230    def neg_sql(self, expression: exp.Neg) -> str:
3231        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3232        this_sql = self.sql(expression, "this")
3233        sep = " " if this_sql[0] == "-" else ""
3234        return f"-{sep}{this_sql}"
3235
3236    def not_sql(self, expression: exp.Not) -> str:
3237        return f"NOT {self.sql(expression, 'this')}"
3238
3239    def alias_sql(self, expression: exp.Alias) -> str:
3240        alias = self.sql(expression, "alias")
3241        alias = f" AS {alias}" if alias else ""
3242        return f"{self.sql(expression, 'this')}{alias}"
3243
3244    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3245        alias = expression.args["alias"]
3246
3247        parent = expression.parent
3248        pivot = parent and parent.parent
3249
3250        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3251            identifier_alias = isinstance(alias, exp.Identifier)
3252            literal_alias = isinstance(alias, exp.Literal)
3253
3254            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3255                alias.replace(exp.Literal.string(alias.output_name))
3256            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3257                alias.replace(exp.to_identifier(alias.output_name))
3258
3259        return self.alias_sql(expression)
3260
3261    def aliases_sql(self, expression: exp.Aliases) -> str:
3262        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3263
3264    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3265        this = self.sql(expression, "this")
3266        index = self.sql(expression, "expression")
3267        return f"{this} AT {index}"
3268
3269    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3270        this = self.sql(expression, "this")
3271        zone = self.sql(expression, "zone")
3272        return f"{this} AT TIME ZONE {zone}"
3273
3274    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3275        this = self.sql(expression, "this")
3276        zone = self.sql(expression, "zone")
3277        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3278
3279    def add_sql(self, expression: exp.Add) -> str:
3280        return self.binary(expression, "+")
3281
3282    def and_sql(
3283        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3284    ) -> str:
3285        return self.connector_sql(expression, "AND", stack)
3286
3287    def or_sql(
3288        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3289    ) -> str:
3290        return self.connector_sql(expression, "OR", stack)
3291
3292    def xor_sql(
3293        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3294    ) -> str:
3295        return self.connector_sql(expression, "XOR", stack)
3296
3297    def connector_sql(
3298        self,
3299        expression: exp.Connector,
3300        op: str,
3301        stack: t.Optional[t.List[str | exp.Expression]] = None,
3302    ) -> str:
3303        if stack is not None:
3304            if expression.expressions:
3305                stack.append(self.expressions(expression, sep=f" {op} "))
3306            else:
3307                stack.append(expression.right)
3308                if expression.comments and self.comments:
3309                    for comment in expression.comments:
3310                        if comment:
3311                            op += f" /*{self.sanitize_comment(comment)}*/"
3312                stack.extend((op, expression.left))
3313            return op
3314
3315        stack = [expression]
3316        sqls: t.List[str] = []
3317        ops = set()
3318
3319        while stack:
3320            node = stack.pop()
3321            if isinstance(node, exp.Connector):
3322                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3323            else:
3324                sql = self.sql(node)
3325                if sqls and sqls[-1] in ops:
3326                    sqls[-1] += f" {sql}"
3327                else:
3328                    sqls.append(sql)
3329
3330        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3331        return sep.join(sqls)
3332
3333    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3334        return self.binary(expression, "&")
3335
3336    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3337        return self.binary(expression, "<<")
3338
3339    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3340        return f"~{self.sql(expression, 'this')}"
3341
3342    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3343        return self.binary(expression, "|")
3344
3345    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3346        return self.binary(expression, ">>")
3347
3348    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3349        return self.binary(expression, "^")
3350
3351    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3352        format_sql = self.sql(expression, "format")
3353        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3354        to_sql = self.sql(expression, "to")
3355        to_sql = f" {to_sql}" if to_sql else ""
3356        action = self.sql(expression, "action")
3357        action = f" {action}" if action else ""
3358        default = self.sql(expression, "default")
3359        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3360        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3361
3362    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3363        zone = self.sql(expression, "this")
3364        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3365
3366    def collate_sql(self, expression: exp.Collate) -> str:
3367        if self.COLLATE_IS_FUNC:
3368            return self.function_fallback_sql(expression)
3369        return self.binary(expression, "COLLATE")
3370
3371    def command_sql(self, expression: exp.Command) -> str:
3372        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3373
3374    def comment_sql(self, expression: exp.Comment) -> str:
3375        this = self.sql(expression, "this")
3376        kind = expression.args["kind"]
3377        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3378        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3379        expression_sql = self.sql(expression, "expression")
3380        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3381
3382    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3383        this = self.sql(expression, "this")
3384        delete = " DELETE" if expression.args.get("delete") else ""
3385        recompress = self.sql(expression, "recompress")
3386        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3387        to_disk = self.sql(expression, "to_disk")
3388        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3389        to_volume = self.sql(expression, "to_volume")
3390        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3391        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3392
3393    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3394        where = self.sql(expression, "where")
3395        group = self.sql(expression, "group")
3396        aggregates = self.expressions(expression, key="aggregates")
3397        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3398
3399        if not (where or group or aggregates) and len(expression.expressions) == 1:
3400            return f"TTL {self.expressions(expression, flat=True)}"
3401
3402        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3403
3404    def transaction_sql(self, expression: exp.Transaction) -> str:
3405        return "BEGIN"
3406
3407    def commit_sql(self, expression: exp.Commit) -> str:
3408        chain = expression.args.get("chain")
3409        if chain is not None:
3410            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3411
3412        return f"COMMIT{chain or ''}"
3413
3414    def rollback_sql(self, expression: exp.Rollback) -> str:
3415        savepoint = expression.args.get("savepoint")
3416        savepoint = f" TO {savepoint}" if savepoint else ""
3417        return f"ROLLBACK{savepoint}"
3418
3419    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3420        this = self.sql(expression, "this")
3421
3422        dtype = self.sql(expression, "dtype")
3423        if dtype:
3424            collate = self.sql(expression, "collate")
3425            collate = f" COLLATE {collate}" if collate else ""
3426            using = self.sql(expression, "using")
3427            using = f" USING {using}" if using else ""
3428            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3429            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3430
3431        default = self.sql(expression, "default")
3432        if default:
3433            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3434
3435        comment = self.sql(expression, "comment")
3436        if comment:
3437            return f"ALTER COLUMN {this} COMMENT {comment}"
3438
3439        visible = expression.args.get("visible")
3440        if visible:
3441            return f"ALTER COLUMN {this} SET {visible}"
3442
3443        allow_null = expression.args.get("allow_null")
3444        drop = expression.args.get("drop")
3445
3446        if not drop and not allow_null:
3447            self.unsupported("Unsupported ALTER COLUMN syntax")
3448
3449        if allow_null is not None:
3450            keyword = "DROP" if drop else "SET"
3451            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3452
3453        return f"ALTER COLUMN {this} DROP DEFAULT"
3454
3455    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3456        this = self.sql(expression, "this")
3457
3458        visible = expression.args.get("visible")
3459        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3460
3461        return f"ALTER INDEX {this} {visible_sql}"
3462
3463    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3464        this = self.sql(expression, "this")
3465        if not isinstance(expression.this, exp.Var):
3466            this = f"KEY DISTKEY {this}"
3467        return f"ALTER DISTSTYLE {this}"
3468
3469    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3470        compound = " COMPOUND" if expression.args.get("compound") else ""
3471        this = self.sql(expression, "this")
3472        expressions = self.expressions(expression, flat=True)
3473        expressions = f"({expressions})" if expressions else ""
3474        return f"ALTER{compound} SORTKEY {this or expressions}"
3475
3476    def alterrename_sql(self, expression: exp.AlterRename) -> str:
3477        if not self.RENAME_TABLE_WITH_DB:
3478            # Remove db from tables
3479            expression = expression.transform(
3480                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3481            ).assert_is(exp.AlterRename)
3482        this = self.sql(expression, "this")
3483        return f"RENAME TO {this}"
3484
3485    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3486        exists = " IF EXISTS" if expression.args.get("exists") else ""
3487        old_column = self.sql(expression, "this")
3488        new_column = self.sql(expression, "to")
3489        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3490
3491    def alterset_sql(self, expression: exp.AlterSet) -> str:
3492        exprs = self.expressions(expression, flat=True)
3493        if self.ALTER_SET_WRAPPED:
3494            exprs = f"({exprs})"
3495
3496        return f"SET {exprs}"
3497
3498    def alter_sql(self, expression: exp.Alter) -> str:
3499        actions = expression.args["actions"]
3500
3501        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3502            actions[0], exp.ColumnDef
3503        ):
3504            actions_sql = self.expressions(expression, key="actions", flat=True)
3505            actions_sql = f"ADD {actions_sql}"
3506        else:
3507            actions_list = []
3508            for action in actions:
3509                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3510                    action_sql = self.add_column_sql(action)
3511                else:
3512                    action_sql = self.sql(action)
3513                    if isinstance(action, exp.Query):
3514                        action_sql = f"AS {action_sql}"
3515
3516                actions_list.append(action_sql)
3517
3518            actions_sql = self.format_args(*actions_list).lstrip("\n")
3519
3520        exists = " IF EXISTS" if expression.args.get("exists") else ""
3521        on_cluster = self.sql(expression, "cluster")
3522        on_cluster = f" {on_cluster}" if on_cluster else ""
3523        only = " ONLY" if expression.args.get("only") else ""
3524        options = self.expressions(expression, key="options")
3525        options = f", {options}" if options else ""
3526        kind = self.sql(expression, "kind")
3527        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3528
3529        return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
3530
3531    def add_column_sql(self, expression: exp.Expression) -> str:
3532        sql = self.sql(expression)
3533        if isinstance(expression, exp.Schema):
3534            column_text = " COLUMNS"
3535        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3536            column_text = " COLUMN"
3537        else:
3538            column_text = ""
3539
3540        return f"ADD{column_text} {sql}"
3541
3542    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3543        expressions = self.expressions(expression)
3544        exists = " IF EXISTS " if expression.args.get("exists") else " "
3545        return f"DROP{exists}{expressions}"
3546
3547    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3548        return f"ADD {self.expressions(expression, indent=False)}"
3549
3550    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3551        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3552        location = self.sql(expression, "location")
3553        location = f" {location}" if location else ""
3554        return f"ADD {exists}{self.sql(expression.this)}{location}"
3555
3556    def distinct_sql(self, expression: exp.Distinct) -> str:
3557        this = self.expressions(expression, flat=True)
3558
3559        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3560            case = exp.case()
3561            for arg in expression.expressions:
3562                case = case.when(arg.is_(exp.null()), exp.null())
3563            this = self.sql(case.else_(f"({this})"))
3564
3565        this = f" {this}" if this else ""
3566
3567        on = self.sql(expression, "on")
3568        on = f" ON {on}" if on else ""
3569        return f"DISTINCT{this}{on}"
3570
3571    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3572        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
3573
3574    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3575        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
3576
3577    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3578        this_sql = self.sql(expression, "this")
3579        expression_sql = self.sql(expression, "expression")
3580        kind = "MAX" if expression.args.get("max") else "MIN"
3581        return f"{this_sql} HAVING {kind} {expression_sql}"
3582
3583    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3584        return self.sql(
3585            exp.Cast(
3586                this=exp.Div(this=expression.this, expression=expression.expression),
3587                to=exp.DataType(this=exp.DataType.Type.INT),
3588            )
3589        )
3590
3591    def dpipe_sql(self, expression: exp.DPipe) -> str:
3592        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3593            return self.func(
3594                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3595            )
3596        return self.binary(expression, "||")
3597
3598    def div_sql(self, expression: exp.Div) -> str:
3599        l, r = expression.left, expression.right
3600
3601        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3602            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3603
3604        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3605            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3606                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3607
3608        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3609            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3610                return self.sql(
3611                    exp.cast(
3612                        l / r,
3613                        to=exp.DataType.Type.BIGINT,
3614                    )
3615                )
3616
3617        return self.binary(expression, "/")
3618
3619    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3620        n = exp._wrap(expression.this, exp.Binary)
3621        d = exp._wrap(expression.expression, exp.Binary)
3622        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
3623
3624    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3625        return self.binary(expression, "OVERLAPS")
3626
3627    def distance_sql(self, expression: exp.Distance) -> str:
3628        return self.binary(expression, "<->")
3629
3630    def dot_sql(self, expression: exp.Dot) -> str:
3631        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
3632
3633    def eq_sql(self, expression: exp.EQ) -> str:
3634        return self.binary(expression, "=")
3635
3636    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3637        return self.binary(expression, ":=")
3638
3639    def escape_sql(self, expression: exp.Escape) -> str:
3640        return self.binary(expression, "ESCAPE")
3641
3642    def glob_sql(self, expression: exp.Glob) -> str:
3643        return self.binary(expression, "GLOB")
3644
3645    def gt_sql(self, expression: exp.GT) -> str:
3646        return self.binary(expression, ">")
3647
3648    def gte_sql(self, expression: exp.GTE) -> str:
3649        return self.binary(expression, ">=")
3650
3651    def ilike_sql(self, expression: exp.ILike) -> str:
3652        return self.binary(expression, "ILIKE")
3653
3654    def ilikeany_sql(self, expression: exp.ILikeAny) -> str:
3655        return self.binary(expression, "ILIKE ANY")
3656
3657    def is_sql(self, expression: exp.Is) -> str:
3658        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
3659            return self.sql(
3660                expression.this if expression.expression.this else exp.not_(expression.this)
3661            )
3662        return self.binary(expression, "IS")
3663
3664    def like_sql(self, expression: exp.Like) -> str:
3665        return self.binary(expression, "LIKE")
3666
3667    def likeany_sql(self, expression: exp.LikeAny) -> str:
3668        return self.binary(expression, "LIKE ANY")
3669
3670    def similarto_sql(self, expression: exp.SimilarTo) -> str:
3671        return self.binary(expression, "SIMILAR TO")
3672
3673    def lt_sql(self, expression: exp.LT) -> str:
3674        return self.binary(expression, "<")
3675
3676    def lte_sql(self, expression: exp.LTE) -> str:
3677        return self.binary(expression, "<=")
3678
3679    def mod_sql(self, expression: exp.Mod) -> str:
3680        return self.binary(expression, "%")
3681
3682    def mul_sql(self, expression: exp.Mul) -> str:
3683        return self.binary(expression, "*")
3684
3685    def neq_sql(self, expression: exp.NEQ) -> str:
3686        return self.binary(expression, "<>")
3687
3688    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
3689        return self.binary(expression, "IS NOT DISTINCT FROM")
3690
3691    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3692        return self.binary(expression, "IS DISTINCT FROM")
3693
3694    def slice_sql(self, expression: exp.Slice) -> str:
3695        return self.binary(expression, ":")
3696
3697    def sub_sql(self, expression: exp.Sub) -> str:
3698        return self.binary(expression, "-")
3699
3700    def trycast_sql(self, expression: exp.TryCast) -> str:
3701        return self.cast_sql(expression, safe_prefix="TRY_")
3702
3703    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
3704        return self.cast_sql(expression)
3705
3706    def try_sql(self, expression: exp.Try) -> str:
3707        if not self.TRY_SUPPORTED:
3708            self.unsupported("Unsupported TRY function")
3709            return self.sql(expression, "this")
3710
3711        return self.func("TRY", expression.this)
3712
3713    def log_sql(self, expression: exp.Log) -> str:
3714        this = expression.this
3715        expr = expression.expression
3716
3717        if self.dialect.LOG_BASE_FIRST is False:
3718            this, expr = expr, this
3719        elif self.dialect.LOG_BASE_FIRST is None and expr:
3720            if this.name in ("2", "10"):
3721                return self.func(f"LOG{this.name}", expr)
3722
3723            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
3724
3725        return self.func("LOG", this, expr)
3726
3727    def use_sql(self, expression: exp.Use) -> str:
3728        kind = self.sql(expression, "kind")
3729        kind = f" {kind}" if kind else ""
3730        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
3731        this = f" {this}" if this else ""
3732        return f"USE{kind}{this}"
3733
3734    def binary(self, expression: exp.Binary, op: str) -> str:
3735        sqls: t.List[str] = []
3736        stack: t.List[t.Union[str, exp.Expression]] = [expression]
3737        binary_type = type(expression)
3738
3739        while stack:
3740            node = stack.pop()
3741
3742            if type(node) is binary_type:
3743                op_func = node.args.get("operator")
3744                if op_func:
3745                    op = f"OPERATOR({self.sql(op_func)})"
3746
3747                stack.append(node.right)
3748                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
3749                stack.append(node.left)
3750            else:
3751                sqls.append(self.sql(node))
3752
3753        return "".join(sqls)
3754
3755    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
3756        to_clause = self.sql(expression, "to")
3757        if to_clause:
3758            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
3759
3760        return self.function_fallback_sql(expression)
3761
3762    def function_fallback_sql(self, expression: exp.Func) -> str:
3763        args = []
3764
3765        for key in expression.arg_types:
3766            arg_value = expression.args.get(key)
3767
3768            if isinstance(arg_value, list):
3769                for value in arg_value:
3770                    args.append(value)
3771            elif arg_value is not None:
3772                args.append(arg_value)
3773
3774        if self.dialect.PRESERVE_ORIGINAL_NAMES:
3775            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
3776        else:
3777            name = expression.sql_name()
3778
3779        return self.func(name, *args)
3780
3781    def func(
3782        self,
3783        name: str,
3784        *args: t.Optional[exp.Expression | str],
3785        prefix: str = "(",
3786        suffix: str = ")",
3787        normalize: bool = True,
3788    ) -> str:
3789        name = self.normalize_func(name) if normalize else name
3790        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
3791
3792    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
3793        arg_sqls = tuple(
3794            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
3795        )
3796        if self.pretty and self.too_wide(arg_sqls):
3797            return self.indent(
3798                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
3799            )
3800        return sep.join(arg_sqls)
3801
3802    def too_wide(self, args: t.Iterable) -> bool:
3803        return sum(len(arg) for arg in args) > self.max_text_width
3804
3805    def format_time(
3806        self,
3807        expression: exp.Expression,
3808        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
3809        inverse_time_trie: t.Optional[t.Dict] = None,
3810    ) -> t.Optional[str]:
3811        return format_time(
3812            self.sql(expression, "format"),
3813            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
3814            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
3815        )
3816
3817    def expressions(
3818        self,
3819        expression: t.Optional[exp.Expression] = None,
3820        key: t.Optional[str] = None,
3821        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
3822        flat: bool = False,
3823        indent: bool = True,
3824        skip_first: bool = False,
3825        skip_last: bool = False,
3826        sep: str = ", ",
3827        prefix: str = "",
3828        dynamic: bool = False,
3829        new_line: bool = False,
3830    ) -> str:
3831        expressions = expression.args.get(key or "expressions") if expression else sqls
3832
3833        if not expressions:
3834            return ""
3835
3836        if flat:
3837            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
3838
3839        num_sqls = len(expressions)
3840        result_sqls = []
3841
3842        for i, e in enumerate(expressions):
3843            sql = self.sql(e, comment=False)
3844            if not sql:
3845                continue
3846
3847            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
3848
3849            if self.pretty:
3850                if self.leading_comma:
3851                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
3852                else:
3853                    result_sqls.append(
3854                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
3855                    )
3856            else:
3857                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
3858
3859        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
3860            if new_line:
3861                result_sqls.insert(0, "")
3862                result_sqls.append("")
3863            result_sql = "\n".join(s.rstrip() for s in result_sqls)
3864        else:
3865            result_sql = "".join(result_sqls)
3866
3867        return (
3868            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
3869            if indent
3870            else result_sql
3871        )
3872
3873    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
3874        flat = flat or isinstance(expression.parent, exp.Properties)
3875        expressions_sql = self.expressions(expression, flat=flat)
3876        if flat:
3877            return f"{op} {expressions_sql}"
3878        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3879
3880    def naked_property(self, expression: exp.Property) -> str:
3881        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
3882        if not property_name:
3883            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
3884        return f"{property_name} {self.sql(expression, 'this')}"
3885
3886    def tag_sql(self, expression: exp.Tag) -> str:
3887        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
3888
3889    def token_sql(self, token_type: TokenType) -> str:
3890        return self.TOKEN_MAPPING.get(token_type, token_type.name)
3891
3892    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
3893        this = self.sql(expression, "this")
3894        expressions = self.no_identify(self.expressions, expression)
3895        expressions = (
3896            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
3897        )
3898        return f"{this}{expressions}" if expressions.strip() != "" else this
3899
3900    def joinhint_sql(self, expression: exp.JoinHint) -> str:
3901        this = self.sql(expression, "this")
3902        expressions = self.expressions(expression, flat=True)
3903        return f"{this}({expressions})"
3904
3905    def kwarg_sql(self, expression: exp.Kwarg) -> str:
3906        return self.binary(expression, "=>")
3907
3908    def when_sql(self, expression: exp.When) -> str:
3909        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
3910        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
3911        condition = self.sql(expression, "condition")
3912        condition = f" AND {condition}" if condition else ""
3913
3914        then_expression = expression.args.get("then")
3915        if isinstance(then_expression, exp.Insert):
3916            this = self.sql(then_expression, "this")
3917            this = f"INSERT {this}" if this else "INSERT"
3918            then = self.sql(then_expression, "expression")
3919            then = f"{this} VALUES {then}" if then else this
3920        elif isinstance(then_expression, exp.Update):
3921            if isinstance(then_expression.args.get("expressions"), exp.Star):
3922                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
3923            else:
3924                then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}"
3925        else:
3926            then = self.sql(then_expression)
3927        return f"WHEN {matched}{source}{condition} THEN {then}"
3928
3929    def whens_sql(self, expression: exp.Whens) -> str:
3930        return self.expressions(expression, sep=" ", indent=False)
3931
3932    def merge_sql(self, expression: exp.Merge) -> str:
3933        table = expression.this
3934        table_alias = ""
3935
3936        hints = table.args.get("hints")
3937        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
3938            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
3939            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
3940
3941        this = self.sql(table)
3942        using = f"USING {self.sql(expression, 'using')}"
3943        on = f"ON {self.sql(expression, 'on')}"
3944        whens = self.sql(expression, "whens")
3945
3946        returning = self.sql(expression, "returning")
3947        if returning:
3948            whens = f"{whens}{returning}"
3949
3950        sep = self.sep()
3951
3952        return self.prepend_ctes(
3953            expression,
3954            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
3955        )
3956
3957    @unsupported_args("format")
3958    def tochar_sql(self, expression: exp.ToChar) -> str:
3959        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
3960
3961    def tonumber_sql(self, expression: exp.ToNumber) -> str:
3962        if not self.SUPPORTS_TO_NUMBER:
3963            self.unsupported("Unsupported TO_NUMBER function")
3964            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3965
3966        fmt = expression.args.get("format")
3967        if not fmt:
3968            self.unsupported("Conversion format is required for TO_NUMBER")
3969            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3970
3971        return self.func("TO_NUMBER", expression.this, fmt)
3972
3973    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
3974        this = self.sql(expression, "this")
3975        kind = self.sql(expression, "kind")
3976        settings_sql = self.expressions(expression, key="settings", sep=" ")
3977        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
3978        return f"{this}({kind}{args})"
3979
3980    def dictrange_sql(self, expression: exp.DictRange) -> str:
3981        this = self.sql(expression, "this")
3982        max = self.sql(expression, "max")
3983        min = self.sql(expression, "min")
3984        return f"{this}(MIN {min} MAX {max})"
3985
3986    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
3987        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
3988
3989    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
3990        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
3991
3992    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
3993    def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str:
3994        return f"UNIQUE KEY ({self.expressions(expression, flat=True)})"
3995
3996    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
3997    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
3998        expressions = self.expressions(expression, flat=True)
3999        expressions = f" {self.wrap(expressions)}" if expressions else ""
4000        buckets = self.sql(expression, "buckets")
4001        kind = self.sql(expression, "kind")
4002        buckets = f" BUCKETS {buckets}" if buckets else ""
4003        order = self.sql(expression, "order")
4004        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4005
4006    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4007        return ""
4008
4009    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4010        expressions = self.expressions(expression, key="expressions", flat=True)
4011        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4012        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4013        buckets = self.sql(expression, "buckets")
4014        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4015
4016    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4017        this = self.sql(expression, "this")
4018        having = self.sql(expression, "having")
4019
4020        if having:
4021            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4022
4023        return self.func("ANY_VALUE", this)
4024
4025    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4026        transform = self.func("TRANSFORM", *expression.expressions)
4027        row_format_before = self.sql(expression, "row_format_before")
4028        row_format_before = f" {row_format_before}" if row_format_before else ""
4029        record_writer = self.sql(expression, "record_writer")
4030        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4031        using = f" USING {self.sql(expression, 'command_script')}"
4032        schema = self.sql(expression, "schema")
4033        schema = f" AS {schema}" if schema else ""
4034        row_format_after = self.sql(expression, "row_format_after")
4035        row_format_after = f" {row_format_after}" if row_format_after else ""
4036        record_reader = self.sql(expression, "record_reader")
4037        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4038        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4039
4040    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4041        key_block_size = self.sql(expression, "key_block_size")
4042        if key_block_size:
4043            return f"KEY_BLOCK_SIZE = {key_block_size}"
4044
4045        using = self.sql(expression, "using")
4046        if using:
4047            return f"USING {using}"
4048
4049        parser = self.sql(expression, "parser")
4050        if parser:
4051            return f"WITH PARSER {parser}"
4052
4053        comment = self.sql(expression, "comment")
4054        if comment:
4055            return f"COMMENT {comment}"
4056
4057        visible = expression.args.get("visible")
4058        if visible is not None:
4059            return "VISIBLE" if visible else "INVISIBLE"
4060
4061        engine_attr = self.sql(expression, "engine_attr")
4062        if engine_attr:
4063            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4064
4065        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4066        if secondary_engine_attr:
4067            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4068
4069        self.unsupported("Unsupported index constraint option.")
4070        return ""
4071
4072    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4073        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4074        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4075
4076    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4077        kind = self.sql(expression, "kind")
4078        kind = f"{kind} INDEX" if kind else "INDEX"
4079        this = self.sql(expression, "this")
4080        this = f" {this}" if this else ""
4081        index_type = self.sql(expression, "index_type")
4082        index_type = f" USING {index_type}" if index_type else ""
4083        expressions = self.expressions(expression, flat=True)
4084        expressions = f" ({expressions})" if expressions else ""
4085        options = self.expressions(expression, key="options", sep=" ")
4086        options = f" {options}" if options else ""
4087        return f"{kind}{this}{index_type}{expressions}{options}"
4088
4089    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4090        if self.NVL2_SUPPORTED:
4091            return self.function_fallback_sql(expression)
4092
4093        case = exp.Case().when(
4094            expression.this.is_(exp.null()).not_(copy=False),
4095            expression.args["true"],
4096            copy=False,
4097        )
4098        else_cond = expression.args.get("false")
4099        if else_cond:
4100            case.else_(else_cond, copy=False)
4101
4102        return self.sql(case)
4103
4104    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4105        this = self.sql(expression, "this")
4106        expr = self.sql(expression, "expression")
4107        iterator = self.sql(expression, "iterator")
4108        condition = self.sql(expression, "condition")
4109        condition = f" IF {condition}" if condition else ""
4110        return f"{this} FOR {expr} IN {iterator}{condition}"
4111
4112    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4113        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4114
4115    def opclass_sql(self, expression: exp.Opclass) -> str:
4116        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4117
4118    def predict_sql(self, expression: exp.Predict) -> str:
4119        model = self.sql(expression, "this")
4120        model = f"MODEL {model}"
4121        table = self.sql(expression, "expression")
4122        table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table
4123        parameters = self.sql(expression, "params_struct")
4124        return self.func("PREDICT", model, table, parameters or None)
4125
4126    def forin_sql(self, expression: exp.ForIn) -> str:
4127        this = self.sql(expression, "this")
4128        expression_sql = self.sql(expression, "expression")
4129        return f"FOR {this} DO {expression_sql}"
4130
4131    def refresh_sql(self, expression: exp.Refresh) -> str:
4132        this = self.sql(expression, "this")
4133        table = "" if isinstance(expression.this, exp.Literal) else "TABLE "
4134        return f"REFRESH {table}{this}"
4135
4136    def toarray_sql(self, expression: exp.ToArray) -> str:
4137        arg = expression.this
4138        if not arg.type:
4139            from sqlglot.optimizer.annotate_types import annotate_types
4140
4141            arg = annotate_types(arg, dialect=self.dialect)
4142
4143        if arg.is_type(exp.DataType.Type.ARRAY):
4144            return self.sql(arg)
4145
4146        cond_for_null = arg.is_(exp.null())
4147        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4148
4149    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4150        this = expression.this
4151        time_format = self.format_time(expression)
4152
4153        if time_format:
4154            return self.sql(
4155                exp.cast(
4156                    exp.StrToTime(this=this, format=expression.args["format"]),
4157                    exp.DataType.Type.TIME,
4158                )
4159            )
4160
4161        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4162            return self.sql(this)
4163
4164        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4165
4166    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4167        this = expression.this
4168        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4169            return self.sql(this)
4170
4171        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4172
4173    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4174        this = expression.this
4175        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4176            return self.sql(this)
4177
4178        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4179
4180    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4181        this = expression.this
4182        time_format = self.format_time(expression)
4183
4184        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4185            return self.sql(
4186                exp.cast(
4187                    exp.StrToTime(this=this, format=expression.args["format"]),
4188                    exp.DataType.Type.DATE,
4189                )
4190            )
4191
4192        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4193            return self.sql(this)
4194
4195        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4196
4197    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4198        return self.sql(
4199            exp.func(
4200                "DATEDIFF",
4201                expression.this,
4202                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4203                "day",
4204            )
4205        )
4206
4207    def lastday_sql(self, expression: exp.LastDay) -> str:
4208        if self.LAST_DAY_SUPPORTS_DATE_PART:
4209            return self.function_fallback_sql(expression)
4210
4211        unit = expression.text("unit")
4212        if unit and unit != "MONTH":
4213            self.unsupported("Date parts are not supported in LAST_DAY.")
4214
4215        return self.func("LAST_DAY", expression.this)
4216
4217    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4218        from sqlglot.dialects.dialect import unit_to_str
4219
4220        return self.func(
4221            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4222        )
4223
4224    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4225        if self.CAN_IMPLEMENT_ARRAY_ANY:
4226            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4227            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4228            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4229            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4230
4231        from sqlglot.dialects import Dialect
4232
4233        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4234        if self.dialect.__class__ != Dialect:
4235            self.unsupported("ARRAY_ANY is unsupported")
4236
4237        return self.function_fallback_sql(expression)
4238
4239    def struct_sql(self, expression: exp.Struct) -> str:
4240        expression.set(
4241            "expressions",
4242            [
4243                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4244                if isinstance(e, exp.PropertyEQ)
4245                else e
4246                for e in expression.expressions
4247            ],
4248        )
4249
4250        return self.function_fallback_sql(expression)
4251
4252    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4253        low = self.sql(expression, "this")
4254        high = self.sql(expression, "expression")
4255
4256        return f"{low} TO {high}"
4257
4258    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4259        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4260        tables = f" {self.expressions(expression)}"
4261
4262        exists = " IF EXISTS" if expression.args.get("exists") else ""
4263
4264        on_cluster = self.sql(expression, "cluster")
4265        on_cluster = f" {on_cluster}" if on_cluster else ""
4266
4267        identity = self.sql(expression, "identity")
4268        identity = f" {identity} IDENTITY" if identity else ""
4269
4270        option = self.sql(expression, "option")
4271        option = f" {option}" if option else ""
4272
4273        partition = self.sql(expression, "partition")
4274        partition = f" {partition}" if partition else ""
4275
4276        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4277
4278    # This transpiles T-SQL's CONVERT function
4279    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4280    def convert_sql(self, expression: exp.Convert) -> str:
4281        to = expression.this
4282        value = expression.expression
4283        style = expression.args.get("style")
4284        safe = expression.args.get("safe")
4285        strict = expression.args.get("strict")
4286
4287        if not to or not value:
4288            return ""
4289
4290        # Retrieve length of datatype and override to default if not specified
4291        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4292            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4293
4294        transformed: t.Optional[exp.Expression] = None
4295        cast = exp.Cast if strict else exp.TryCast
4296
4297        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4298        if isinstance(style, exp.Literal) and style.is_int:
4299            from sqlglot.dialects.tsql import TSQL
4300
4301            style_value = style.name
4302            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4303            if not converted_style:
4304                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4305
4306            fmt = exp.Literal.string(converted_style)
4307
4308            if to.this == exp.DataType.Type.DATE:
4309                transformed = exp.StrToDate(this=value, format=fmt)
4310            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4311                transformed = exp.StrToTime(this=value, format=fmt)
4312            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4313                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4314            elif to.this == exp.DataType.Type.TEXT:
4315                transformed = exp.TimeToStr(this=value, format=fmt)
4316
4317        if not transformed:
4318            transformed = cast(this=value, to=to, safe=safe)
4319
4320        return self.sql(transformed)
4321
4322    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4323        this = expression.this
4324        if isinstance(this, exp.JSONPathWildcard):
4325            this = self.json_path_part(this)
4326            return f".{this}" if this else ""
4327
4328        if exp.SAFE_IDENTIFIER_RE.match(this):
4329            return f".{this}"
4330
4331        this = self.json_path_part(this)
4332        return (
4333            f"[{this}]"
4334            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
4335            else f".{this}"
4336        )
4337
4338    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
4339        this = self.json_path_part(expression.this)
4340        return f"[{this}]" if this else ""
4341
4342    def _simplify_unless_literal(self, expression: E) -> E:
4343        if not isinstance(expression, exp.Literal):
4344            from sqlglot.optimizer.simplify import simplify
4345
4346            expression = simplify(expression, dialect=self.dialect)
4347
4348        return expression
4349
4350    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
4351        this = expression.this
4352        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
4353            self.unsupported(
4354                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
4355            )
4356            return self.sql(this)
4357
4358        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
4359            # The first modifier here will be the one closest to the AggFunc's arg
4360            mods = sorted(
4361                expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
4362                key=lambda x: 0
4363                if isinstance(x, exp.HavingMax)
4364                else (1 if isinstance(x, exp.Order) else 2),
4365            )
4366
4367            if mods:
4368                mod = mods[0]
4369                this = expression.__class__(this=mod.this.copy())
4370                this.meta["inline"] = True
4371                mod.this.replace(this)
4372                return self.sql(expression.this)
4373
4374            agg_func = expression.find(exp.AggFunc)
4375
4376            if agg_func:
4377                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
4378                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
4379
4380        return f"{self.sql(expression, 'this')} {text}"
4381
4382    def _replace_line_breaks(self, string: str) -> str:
4383        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
4384        if self.pretty:
4385            return string.replace("\n", self.SENTINEL_LINE_BREAK)
4386        return string
4387
4388    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4389        option = self.sql(expression, "this")
4390
4391        if expression.expressions:
4392            upper = option.upper()
4393
4394            # Snowflake FILE_FORMAT options are separated by whitespace
4395            sep = " " if upper == "FILE_FORMAT" else ", "
4396
4397            # Databricks copy/format options do not set their list of values with EQ
4398            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4399            values = self.expressions(expression, flat=True, sep=sep)
4400            return f"{option}{op}({values})"
4401
4402        value = self.sql(expression, "expression")
4403
4404        if not value:
4405            return option
4406
4407        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4408
4409        return f"{option}{op}{value}"
4410
4411    def credentials_sql(self, expression: exp.Credentials) -> str:
4412        cred_expr = expression.args.get("credentials")
4413        if isinstance(cred_expr, exp.Literal):
4414            # Redshift case: CREDENTIALS <string>
4415            credentials = self.sql(expression, "credentials")
4416            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4417        else:
4418            # Snowflake case: CREDENTIALS = (...)
4419            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4420            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4421
4422        storage = self.sql(expression, "storage")
4423        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4424
4425        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4426        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4427
4428        iam_role = self.sql(expression, "iam_role")
4429        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4430
4431        region = self.sql(expression, "region")
4432        region = f" REGION {region}" if region else ""
4433
4434        return f"{credentials}{storage}{encryption}{iam_role}{region}"
4435
4436    def copy_sql(self, expression: exp.Copy) -> str:
4437        this = self.sql(expression, "this")
4438        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4439
4440        credentials = self.sql(expression, "credentials")
4441        credentials = self.seg(credentials) if credentials else ""
4442        kind = self.seg("FROM" if expression.args.get("kind") else "TO")
4443        files = self.expressions(expression, key="files", flat=True)
4444
4445        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4446        params = self.expressions(
4447            expression,
4448            key="params",
4449            sep=sep,
4450            new_line=True,
4451            skip_last=True,
4452            skip_first=True,
4453            indent=self.COPY_PARAMS_ARE_WRAPPED,
4454        )
4455
4456        if params:
4457            if self.COPY_PARAMS_ARE_WRAPPED:
4458                params = f" WITH ({params})"
4459            elif not self.pretty:
4460                params = f" {params}"
4461
4462        return f"COPY{this}{kind} {files}{credentials}{params}"
4463
4464    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4465        return ""
4466
4467    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4468        on_sql = "ON" if expression.args.get("on") else "OFF"
4469        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4470        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4471        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4472        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4473
4474        if filter_col or retention_period:
4475            on_sql = self.func("ON", filter_col, retention_period)
4476
4477        return f"DATA_DELETION={on_sql}"
4478
4479    def maskingpolicycolumnconstraint_sql(
4480        self, expression: exp.MaskingPolicyColumnConstraint
4481    ) -> str:
4482        this = self.sql(expression, "this")
4483        expressions = self.expressions(expression, flat=True)
4484        expressions = f" USING ({expressions})" if expressions else ""
4485        return f"MASKING POLICY {this}{expressions}"
4486
4487    def gapfill_sql(self, expression: exp.GapFill) -> str:
4488        this = self.sql(expression, "this")
4489        this = f"TABLE {this}"
4490        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
4491
4492    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4493        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
4494
4495    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4496        this = self.sql(expression, "this")
4497        expr = expression.expression
4498
4499        if isinstance(expr, exp.Func):
4500            # T-SQL's CLR functions are case sensitive
4501            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4502        else:
4503            expr = self.sql(expression, "expression")
4504
4505        return self.scope_resolution(expr, this)
4506
4507    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4508        if self.PARSE_JSON_NAME is None:
4509            return self.sql(expression.this)
4510
4511        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
4512
4513    def rand_sql(self, expression: exp.Rand) -> str:
4514        lower = self.sql(expression, "lower")
4515        upper = self.sql(expression, "upper")
4516
4517        if lower and upper:
4518            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4519        return self.func("RAND", expression.this)
4520
4521    def changes_sql(self, expression: exp.Changes) -> str:
4522        information = self.sql(expression, "information")
4523        information = f"INFORMATION => {information}"
4524        at_before = self.sql(expression, "at_before")
4525        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4526        end = self.sql(expression, "end")
4527        end = f"{self.seg('')}{end}" if end else ""
4528
4529        return f"CHANGES ({information}){at_before}{end}"
4530
4531    def pad_sql(self, expression: exp.Pad) -> str:
4532        prefix = "L" if expression.args.get("is_left") else "R"
4533
4534        fill_pattern = self.sql(expression, "fill_pattern") or None
4535        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4536            fill_pattern = "' '"
4537
4538        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
4539
4540    def summarize_sql(self, expression: exp.Summarize) -> str:
4541        table = " TABLE" if expression.args.get("table") else ""
4542        return f"SUMMARIZE{table} {self.sql(expression.this)}"
4543
4544    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4545        generate_series = exp.GenerateSeries(**expression.args)
4546
4547        parent = expression.parent
4548        if isinstance(parent, (exp.Alias, exp.TableAlias)):
4549            parent = parent.parent
4550
4551        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
4552            return self.sql(exp.Unnest(expressions=[generate_series]))
4553
4554        if isinstance(parent, exp.Select):
4555            self.unsupported("GenerateSeries projection unnesting is not supported.")
4556
4557        return self.sql(generate_series)
4558
4559    def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str:
4560        exprs = expression.expressions
4561        if not self.ARRAY_CONCAT_IS_VAR_LEN:
4562            rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs)
4563        else:
4564            rhs = self.expressions(expression)
4565
4566        return self.func(name, expression.this, rhs or None)
4567
4568    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
4569        if self.SUPPORTS_CONVERT_TIMEZONE:
4570            return self.function_fallback_sql(expression)
4571
4572        source_tz = expression.args.get("source_tz")
4573        target_tz = expression.args.get("target_tz")
4574        timestamp = expression.args.get("timestamp")
4575
4576        if source_tz and timestamp:
4577            timestamp = exp.AtTimeZone(
4578                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
4579            )
4580
4581        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
4582
4583        return self.sql(expr)
4584
4585    def json_sql(self, expression: exp.JSON) -> str:
4586        this = self.sql(expression, "this")
4587        this = f" {this}" if this else ""
4588
4589        _with = expression.args.get("with")
4590
4591        if _with is None:
4592            with_sql = ""
4593        elif not _with:
4594            with_sql = " WITHOUT"
4595        else:
4596            with_sql = " WITH"
4597
4598        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
4599
4600        return f"JSON{this}{with_sql}{unique_sql}"
4601
4602    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
4603        def _generate_on_options(arg: t.Any) -> str:
4604            return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}"
4605
4606        path = self.sql(expression, "path")
4607        returning = self.sql(expression, "returning")
4608        returning = f" RETURNING {returning}" if returning else ""
4609
4610        on_condition = self.sql(expression, "on_condition")
4611        on_condition = f" {on_condition}" if on_condition else ""
4612
4613        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4614
4615    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
4616        else_ = "ELSE " if expression.args.get("else_") else ""
4617        condition = self.sql(expression, "expression")
4618        condition = f"WHEN {condition} THEN " if condition else else_
4619        insert = self.sql(expression, "this")[len("INSERT") :].strip()
4620        return f"{condition}{insert}"
4621
4622    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
4623        kind = self.sql(expression, "kind")
4624        expressions = self.seg(self.expressions(expression, sep=" "))
4625        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
4626        return res
4627
4628    def oncondition_sql(self, expression: exp.OnCondition) -> str:
4629        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
4630        empty = expression.args.get("empty")
4631        empty = (
4632            f"DEFAULT {empty} ON EMPTY"
4633            if isinstance(empty, exp.Expression)
4634            else self.sql(expression, "empty")
4635        )
4636
4637        error = expression.args.get("error")
4638        error = (
4639            f"DEFAULT {error} ON ERROR"
4640            if isinstance(error, exp.Expression)
4641            else self.sql(expression, "error")
4642        )
4643
4644        if error and empty:
4645            error = (
4646                f"{empty} {error}"
4647                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
4648                else f"{error} {empty}"
4649            )
4650            empty = ""
4651
4652        null = self.sql(expression, "null")
4653
4654        return f"{empty}{error}{null}"
4655
4656    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
4657        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
4658        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
4659
4660    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
4661        this = self.sql(expression, "this")
4662        path = self.sql(expression, "path")
4663
4664        passing = self.expressions(expression, "passing")
4665        passing = f" PASSING {passing}" if passing else ""
4666
4667        on_condition = self.sql(expression, "on_condition")
4668        on_condition = f" {on_condition}" if on_condition else ""
4669
4670        path = f"{path}{passing}{on_condition}"
4671
4672        return self.func("JSON_EXISTS", this, path)
4673
4674    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
4675        array_agg = self.function_fallback_sql(expression)
4676
4677        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
4678        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
4679        if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"):
4680            parent = expression.parent
4681            if isinstance(parent, exp.Filter):
4682                parent_cond = parent.expression.this
4683                parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_()))
4684            else:
4685                this = expression.this
4686                # Do not add the filter if the input is not a column (e.g. literal, struct etc)
4687                if this.find(exp.Column):
4688                    # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
4689                    this_sql = (
4690                        self.expressions(this)
4691                        if isinstance(this, exp.Distinct)
4692                        else self.sql(expression, "this")
4693                    )
4694
4695                    array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)"
4696
4697        return array_agg
4698
4699    def apply_sql(self, expression: exp.Apply) -> str:
4700        this = self.sql(expression, "this")
4701        expr = self.sql(expression, "expression")
4702
4703        return f"{this} APPLY({expr})"
4704
4705    def grant_sql(self, expression: exp.Grant) -> str:
4706        privileges_sql = self.expressions(expression, key="privileges", flat=True)
4707
4708        kind = self.sql(expression, "kind")
4709        kind = f" {kind}" if kind else ""
4710
4711        securable = self.sql(expression, "securable")
4712        securable = f" {securable}" if securable else ""
4713
4714        principals = self.expressions(expression, key="principals", flat=True)
4715
4716        grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else ""
4717
4718        return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4719
4720    def grantprivilege_sql(self, expression: exp.GrantPrivilege):
4721        this = self.sql(expression, "this")
4722        columns = self.expressions(expression, flat=True)
4723        columns = f"({columns})" if columns else ""
4724
4725        return f"{this}{columns}"
4726
4727    def grantprincipal_sql(self, expression: exp.GrantPrincipal):
4728        this = self.sql(expression, "this")
4729
4730        kind = self.sql(expression, "kind")
4731        kind = f"{kind} " if kind else ""
4732
4733        return f"{kind}{this}"
4734
4735    def columns_sql(self, expression: exp.Columns):
4736        func = self.function_fallback_sql(expression)
4737        if expression.args.get("unpack"):
4738            func = f"*{func}"
4739
4740        return func
4741
4742    def overlay_sql(self, expression: exp.Overlay):
4743        this = self.sql(expression, "this")
4744        expr = self.sql(expression, "expression")
4745        from_sql = self.sql(expression, "from")
4746        for_sql = self.sql(expression, "for")
4747        for_sql = f" FOR {for_sql}" if for_sql else ""
4748
4749        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
4750
4751    @unsupported_args("format")
4752    def todouble_sql(self, expression: exp.ToDouble) -> str:
4753        return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4754
4755    def string_sql(self, expression: exp.String) -> str:
4756        this = expression.this
4757        zone = expression.args.get("zone")
4758
4759        if zone:
4760            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
4761            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
4762            # set for source_tz to transpile the time conversion before the STRING cast
4763            this = exp.ConvertTimezone(
4764                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
4765            )
4766
4767        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
4768
4769    def median_sql(self, expression: exp.Median):
4770        if not self.SUPPORTS_MEDIAN:
4771            return self.sql(
4772                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
4773            )
4774
4775        return self.function_fallback_sql(expression)
4776
4777    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
4778        filler = self.sql(expression, "this")
4779        filler = f" {filler}" if filler else ""
4780        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
4781        return f"TRUNCATE{filler} {with_count}"
4782
4783    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
4784        if self.SUPPORTS_UNIX_SECONDS:
4785            return self.function_fallback_sql(expression)
4786
4787        start_ts = exp.cast(
4788            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
4789        )
4790
4791        return self.sql(
4792            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
4793        )
4794
4795    def arraysize_sql(self, expression: exp.ArraySize) -> str:
4796        dim = expression.expression
4797
4798        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
4799        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
4800            if not (dim.is_int and dim.name == "1"):
4801                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
4802            dim = None
4803
4804        # If dimension is required but not specified, default initialize it
4805        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
4806            dim = exp.Literal.number(1)
4807
4808        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4809
4810    def attach_sql(self, expression: exp.Attach) -> str:
4811        this = self.sql(expression, "this")
4812        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
4813        expressions = self.expressions(expression)
4814        expressions = f" ({expressions})" if expressions else ""
4815
4816        return f"ATTACH{exists_sql} {this}{expressions}"
4817
4818    def detach_sql(self, expression: exp.Detach) -> str:
4819        this = self.sql(expression, "this")
4820        # the DATABASE keyword is required if IF EXISTS is set
4821        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
4822        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
4823        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
4824
4825        return f"DETACH{exists_sql} {this}"
4826
4827    def attachoption_sql(self, expression: exp.AttachOption) -> str:
4828        this = self.sql(expression, "this")
4829        value = self.sql(expression, "expression")
4830        value = f" {value}" if value else ""
4831        return f"{this}{value}"
4832
4833    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4834        this_sql = self.sql(expression, "this")
4835        if isinstance(expression.this, exp.Table):
4836            this_sql = f"TABLE {this_sql}"
4837
4838        return self.func(
4839            "FEATURES_AT_TIME",
4840            this_sql,
4841            expression.args.get("time"),
4842            expression.args.get("num_rows"),
4843            expression.args.get("ignore_feature_nulls"),
4844        )
4845
4846    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
4847        return (
4848            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
4849        )
4850
4851    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
4852        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
4853        encode = f"{encode} {self.sql(expression, 'this')}"
4854
4855        properties = expression.args.get("properties")
4856        if properties:
4857            encode = f"{encode} {self.properties(properties)}"
4858
4859        return encode
4860
4861    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
4862        this = self.sql(expression, "this")
4863        include = f"INCLUDE {this}"
4864
4865        column_def = self.sql(expression, "column_def")
4866        if column_def:
4867            include = f"{include} {column_def}"
4868
4869        alias = self.sql(expression, "alias")
4870        if alias:
4871            include = f"{include} AS {alias}"
4872
4873        return include
4874
4875    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
4876        name = f"NAME {self.sql(expression, 'this')}"
4877        return self.func("XMLELEMENT", name, *expression.expressions)
4878
4879    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
4880        this = self.sql(expression, "this")
4881        expr = self.sql(expression, "expression")
4882        expr = f"({expr})" if expr else ""
4883        return f"{this}{expr}"
4884
4885    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
4886        partitions = self.expressions(expression, "partition_expressions")
4887        create = self.expressions(expression, "create_expressions")
4888        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
4889
4890    def partitionbyrangepropertydynamic_sql(
4891        self, expression: exp.PartitionByRangePropertyDynamic
4892    ) -> str:
4893        start = self.sql(expression, "start")
4894        end = self.sql(expression, "end")
4895
4896        every = expression.args["every"]
4897        if isinstance(every, exp.Interval) and every.this.is_string:
4898            every.this.replace(exp.Literal.number(every.name))
4899
4900        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
4901
4902    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
4903        name = self.sql(expression, "this")
4904        values = self.expressions(expression, flat=True)
4905
4906        return f"NAME {name} VALUE {values}"
4907
4908    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
4909        kind = self.sql(expression, "kind")
4910        sample = self.sql(expression, "sample")
4911        return f"SAMPLE {sample} {kind}"
4912
4913    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
4914        kind = self.sql(expression, "kind")
4915        option = self.sql(expression, "option")
4916        option = f" {option}" if option else ""
4917        this = self.sql(expression, "this")
4918        this = f" {this}" if this else ""
4919        columns = self.expressions(expression)
4920        columns = f" {columns}" if columns else ""
4921        return f"{kind}{option} STATISTICS{this}{columns}"
4922
4923    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
4924        this = self.sql(expression, "this")
4925        columns = self.expressions(expression)
4926        inner_expression = self.sql(expression, "expression")
4927        inner_expression = f" {inner_expression}" if inner_expression else ""
4928        update_options = self.sql(expression, "update_options")
4929        update_options = f" {update_options} UPDATE" if update_options else ""
4930        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
4931
4932    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
4933        kind = self.sql(expression, "kind")
4934        kind = f" {kind}" if kind else ""
4935        return f"DELETE{kind} STATISTICS"
4936
4937    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
4938        inner_expression = self.sql(expression, "expression")
4939        return f"LIST CHAINED ROWS{inner_expression}"
4940
4941    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
4942        kind = self.sql(expression, "kind")
4943        this = self.sql(expression, "this")
4944        this = f" {this}" if this else ""
4945        inner_expression = self.sql(expression, "expression")
4946        return f"VALIDATE {kind}{this}{inner_expression}"
4947
4948    def analyze_sql(self, expression: exp.Analyze) -> str:
4949        options = self.expressions(expression, key="options", sep=" ")
4950        options = f" {options}" if options else ""
4951        kind = self.sql(expression, "kind")
4952        kind = f" {kind}" if kind else ""
4953        this = self.sql(expression, "this")
4954        this = f" {this}" if this else ""
4955        mode = self.sql(expression, "mode")
4956        mode = f" {mode}" if mode else ""
4957        properties = self.sql(expression, "properties")
4958        properties = f" {properties}" if properties else ""
4959        partition = self.sql(expression, "partition")
4960        partition = f" {partition}" if partition else ""
4961        inner_expression = self.sql(expression, "expression")
4962        inner_expression = f" {inner_expression}" if inner_expression else ""
4963        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
4964
4965    def xmltable_sql(self, expression: exp.XMLTable) -> str:
4966        this = self.sql(expression, "this")
4967        namespaces = self.expressions(expression, key="namespaces")
4968        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
4969        passing = self.expressions(expression, key="passing")
4970        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
4971        columns = self.expressions(expression, key="columns")
4972        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
4973        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
4974        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
4975
4976    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
4977        this = self.sql(expression, "this")
4978        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
4979
4980    def export_sql(self, expression: exp.Export) -> str:
4981        this = self.sql(expression, "this")
4982        connection = self.sql(expression, "connection")
4983        connection = f"WITH CONNECTION {connection} " if connection else ""
4984        options = self.sql(expression, "options")
4985        return f"EXPORT DATA {connection}{options} AS {this}"
4986
4987    def declare_sql(self, expression: exp.Declare) -> str:
4988        return f"DECLARE {self.expressions(expression, flat=True)}"
4989
4990    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
4991        variable = self.sql(expression, "this")
4992        default = self.sql(expression, "default")
4993        default = f" = {default}" if default else ""
4994
4995        kind = self.sql(expression, "kind")
4996        if isinstance(expression.args.get("kind"), exp.Schema):
4997            kind = f"TABLE {kind}"
4998
4999        return f"{variable} AS {kind}{default}"
5000
5001    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5002        kind = self.sql(expression, "kind")
5003        this = self.sql(expression, "this")
5004        set = self.sql(expression, "expression")
5005        using = self.sql(expression, "using")
5006        using = f" USING {using}" if using else ""
5007
5008        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5009
5010        return f"{kind_sql} {this} SET {set}{using}"
5011
5012    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5013        params = self.expressions(expression, key="params", flat=True)
5014        return self.func(expression.name, *expression.expressions) + f"({params})"
5015
5016    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5017        return self.func(expression.name, *expression.expressions)
5018
5019    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5020        return self.anonymousaggfunc_sql(expression)
5021
5022    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5023        return self.parameterizedagg_sql(expression)
5024
5025    def show_sql(self, expression: exp.Show) -> str:
5026        self.unsupported("Unsupported SHOW statement")
5027        return ""
5028
5029    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5030        # Snowflake GET/PUT statements:
5031        #   PUT <file> <internalStage> <properties>
5032        #   GET <internalStage> <file> <properties>
5033        props = expression.args.get("properties")
5034        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5035        this = self.sql(expression, "this")
5036        target = self.sql(expression, "target")
5037
5038        if isinstance(expression, exp.Put):
5039            return f"PUT {this} {target}{props_sql}"
5040        else:
5041            return f"GET {target} {this}{props_sql}"
5042
5043    def translatecharacters_sql(self, expression: exp.TranslateCharacters):
5044        this = self.sql(expression, "this")
5045        expr = self.sql(expression, "expression")
5046        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5047        return f"TRANSLATE({this} USING {expr}{with_error})"
5048
5049    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5050        if self.SUPPORTS_DECODE_CASE:
5051            return self.func("DECODE", *expression.expressions)
5052
5053        expression, *expressions = expression.expressions
5054
5055        ifs = []
5056        for search, result in zip(expressions[::2], expressions[1::2]):
5057            if isinstance(search, exp.Literal):
5058                ifs.append(exp.If(this=expression.eq(search), true=result))
5059            elif isinstance(search, exp.Null):
5060                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5061            else:
5062                if isinstance(search, exp.Binary):
5063                    search = exp.paren(search)
5064
5065                cond = exp.or_(
5066                    expression.eq(search),
5067                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5068                    copy=False,
5069                )
5070                ifs.append(exp.If(this=cond, true=result))
5071
5072        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5073        return self.sql(case)
5074
5075    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5076        this = self.sql(expression, "this")
5077        this = self.seg(this, sep="")
5078        dimensions = self.expressions(
5079            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5080        )
5081        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5082        metrics = self.expressions(
5083            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5084        )
5085        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5086        where = self.sql(expression, "where")
5087        where = self.seg(f"WHERE {where}") if where else ""
5088        return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"
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 or 'always': Always quote.
  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.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.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 142        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 143        exp.DynamicProperty: lambda *_: "DYNAMIC",
 144        exp.EmptyProperty: lambda *_: "EMPTY",
 145        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 146        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 147        exp.EphemeralColumnConstraint: lambda self,
 148        e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}",
 149        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 150        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 151        exp.Except: lambda self, e: self.set_operations(e),
 152        exp.ExternalProperty: lambda *_: "EXTERNAL",
 153        exp.Floor: lambda self, e: self.ceil_floor(e),
 154        exp.Get: lambda self, e: self.get_put_sql(e),
 155        exp.GlobalProperty: lambda *_: "GLOBAL",
 156        exp.HeapProperty: lambda *_: "HEAP",
 157        exp.IcebergProperty: lambda *_: "ICEBERG",
 158        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 159        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 160        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 161        exp.Intersect: lambda self, e: self.set_operations(e),
 162        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 163        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)),
 164        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 165        exp.LocationProperty: lambda self, e: self.naked_property(e),
 166        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 167        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 168        exp.NonClusteredColumnConstraint: lambda self,
 169        e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})",
 170        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 171        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 172        exp.OnCommitProperty: lambda _,
 173        e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS",
 174        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 175        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 176        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 177        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 178        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 179        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 180        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 181        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 182        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 183        exp.ProjectionPolicyColumnConstraint: lambda self,
 184        e: f"PROJECTION POLICY {self.sql(e, 'this')}",
 185        exp.Put: lambda self, e: self.get_put_sql(e),
 186        exp.RemoteWithConnectionModelProperty: lambda self,
 187        e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
 188        exp.ReturnsProperty: lambda self, e: (
 189            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 190        ),
 191        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 192        exp.SecureProperty: lambda *_: "SECURE",
 193        exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}",
 194        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 195        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 196        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 197        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 198        exp.SqlReadWriteProperty: lambda _, e: e.name,
 199        exp.SqlSecurityProperty: lambda _,
 200        e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}",
 201        exp.StabilityProperty: lambda _, e: e.name,
 202        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 203        exp.StreamingTableProperty: lambda *_: "STREAMING",
 204        exp.StrictProperty: lambda *_: "STRICT",
 205        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 206        exp.TableColumn: lambda self, e: self.sql(e.this),
 207        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 208        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 209        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 210        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 211        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 212        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 213        exp.TransientProperty: lambda *_: "TRANSIENT",
 214        exp.Union: lambda self, e: self.set_operations(e),
 215        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 216        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 217        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 218        exp.Uuid: lambda *_: "UUID()",
 219        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 220        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 221        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 222        exp.VolatileProperty: lambda *_: "VOLATILE",
 223        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 224        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 225        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 226        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 227        exp.ForceProperty: lambda *_: "FORCE",
 228    }
 229
 230    # Whether null ordering is supported in order by
 231    # True: Full Support, None: No support, False: No support for certain cases
 232    # such as window specifications, aggregate functions etc
 233    NULL_ORDERING_SUPPORTED: t.Optional[bool] = True
 234
 235    # Whether ignore nulls is inside the agg or outside.
 236    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 237    IGNORE_NULLS_IN_FUNC = False
 238
 239    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 240    LOCKING_READS_SUPPORTED = False
 241
 242    # Whether the EXCEPT and INTERSECT operations can return duplicates
 243    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 244
 245    # Wrap derived values in parens, usually standard but spark doesn't support it
 246    WRAP_DERIVED_VALUES = True
 247
 248    # Whether create function uses an AS before the RETURN
 249    CREATE_FUNCTION_RETURN_AS = True
 250
 251    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 252    MATCHED_BY_SOURCE = True
 253
 254    # Whether the INTERVAL expression works only with values like '1 day'
 255    SINGLE_STRING_INTERVAL = False
 256
 257    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 258    INTERVAL_ALLOWS_PLURAL_FORM = True
 259
 260    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 261    LIMIT_FETCH = "ALL"
 262
 263    # Whether limit and fetch allows expresions or just limits
 264    LIMIT_ONLY_LITERALS = False
 265
 266    # Whether a table is allowed to be renamed with a db
 267    RENAME_TABLE_WITH_DB = True
 268
 269    # The separator for grouping sets and rollups
 270    GROUPINGS_SEP = ","
 271
 272    # The string used for creating an index on a table
 273    INDEX_ON = "ON"
 274
 275    # Whether join hints should be generated
 276    JOIN_HINTS = True
 277
 278    # Whether table hints should be generated
 279    TABLE_HINTS = True
 280
 281    # Whether query hints should be generated
 282    QUERY_HINTS = True
 283
 284    # What kind of separator to use for query hints
 285    QUERY_HINT_SEP = ", "
 286
 287    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 288    IS_BOOL_ALLOWED = True
 289
 290    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 291    DUPLICATE_KEY_UPDATE_WITH_SET = True
 292
 293    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 294    LIMIT_IS_TOP = False
 295
 296    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 297    RETURNING_END = True
 298
 299    # Whether to generate an unquoted value for EXTRACT's date part argument
 300    EXTRACT_ALLOWS_QUOTES = True
 301
 302    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 303    TZ_TO_WITH_TIME_ZONE = False
 304
 305    # Whether the NVL2 function is supported
 306    NVL2_SUPPORTED = True
 307
 308    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 309    SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE")
 310
 311    # Whether VALUES statements can be used as derived tables.
 312    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 313    # SELECT * VALUES into SELECT UNION
 314    VALUES_AS_TABLE = True
 315
 316    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 317    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 318
 319    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 320    UNNEST_WITH_ORDINALITY = True
 321
 322    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 323    AGGREGATE_FILTER_SUPPORTED = True
 324
 325    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 326    SEMI_ANTI_JOIN_WITH_SIDE = True
 327
 328    # Whether to include the type of a computed column in the CREATE DDL
 329    COMPUTED_COLUMN_WITH_TYPE = True
 330
 331    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 332    SUPPORTS_TABLE_COPY = True
 333
 334    # Whether parentheses are required around the table sample's expression
 335    TABLESAMPLE_REQUIRES_PARENS = True
 336
 337    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 338    TABLESAMPLE_SIZE_IS_ROWS = True
 339
 340    # The keyword(s) to use when generating a sample clause
 341    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 342
 343    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 344    TABLESAMPLE_WITH_METHOD = True
 345
 346    # The keyword to use when specifying the seed of a sample clause
 347    TABLESAMPLE_SEED_KEYWORD = "SEED"
 348
 349    # Whether COLLATE is a function instead of a binary operator
 350    COLLATE_IS_FUNC = False
 351
 352    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 353    DATA_TYPE_SPECIFIERS_ALLOWED = False
 354
 355    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 356    ENSURE_BOOLS = False
 357
 358    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 359    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 360
 361    # Whether CONCAT requires >1 arguments
 362    SUPPORTS_SINGLE_ARG_CONCAT = True
 363
 364    # Whether LAST_DAY function supports a date part argument
 365    LAST_DAY_SUPPORTS_DATE_PART = True
 366
 367    # Whether named columns are allowed in table aliases
 368    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 369
 370    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 371    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 372
 373    # What delimiter to use for separating JSON key/value pairs
 374    JSON_KEY_VALUE_PAIR_SEP = ":"
 375
 376    # INSERT OVERWRITE TABLE x override
 377    INSERT_OVERWRITE = " OVERWRITE TABLE"
 378
 379    # Whether the SELECT .. INTO syntax is used instead of CTAS
 380    SUPPORTS_SELECT_INTO = False
 381
 382    # Whether UNLOGGED tables can be created
 383    SUPPORTS_UNLOGGED_TABLES = False
 384
 385    # Whether the CREATE TABLE LIKE statement is supported
 386    SUPPORTS_CREATE_TABLE_LIKE = True
 387
 388    # Whether the LikeProperty needs to be specified inside of the schema clause
 389    LIKE_PROPERTY_INSIDE_SCHEMA = False
 390
 391    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 392    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 393    MULTI_ARG_DISTINCT = True
 394
 395    # Whether the JSON extraction operators expect a value of type JSON
 396    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 397
 398    # Whether bracketed keys like ["foo"] are supported in JSON paths
 399    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 400
 401    # Whether to escape keys using single quotes in JSON paths
 402    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 403
 404    # The JSONPathPart expressions supported by this dialect
 405    SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy()
 406
 407    # Whether any(f(x) for x in array) can be implemented by this dialect
 408    CAN_IMPLEMENT_ARRAY_ANY = False
 409
 410    # Whether the function TO_NUMBER is supported
 411    SUPPORTS_TO_NUMBER = True
 412
 413    # Whether EXCLUDE in window specification is supported
 414    SUPPORTS_WINDOW_EXCLUDE = False
 415
 416    # Whether or not set op modifiers apply to the outer set op or select.
 417    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 418    # True means limit 1 happens after the set op, False means it it happens on y.
 419    SET_OP_MODIFIERS = True
 420
 421    # Whether parameters from COPY statement are wrapped in parentheses
 422    COPY_PARAMS_ARE_WRAPPED = True
 423
 424    # Whether values of params are set with "=" token or empty space
 425    COPY_PARAMS_EQ_REQUIRED = False
 426
 427    # Whether COPY statement has INTO keyword
 428    COPY_HAS_INTO_KEYWORD = True
 429
 430    # Whether the conditional TRY(expression) function is supported
 431    TRY_SUPPORTED = True
 432
 433    # Whether the UESCAPE syntax in unicode strings is supported
 434    SUPPORTS_UESCAPE = True
 435
 436    # The keyword to use when generating a star projection with excluded columns
 437    STAR_EXCEPT = "EXCEPT"
 438
 439    # The HEX function name
 440    HEX_FUNC = "HEX"
 441
 442    # The keywords to use when prefixing & separating WITH based properties
 443    WITH_PROPERTIES_PREFIX = "WITH"
 444
 445    # Whether to quote the generated expression of exp.JsonPath
 446    QUOTE_JSON_PATH = True
 447
 448    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 449    PAD_FILL_PATTERN_IS_REQUIRED = False
 450
 451    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 452    SUPPORTS_EXPLODING_PROJECTIONS = True
 453
 454    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 455    ARRAY_CONCAT_IS_VAR_LEN = True
 456
 457    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 458    SUPPORTS_CONVERT_TIMEZONE = False
 459
 460    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 461    SUPPORTS_MEDIAN = True
 462
 463    # Whether UNIX_SECONDS(timestamp) is supported
 464    SUPPORTS_UNIX_SECONDS = False
 465
 466    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 467    ALTER_SET_WRAPPED = False
 468
 469    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 470    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 471    # TODO: The normalization should be done by default once we've tested it across all dialects.
 472    NORMALIZE_EXTRACT_DATE_PARTS = False
 473
 474    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 475    PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
 476
 477    # The function name of the exp.ArraySize expression
 478    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 479
 480    # The syntax to use when altering the type of a column
 481    ALTER_SET_TYPE = "SET DATA TYPE"
 482
 483    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 484    # None -> Doesn't support it at all
 485    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 486    # True (Postgres) -> Explicitly requires it
 487    ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None
 488
 489    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 490    SUPPORTS_DECODE_CASE = True
 491
 492    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 493    SUPPORTS_BETWEEN_FLAGS = False
 494
 495    TYPE_MAPPING = {
 496        exp.DataType.Type.DATETIME2: "TIMESTAMP",
 497        exp.DataType.Type.NCHAR: "CHAR",
 498        exp.DataType.Type.NVARCHAR: "VARCHAR",
 499        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 500        exp.DataType.Type.LONGTEXT: "TEXT",
 501        exp.DataType.Type.TINYTEXT: "TEXT",
 502        exp.DataType.Type.BLOB: "VARBINARY",
 503        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 504        exp.DataType.Type.LONGBLOB: "BLOB",
 505        exp.DataType.Type.TINYBLOB: "BLOB",
 506        exp.DataType.Type.INET: "INET",
 507        exp.DataType.Type.ROWVERSION: "VARBINARY",
 508        exp.DataType.Type.SMALLDATETIME: "TIMESTAMP",
 509    }
 510
 511    TIME_PART_SINGULARS = {
 512        "MICROSECONDS": "MICROSECOND",
 513        "SECONDS": "SECOND",
 514        "MINUTES": "MINUTE",
 515        "HOURS": "HOUR",
 516        "DAYS": "DAY",
 517        "WEEKS": "WEEK",
 518        "MONTHS": "MONTH",
 519        "QUARTERS": "QUARTER",
 520        "YEARS": "YEAR",
 521    }
 522
 523    AFTER_HAVING_MODIFIER_TRANSFORMS = {
 524        "cluster": lambda self, e: self.sql(e, "cluster"),
 525        "distribute": lambda self, e: self.sql(e, "distribute"),
 526        "sort": lambda self, e: self.sql(e, "sort"),
 527        "windows": lambda self, e: (
 528            self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
 529            if e.args.get("windows")
 530            else ""
 531        ),
 532        "qualify": lambda self, e: self.sql(e, "qualify"),
 533    }
 534
 535    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 536
 537    STRUCT_DELIMITER = ("<", ">")
 538
 539    PARAMETER_TOKEN = "@"
 540    NAMED_PLACEHOLDER_TOKEN = ":"
 541
 542    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set()
 543
 544    PROPERTIES_LOCATION = {
 545        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 546        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 547        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 548        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 549        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 550        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 551        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 552        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 553        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 554        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 555        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 556        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 557        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 558        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 559        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 560        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 561        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 562        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 563        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 564        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 565        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 566        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 567        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 568        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 569        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 570        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 571        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 572        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 573        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 574        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 575        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 576        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 577        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 578        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 579        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 580        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 581        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 582        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 583        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 584        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 585        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 586        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 587        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 588        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 589        exp.LogProperty: exp.Properties.Location.POST_NAME,
 590        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 591        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 592        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 593        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 594        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 595        exp.Order: exp.Properties.Location.POST_SCHEMA,
 596        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 597        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 598        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 599        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 600        exp.Property: exp.Properties.Location.POST_WITH,
 601        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 602        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 603        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 604        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 605        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 606        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 607        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 608        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 609        exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA,
 610        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 611        exp.Set: exp.Properties.Location.POST_SCHEMA,
 612        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 613        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 614        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 615        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 616        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 617        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 618        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 619        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 620        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 621        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 622        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 623        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 624        exp.Tags: exp.Properties.Location.POST_WITH,
 625        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 626        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 627        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 628        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 629        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 630        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 631        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 632        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 633        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 634        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 635        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 636        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 637        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 638        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 639        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 640    }
 641
 642    # Keywords that can't be used as unquoted identifier names
 643    RESERVED_KEYWORDS: t.Set[str] = set()
 644
 645    # Expressions whose comments are separated from them for better formatting
 646    WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 647        exp.Command,
 648        exp.Create,
 649        exp.Describe,
 650        exp.Delete,
 651        exp.Drop,
 652        exp.From,
 653        exp.Insert,
 654        exp.Join,
 655        exp.MultitableInserts,
 656        exp.Order,
 657        exp.Group,
 658        exp.Having,
 659        exp.Select,
 660        exp.SetOperation,
 661        exp.Update,
 662        exp.Where,
 663        exp.With,
 664    )
 665
 666    # Expressions that should not have their comments generated in maybe_comment
 667    EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 668        exp.Binary,
 669        exp.SetOperation,
 670    )
 671
 672    # Expressions that can remain unwrapped when appearing in the context of an INTERVAL
 673    UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = (
 674        exp.Column,
 675        exp.Literal,
 676        exp.Neg,
 677        exp.Paren,
 678    )
 679
 680    PARAMETERIZABLE_TEXT_TYPES = {
 681        exp.DataType.Type.NVARCHAR,
 682        exp.DataType.Type.VARCHAR,
 683        exp.DataType.Type.CHAR,
 684        exp.DataType.Type.NCHAR,
 685    }
 686
 687    # Expressions that need to have all CTEs under them bubbled up to them
 688    EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set()
 689
 690    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = ()
 691
 692    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 693
 694    __slots__ = (
 695        "pretty",
 696        "identify",
 697        "normalize",
 698        "pad",
 699        "_indent",
 700        "normalize_functions",
 701        "unsupported_level",
 702        "max_unsupported",
 703        "leading_comma",
 704        "max_text_width",
 705        "comments",
 706        "dialect",
 707        "unsupported_messages",
 708        "_escaped_quote_end",
 709        "_escaped_identifier_end",
 710        "_next_name",
 711        "_identifier_start",
 712        "_identifier_end",
 713        "_quote_json_path_key_using_brackets",
 714    )
 715
 716    def __init__(
 717        self,
 718        pretty: t.Optional[bool] = None,
 719        identify: str | bool = False,
 720        normalize: bool = False,
 721        pad: int = 2,
 722        indent: int = 2,
 723        normalize_functions: t.Optional[str | bool] = None,
 724        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 725        max_unsupported: int = 3,
 726        leading_comma: bool = False,
 727        max_text_width: int = 80,
 728        comments: bool = True,
 729        dialect: DialectType = None,
 730    ):
 731        import sqlglot
 732        from sqlglot.dialects import Dialect
 733
 734        self.pretty = pretty if pretty is not None else sqlglot.pretty
 735        self.identify = identify
 736        self.normalize = normalize
 737        self.pad = pad
 738        self._indent = indent
 739        self.unsupported_level = unsupported_level
 740        self.max_unsupported = max_unsupported
 741        self.leading_comma = leading_comma
 742        self.max_text_width = max_text_width
 743        self.comments = comments
 744        self.dialect = Dialect.get_or_raise(dialect)
 745
 746        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 747        self.normalize_functions = (
 748            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 749        )
 750
 751        self.unsupported_messages: t.List[str] = []
 752        self._escaped_quote_end: str = (
 753            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 754        )
 755        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 756
 757        self._next_name = name_sequence("_t")
 758
 759        self._identifier_start = self.dialect.IDENTIFIER_START
 760        self._identifier_end = self.dialect.IDENTIFIER_END
 761
 762        self._quote_json_path_key_using_brackets = True
 763
 764    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
 765        """
 766        Generates the SQL string corresponding to the given syntax tree.
 767
 768        Args:
 769            expression: The syntax tree.
 770            copy: Whether to copy the expression. The generator performs mutations so
 771                it is safer to copy.
 772
 773        Returns:
 774            The SQL string corresponding to `expression`.
 775        """
 776        if copy:
 777            expression = expression.copy()
 778
 779        expression = self.preprocess(expression)
 780
 781        self.unsupported_messages = []
 782        sql = self.sql(expression).strip()
 783
 784        if self.pretty:
 785            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 786
 787        if self.unsupported_level == ErrorLevel.IGNORE:
 788            return sql
 789
 790        if self.unsupported_level == ErrorLevel.WARN:
 791            for msg in self.unsupported_messages:
 792                logger.warning(msg)
 793        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 794            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 795
 796        return sql
 797
 798    def preprocess(self, expression: exp.Expression) -> exp.Expression:
 799        """Apply generic preprocessing transformations to a given expression."""
 800        expression = self._move_ctes_to_top_level(expression)
 801
 802        if self.ENSURE_BOOLS:
 803            from sqlglot.transforms import ensure_bools
 804
 805            expression = ensure_bools(expression)
 806
 807        return expression
 808
 809    def _move_ctes_to_top_level(self, expression: E) -> E:
 810        if (
 811            not expression.parent
 812            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 813            and any(node.parent is not expression for node in expression.find_all(exp.With))
 814        ):
 815            from sqlglot.transforms import move_ctes_to_top_level
 816
 817            expression = move_ctes_to_top_level(expression)
 818        return expression
 819
 820    def unsupported(self, message: str) -> None:
 821        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 822            raise UnsupportedError(message)
 823        self.unsupported_messages.append(message)
 824
 825    def sep(self, sep: str = " ") -> str:
 826        return f"{sep.strip()}\n" if self.pretty else sep
 827
 828    def seg(self, sql: str, sep: str = " ") -> str:
 829        return f"{self.sep(sep)}{sql}"
 830
 831    def sanitize_comment(self, comment: str) -> str:
 832        comment = " " + comment if comment[0].strip() else comment
 833        comment = comment + " " if comment[-1].strip() else comment
 834
 835        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
 836            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
 837            comment = comment.replace("*/", "* /")
 838
 839        return comment
 840
 841    def maybe_comment(
 842        self,
 843        sql: str,
 844        expression: t.Optional[exp.Expression] = None,
 845        comments: t.Optional[t.List[str]] = None,
 846        separated: bool = False,
 847    ) -> str:
 848        comments = (
 849            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 850            if self.comments
 851            else None
 852        )
 853
 854        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 855            return sql
 856
 857        comments_sql = " ".join(
 858            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
 859        )
 860
 861        if not comments_sql:
 862            return sql
 863
 864        comments_sql = self._replace_line_breaks(comments_sql)
 865
 866        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 867            return (
 868                f"{self.sep()}{comments_sql}{sql}"
 869                if not sql or sql[0].isspace()
 870                else f"{comments_sql}{self.sep()}{sql}"
 871            )
 872
 873        return f"{sql} {comments_sql}"
 874
 875    def wrap(self, expression: exp.Expression | str) -> str:
 876        this_sql = (
 877            self.sql(expression)
 878            if isinstance(expression, exp.UNWRAPPED_QUERIES)
 879            else self.sql(expression, "this")
 880        )
 881        if not this_sql:
 882            return "()"
 883
 884        this_sql = self.indent(this_sql, level=1, pad=0)
 885        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 886
 887    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 888        original = self.identify
 889        self.identify = False
 890        result = func(*args, **kwargs)
 891        self.identify = original
 892        return result
 893
 894    def normalize_func(self, name: str) -> str:
 895        if self.normalize_functions == "upper" or self.normalize_functions is True:
 896            return name.upper()
 897        if self.normalize_functions == "lower":
 898            return name.lower()
 899        return name
 900
 901    def indent(
 902        self,
 903        sql: str,
 904        level: int = 0,
 905        pad: t.Optional[int] = None,
 906        skip_first: bool = False,
 907        skip_last: bool = False,
 908    ) -> str:
 909        if not self.pretty or not sql:
 910            return sql
 911
 912        pad = self.pad if pad is None else pad
 913        lines = sql.split("\n")
 914
 915        return "\n".join(
 916            (
 917                line
 918                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 919                else f"{' ' * (level * self._indent + pad)}{line}"
 920            )
 921            for i, line in enumerate(lines)
 922        )
 923
 924    def sql(
 925        self,
 926        expression: t.Optional[str | exp.Expression],
 927        key: t.Optional[str] = None,
 928        comment: bool = True,
 929    ) -> str:
 930        if not expression:
 931            return ""
 932
 933        if isinstance(expression, str):
 934            return expression
 935
 936        if key:
 937            value = expression.args.get(key)
 938            if value:
 939                return self.sql(value)
 940            return ""
 941
 942        transform = self.TRANSFORMS.get(expression.__class__)
 943
 944        if callable(transform):
 945            sql = transform(self, expression)
 946        elif isinstance(expression, exp.Expression):
 947            exp_handler_name = f"{expression.key}_sql"
 948
 949            if hasattr(self, exp_handler_name):
 950                sql = getattr(self, exp_handler_name)(expression)
 951            elif isinstance(expression, exp.Func):
 952                sql = self.function_fallback_sql(expression)
 953            elif isinstance(expression, exp.Property):
 954                sql = self.property_sql(expression)
 955            else:
 956                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
 957        else:
 958            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
 959
 960        return self.maybe_comment(sql, expression) if self.comments and comment else sql
 961
 962    def uncache_sql(self, expression: exp.Uncache) -> str:
 963        table = self.sql(expression, "this")
 964        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
 965        return f"UNCACHE TABLE{exists_sql} {table}"
 966
 967    def cache_sql(self, expression: exp.Cache) -> str:
 968        lazy = " LAZY" if expression.args.get("lazy") else ""
 969        table = self.sql(expression, "this")
 970        options = expression.args.get("options")
 971        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
 972        sql = self.sql(expression, "expression")
 973        sql = f" AS{self.sep()}{sql}" if sql else ""
 974        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
 975        return self.prepend_ctes(expression, sql)
 976
 977    def characterset_sql(self, expression: exp.CharacterSet) -> str:
 978        if isinstance(expression.parent, exp.Cast):
 979            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
 980        default = "DEFAULT " if expression.args.get("default") else ""
 981        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
 982
 983    def column_parts(self, expression: exp.Column) -> str:
 984        return ".".join(
 985            self.sql(part)
 986            for part in (
 987                expression.args.get("catalog"),
 988                expression.args.get("db"),
 989                expression.args.get("table"),
 990                expression.args.get("this"),
 991            )
 992            if part
 993        )
 994
 995    def column_sql(self, expression: exp.Column) -> str:
 996        join_mark = " (+)" if expression.args.get("join_mark") else ""
 997
 998        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
 999            join_mark = ""
1000            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1001
1002        return f"{self.column_parts(expression)}{join_mark}"
1003
1004    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1005        this = self.sql(expression, "this")
1006        this = f" {this}" if this else ""
1007        position = self.sql(expression, "position")
1008        return f"{position}{this}"
1009
1010    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1011        column = self.sql(expression, "this")
1012        kind = self.sql(expression, "kind")
1013        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1014        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1015        kind = f"{sep}{kind}" if kind else ""
1016        constraints = f" {constraints}" if constraints else ""
1017        position = self.sql(expression, "position")
1018        position = f" {position}" if position else ""
1019
1020        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1021            kind = ""
1022
1023        return f"{exists}{column}{kind}{constraints}{position}"
1024
1025    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1026        this = self.sql(expression, "this")
1027        kind_sql = self.sql(expression, "kind").strip()
1028        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1029
1030    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1031        this = self.sql(expression, "this")
1032        if expression.args.get("not_null"):
1033            persisted = " PERSISTED NOT NULL"
1034        elif expression.args.get("persisted"):
1035            persisted = " PERSISTED"
1036        else:
1037            persisted = ""
1038
1039        return f"AS {this}{persisted}"
1040
1041    def autoincrementcolumnconstraint_sql(self, _) -> str:
1042        return self.token_sql(TokenType.AUTO_INCREMENT)
1043
1044    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1045        if isinstance(expression.this, list):
1046            this = self.wrap(self.expressions(expression, key="this", flat=True))
1047        else:
1048            this = self.sql(expression, "this")
1049
1050        return f"COMPRESS {this}"
1051
1052    def generatedasidentitycolumnconstraint_sql(
1053        self, expression: exp.GeneratedAsIdentityColumnConstraint
1054    ) -> str:
1055        this = ""
1056        if expression.this is not None:
1057            on_null = " ON NULL" if expression.args.get("on_null") else ""
1058            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1059
1060        start = expression.args.get("start")
1061        start = f"START WITH {start}" if start else ""
1062        increment = expression.args.get("increment")
1063        increment = f" INCREMENT BY {increment}" if increment else ""
1064        minvalue = expression.args.get("minvalue")
1065        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1066        maxvalue = expression.args.get("maxvalue")
1067        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1068        cycle = expression.args.get("cycle")
1069        cycle_sql = ""
1070
1071        if cycle is not None:
1072            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1073            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1074
1075        sequence_opts = ""
1076        if start or increment or cycle_sql:
1077            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1078            sequence_opts = f" ({sequence_opts.strip()})"
1079
1080        expr = self.sql(expression, "expression")
1081        expr = f"({expr})" if expr else "IDENTITY"
1082
1083        return f"GENERATED{this} AS {expr}{sequence_opts}"
1084
1085    def generatedasrowcolumnconstraint_sql(
1086        self, expression: exp.GeneratedAsRowColumnConstraint
1087    ) -> str:
1088        start = "START" if expression.args.get("start") else "END"
1089        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1090        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1091
1092    def periodforsystemtimeconstraint_sql(
1093        self, expression: exp.PeriodForSystemTimeConstraint
1094    ) -> str:
1095        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1096
1097    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1098        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1099
1100    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1101        desc = expression.args.get("desc")
1102        if desc is not None:
1103            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1104        options = self.expressions(expression, key="options", flat=True, sep=" ")
1105        options = f" {options}" if options else ""
1106        return f"PRIMARY KEY{options}"
1107
1108    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1109        this = self.sql(expression, "this")
1110        this = f" {this}" if this else ""
1111        index_type = expression.args.get("index_type")
1112        index_type = f" USING {index_type}" if index_type else ""
1113        on_conflict = self.sql(expression, "on_conflict")
1114        on_conflict = f" {on_conflict}" if on_conflict else ""
1115        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1116        options = self.expressions(expression, key="options", flat=True, sep=" ")
1117        options = f" {options}" if options else ""
1118        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1119
1120    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1121        return self.sql(expression, "this")
1122
1123    def create_sql(self, expression: exp.Create) -> str:
1124        kind = self.sql(expression, "kind")
1125        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1126        properties = expression.args.get("properties")
1127        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1128
1129        this = self.createable_sql(expression, properties_locs)
1130
1131        properties_sql = ""
1132        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1133            exp.Properties.Location.POST_WITH
1134        ):
1135            properties_sql = self.sql(
1136                exp.Properties(
1137                    expressions=[
1138                        *properties_locs[exp.Properties.Location.POST_SCHEMA],
1139                        *properties_locs[exp.Properties.Location.POST_WITH],
1140                    ]
1141                )
1142            )
1143
1144            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1145                properties_sql = self.sep() + properties_sql
1146            elif not self.pretty:
1147                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1148                properties_sql = f" {properties_sql}"
1149
1150        begin = " BEGIN" if expression.args.get("begin") else ""
1151        end = " END" if expression.args.get("end") else ""
1152
1153        expression_sql = self.sql(expression, "expression")
1154        if expression_sql:
1155            expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
1156
1157            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1158                postalias_props_sql = ""
1159                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1160                    postalias_props_sql = self.properties(
1161                        exp.Properties(
1162                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1163                        ),
1164                        wrapped=False,
1165                    )
1166                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1167                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1168
1169        postindex_props_sql = ""
1170        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1171            postindex_props_sql = self.properties(
1172                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1173                wrapped=False,
1174                prefix=" ",
1175            )
1176
1177        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1178        indexes = f" {indexes}" if indexes else ""
1179        index_sql = indexes + postindex_props_sql
1180
1181        replace = " OR REPLACE" if expression.args.get("replace") else ""
1182        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1183        unique = " UNIQUE" if expression.args.get("unique") else ""
1184
1185        clustered = expression.args.get("clustered")
1186        if clustered is None:
1187            clustered_sql = ""
1188        elif clustered:
1189            clustered_sql = " CLUSTERED COLUMNSTORE"
1190        else:
1191            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1192
1193        postcreate_props_sql = ""
1194        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1195            postcreate_props_sql = self.properties(
1196                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1197                sep=" ",
1198                prefix=" ",
1199                wrapped=False,
1200            )
1201
1202        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1203
1204        postexpression_props_sql = ""
1205        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1206            postexpression_props_sql = self.properties(
1207                exp.Properties(
1208                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1209                ),
1210                sep=" ",
1211                prefix=" ",
1212                wrapped=False,
1213            )
1214
1215        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1216        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1217        no_schema_binding = (
1218            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1219        )
1220
1221        clone = self.sql(expression, "clone")
1222        clone = f" {clone}" if clone else ""
1223
1224        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1225            properties_expression = f"{expression_sql}{properties_sql}"
1226        else:
1227            properties_expression = f"{properties_sql}{expression_sql}"
1228
1229        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1230        return self.prepend_ctes(expression, expression_sql)
1231
1232    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1233        start = self.sql(expression, "start")
1234        start = f"START WITH {start}" if start else ""
1235        increment = self.sql(expression, "increment")
1236        increment = f" INCREMENT BY {increment}" if increment else ""
1237        minvalue = self.sql(expression, "minvalue")
1238        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1239        maxvalue = self.sql(expression, "maxvalue")
1240        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1241        owned = self.sql(expression, "owned")
1242        owned = f" OWNED BY {owned}" if owned else ""
1243
1244        cache = expression.args.get("cache")
1245        if cache is None:
1246            cache_str = ""
1247        elif cache is True:
1248            cache_str = " CACHE"
1249        else:
1250            cache_str = f" CACHE {cache}"
1251
1252        options = self.expressions(expression, key="options", flat=True, sep=" ")
1253        options = f" {options}" if options else ""
1254
1255        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1256
1257    def clone_sql(self, expression: exp.Clone) -> str:
1258        this = self.sql(expression, "this")
1259        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1260        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1261        return f"{shallow}{keyword} {this}"
1262
1263    def describe_sql(self, expression: exp.Describe) -> str:
1264        style = expression.args.get("style")
1265        style = f" {style}" if style else ""
1266        partition = self.sql(expression, "partition")
1267        partition = f" {partition}" if partition else ""
1268        format = self.sql(expression, "format")
1269        format = f" {format}" if format else ""
1270
1271        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1272
1273    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1274        tag = self.sql(expression, "tag")
1275        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1276
1277    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1278        with_ = self.sql(expression, "with")
1279        if with_:
1280            sql = f"{with_}{self.sep()}{sql}"
1281        return sql
1282
1283    def with_sql(self, expression: exp.With) -> str:
1284        sql = self.expressions(expression, flat=True)
1285        recursive = (
1286            "RECURSIVE "
1287            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1288            else ""
1289        )
1290        search = self.sql(expression, "search")
1291        search = f" {search}" if search else ""
1292
1293        return f"WITH {recursive}{sql}{search}"
1294
1295    def cte_sql(self, expression: exp.CTE) -> str:
1296        alias = expression.args.get("alias")
1297        if alias:
1298            alias.add_comments(expression.pop_comments())
1299
1300        alias_sql = self.sql(expression, "alias")
1301
1302        materialized = expression.args.get("materialized")
1303        if materialized is False:
1304            materialized = "NOT MATERIALIZED "
1305        elif materialized:
1306            materialized = "MATERIALIZED "
1307
1308        return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1309
1310    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1311        alias = self.sql(expression, "this")
1312        columns = self.expressions(expression, key="columns", flat=True)
1313        columns = f"({columns})" if columns else ""
1314
1315        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1316            columns = ""
1317            self.unsupported("Named columns are not supported in table alias.")
1318
1319        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1320            alias = self._next_name()
1321
1322        return f"{alias}{columns}"
1323
1324    def bitstring_sql(self, expression: exp.BitString) -> str:
1325        this = self.sql(expression, "this")
1326        if self.dialect.BIT_START:
1327            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1328        return f"{int(this, 2)}"
1329
1330    def hexstring_sql(
1331        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1332    ) -> str:
1333        this = self.sql(expression, "this")
1334        is_integer_type = expression.args.get("is_integer")
1335
1336        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1337            not self.dialect.HEX_START and not binary_function_repr
1338        ):
1339            # Integer representation will be returned if:
1340            # - The read dialect treats the hex value as integer literal but not the write
1341            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1342            return f"{int(this, 16)}"
1343
1344        if not is_integer_type:
1345            # Read dialect treats the hex value as BINARY/BLOB
1346            if binary_function_repr:
1347                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1348                return self.func(binary_function_repr, exp.Literal.string(this))
1349            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1350                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1351                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1352
1353        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1354
1355    def bytestring_sql(self, expression: exp.ByteString) -> str:
1356        this = self.sql(expression, "this")
1357        if self.dialect.BYTE_START:
1358            return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}"
1359        return this
1360
1361    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1362        this = self.sql(expression, "this")
1363        escape = expression.args.get("escape")
1364
1365        if self.dialect.UNICODE_START:
1366            escape_substitute = r"\\\1"
1367            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1368        else:
1369            escape_substitute = r"\\u\1"
1370            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1371
1372        if escape:
1373            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1374            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1375        else:
1376            escape_pattern = ESCAPED_UNICODE_RE
1377            escape_sql = ""
1378
1379        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1380            this = escape_pattern.sub(escape_substitute, this)
1381
1382        return f"{left_quote}{this}{right_quote}{escape_sql}"
1383
1384    def rawstring_sql(self, expression: exp.RawString) -> str:
1385        string = expression.this
1386        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1387            string = string.replace("\\", "\\\\")
1388
1389        string = self.escape_str(string, escape_backslash=False)
1390        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1391
1392    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1393        this = self.sql(expression, "this")
1394        specifier = self.sql(expression, "expression")
1395        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1396        return f"{this}{specifier}"
1397
1398    def datatype_sql(self, expression: exp.DataType) -> str:
1399        nested = ""
1400        values = ""
1401        interior = self.expressions(expression, flat=True)
1402
1403        type_value = expression.this
1404        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1405            type_sql = self.sql(expression, "kind")
1406        else:
1407            type_sql = (
1408                self.TYPE_MAPPING.get(type_value, type_value.value)
1409                if isinstance(type_value, exp.DataType.Type)
1410                else type_value
1411            )
1412
1413        if interior:
1414            if expression.args.get("nested"):
1415                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1416                if expression.args.get("values") is not None:
1417                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1418                    values = self.expressions(expression, key="values", flat=True)
1419                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1420            elif type_value == exp.DataType.Type.INTERVAL:
1421                nested = f" {interior}"
1422            else:
1423                nested = f"({interior})"
1424
1425        type_sql = f"{type_sql}{nested}{values}"
1426        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1427            exp.DataType.Type.TIMETZ,
1428            exp.DataType.Type.TIMESTAMPTZ,
1429        ):
1430            type_sql = f"{type_sql} WITH TIME ZONE"
1431
1432        return type_sql
1433
1434    def directory_sql(self, expression: exp.Directory) -> str:
1435        local = "LOCAL " if expression.args.get("local") else ""
1436        row_format = self.sql(expression, "row_format")
1437        row_format = f" {row_format}" if row_format else ""
1438        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1439
1440    def delete_sql(self, expression: exp.Delete) -> str:
1441        this = self.sql(expression, "this")
1442        this = f" FROM {this}" if this else ""
1443        using = self.sql(expression, "using")
1444        using = f" USING {using}" if using else ""
1445        cluster = self.sql(expression, "cluster")
1446        cluster = f" {cluster}" if cluster else ""
1447        where = self.sql(expression, "where")
1448        returning = self.sql(expression, "returning")
1449        limit = self.sql(expression, "limit")
1450        tables = self.expressions(expression, key="tables")
1451        tables = f" {tables}" if tables else ""
1452        if self.RETURNING_END:
1453            expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}"
1454        else:
1455            expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}"
1456        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1457
1458    def drop_sql(self, expression: exp.Drop) -> str:
1459        this = self.sql(expression, "this")
1460        expressions = self.expressions(expression, flat=True)
1461        expressions = f" ({expressions})" if expressions else ""
1462        kind = expression.args["kind"]
1463        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1464        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1465        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1466        on_cluster = self.sql(expression, "cluster")
1467        on_cluster = f" {on_cluster}" if on_cluster else ""
1468        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1469        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1470        cascade = " CASCADE" if expression.args.get("cascade") else ""
1471        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1472        purge = " PURGE" if expression.args.get("purge") else ""
1473        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1474
1475    def set_operation(self, expression: exp.SetOperation) -> str:
1476        op_type = type(expression)
1477        op_name = op_type.key.upper()
1478
1479        distinct = expression.args.get("distinct")
1480        if (
1481            distinct is False
1482            and op_type in (exp.Except, exp.Intersect)
1483            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1484        ):
1485            self.unsupported(f"{op_name} ALL is not supported")
1486
1487        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1488
1489        if distinct is None:
1490            distinct = default_distinct
1491            if distinct is None:
1492                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1493
1494        if distinct is default_distinct:
1495            distinct_or_all = ""
1496        else:
1497            distinct_or_all = " DISTINCT" if distinct else " ALL"
1498
1499        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1500        side_kind = f"{side_kind} " if side_kind else ""
1501
1502        by_name = " BY NAME" if expression.args.get("by_name") else ""
1503        on = self.expressions(expression, key="on", flat=True)
1504        on = f" ON ({on})" if on else ""
1505
1506        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1507
1508    def set_operations(self, expression: exp.SetOperation) -> str:
1509        if not self.SET_OP_MODIFIERS:
1510            limit = expression.args.get("limit")
1511            order = expression.args.get("order")
1512
1513            if limit or order:
1514                select = self._move_ctes_to_top_level(
1515                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1516                )
1517
1518                if limit:
1519                    select = select.limit(limit.pop(), copy=False)
1520                if order:
1521                    select = select.order_by(order.pop(), copy=False)
1522                return self.sql(select)
1523
1524        sqls: t.List[str] = []
1525        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1526
1527        while stack:
1528            node = stack.pop()
1529
1530            if isinstance(node, exp.SetOperation):
1531                stack.append(node.expression)
1532                stack.append(
1533                    self.maybe_comment(
1534                        self.set_operation(node), comments=node.comments, separated=True
1535                    )
1536                )
1537                stack.append(node.this)
1538            else:
1539                sqls.append(self.sql(node))
1540
1541        this = self.sep().join(sqls)
1542        this = self.query_modifiers(expression, this)
1543        return self.prepend_ctes(expression, this)
1544
1545    def fetch_sql(self, expression: exp.Fetch) -> str:
1546        direction = expression.args.get("direction")
1547        direction = f" {direction}" if direction else ""
1548        count = self.sql(expression, "count")
1549        count = f" {count}" if count else ""
1550        limit_options = self.sql(expression, "limit_options")
1551        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1552        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1553
1554    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1555        percent = " PERCENT" if expression.args.get("percent") else ""
1556        rows = " ROWS" if expression.args.get("rows") else ""
1557        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1558        if not with_ties and rows:
1559            with_ties = " ONLY"
1560        return f"{percent}{rows}{with_ties}"
1561
1562    def filter_sql(self, expression: exp.Filter) -> str:
1563        if self.AGGREGATE_FILTER_SUPPORTED:
1564            this = self.sql(expression, "this")
1565            where = self.sql(expression, "expression").strip()
1566            return f"{this} FILTER({where})"
1567
1568        agg = expression.this
1569        agg_arg = agg.this
1570        cond = expression.expression.this
1571        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1572        return self.sql(agg)
1573
1574    def hint_sql(self, expression: exp.Hint) -> str:
1575        if not self.QUERY_HINTS:
1576            self.unsupported("Hints are not supported")
1577            return ""
1578
1579        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1580
1581    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1582        using = self.sql(expression, "using")
1583        using = f" USING {using}" if using else ""
1584        columns = self.expressions(expression, key="columns", flat=True)
1585        columns = f"({columns})" if columns else ""
1586        partition_by = self.expressions(expression, key="partition_by", flat=True)
1587        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1588        where = self.sql(expression, "where")
1589        include = self.expressions(expression, key="include", flat=True)
1590        if include:
1591            include = f" INCLUDE ({include})"
1592        with_storage = self.expressions(expression, key="with_storage", flat=True)
1593        with_storage = f" WITH ({with_storage})" if with_storage else ""
1594        tablespace = self.sql(expression, "tablespace")
1595        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1596        on = self.sql(expression, "on")
1597        on = f" ON {on}" if on else ""
1598
1599        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1600
1601    def index_sql(self, expression: exp.Index) -> str:
1602        unique = "UNIQUE " if expression.args.get("unique") else ""
1603        primary = "PRIMARY " if expression.args.get("primary") else ""
1604        amp = "AMP " if expression.args.get("amp") else ""
1605        name = self.sql(expression, "this")
1606        name = f"{name} " if name else ""
1607        table = self.sql(expression, "table")
1608        table = f"{self.INDEX_ON} {table}" if table else ""
1609
1610        index = "INDEX " if not table else ""
1611
1612        params = self.sql(expression, "params")
1613        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1614
1615    def identifier_sql(self, expression: exp.Identifier) -> str:
1616        text = expression.name
1617        lower = text.lower()
1618        text = lower if self.normalize and not expression.quoted else text
1619        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1620        if (
1621            expression.quoted
1622            or self.dialect.can_identify(text, self.identify)
1623            or lower in self.RESERVED_KEYWORDS
1624            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1625        ):
1626            text = f"{self._identifier_start}{text}{self._identifier_end}"
1627        return text
1628
1629    def hex_sql(self, expression: exp.Hex) -> str:
1630        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1631        if self.dialect.HEX_LOWERCASE:
1632            text = self.func("LOWER", text)
1633
1634        return text
1635
1636    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1637        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1638        if not self.dialect.HEX_LOWERCASE:
1639            text = self.func("LOWER", text)
1640        return text
1641
1642    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1643        input_format = self.sql(expression, "input_format")
1644        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1645        output_format = self.sql(expression, "output_format")
1646        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1647        return self.sep().join((input_format, output_format))
1648
1649    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1650        string = self.sql(exp.Literal.string(expression.name))
1651        return f"{prefix}{string}"
1652
1653    def partition_sql(self, expression: exp.Partition) -> str:
1654        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1655        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1656
1657    def properties_sql(self, expression: exp.Properties) -> str:
1658        root_properties = []
1659        with_properties = []
1660
1661        for p in expression.expressions:
1662            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1663            if p_loc == exp.Properties.Location.POST_WITH:
1664                with_properties.append(p)
1665            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1666                root_properties.append(p)
1667
1668        root_props = self.root_properties(exp.Properties(expressions=root_properties))
1669        with_props = self.with_properties(exp.Properties(expressions=with_properties))
1670
1671        if root_props and with_props and not self.pretty:
1672            with_props = " " + with_props
1673
1674        return root_props + with_props
1675
1676    def root_properties(self, properties: exp.Properties) -> str:
1677        if properties.expressions:
1678            return self.expressions(properties, indent=False, sep=" ")
1679        return ""
1680
1681    def properties(
1682        self,
1683        properties: exp.Properties,
1684        prefix: str = "",
1685        sep: str = ", ",
1686        suffix: str = "",
1687        wrapped: bool = True,
1688    ) -> str:
1689        if properties.expressions:
1690            expressions = self.expressions(properties, sep=sep, indent=False)
1691            if expressions:
1692                expressions = self.wrap(expressions) if wrapped else expressions
1693                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1694        return ""
1695
1696    def with_properties(self, properties: exp.Properties) -> str:
1697        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1698
1699    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1700        properties_locs = defaultdict(list)
1701        for p in properties.expressions:
1702            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1703            if p_loc != exp.Properties.Location.UNSUPPORTED:
1704                properties_locs[p_loc].append(p)
1705            else:
1706                self.unsupported(f"Unsupported property {p.key}")
1707
1708        return properties_locs
1709
1710    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1711        if isinstance(expression.this, exp.Dot):
1712            return self.sql(expression, "this")
1713        return f"'{expression.name}'" if string_key else expression.name
1714
1715    def property_sql(self, expression: exp.Property) -> str:
1716        property_cls = expression.__class__
1717        if property_cls == exp.Property:
1718            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1719
1720        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1721        if not property_name:
1722            self.unsupported(f"Unsupported property {expression.key}")
1723
1724        return f"{property_name}={self.sql(expression, 'this')}"
1725
1726    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1727        if self.SUPPORTS_CREATE_TABLE_LIKE:
1728            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1729            options = f" {options}" if options else ""
1730
1731            like = f"LIKE {self.sql(expression, 'this')}{options}"
1732            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1733                like = f"({like})"
1734
1735            return like
1736
1737        if expression.expressions:
1738            self.unsupported("Transpilation of LIKE property options is unsupported")
1739
1740        select = exp.select("*").from_(expression.this).limit(0)
1741        return f"AS {self.sql(select)}"
1742
1743    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1744        no = "NO " if expression.args.get("no") else ""
1745        protection = " PROTECTION" if expression.args.get("protection") else ""
1746        return f"{no}FALLBACK{protection}"
1747
1748    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1749        no = "NO " if expression.args.get("no") else ""
1750        local = expression.args.get("local")
1751        local = f"{local} " if local else ""
1752        dual = "DUAL " if expression.args.get("dual") else ""
1753        before = "BEFORE " if expression.args.get("before") else ""
1754        after = "AFTER " if expression.args.get("after") else ""
1755        return f"{no}{local}{dual}{before}{after}JOURNAL"
1756
1757    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1758        freespace = self.sql(expression, "this")
1759        percent = " PERCENT" if expression.args.get("percent") else ""
1760        return f"FREESPACE={freespace}{percent}"
1761
1762    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1763        if expression.args.get("default"):
1764            property = "DEFAULT"
1765        elif expression.args.get("on"):
1766            property = "ON"
1767        else:
1768            property = "OFF"
1769        return f"CHECKSUM={property}"
1770
1771    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1772        if expression.args.get("no"):
1773            return "NO MERGEBLOCKRATIO"
1774        if expression.args.get("default"):
1775            return "DEFAULT MERGEBLOCKRATIO"
1776
1777        percent = " PERCENT" if expression.args.get("percent") else ""
1778        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1779
1780    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1781        default = expression.args.get("default")
1782        minimum = expression.args.get("minimum")
1783        maximum = expression.args.get("maximum")
1784        if default or minimum or maximum:
1785            if default:
1786                prop = "DEFAULT"
1787            elif minimum:
1788                prop = "MINIMUM"
1789            else:
1790                prop = "MAXIMUM"
1791            return f"{prop} DATABLOCKSIZE"
1792        units = expression.args.get("units")
1793        units = f" {units}" if units else ""
1794        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
1795
1796    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1797        autotemp = expression.args.get("autotemp")
1798        always = expression.args.get("always")
1799        default = expression.args.get("default")
1800        manual = expression.args.get("manual")
1801        never = expression.args.get("never")
1802
1803        if autotemp is not None:
1804            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1805        elif always:
1806            prop = "ALWAYS"
1807        elif default:
1808            prop = "DEFAULT"
1809        elif manual:
1810            prop = "MANUAL"
1811        elif never:
1812            prop = "NEVER"
1813        return f"BLOCKCOMPRESSION={prop}"
1814
1815    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1816        no = expression.args.get("no")
1817        no = " NO" if no else ""
1818        concurrent = expression.args.get("concurrent")
1819        concurrent = " CONCURRENT" if concurrent else ""
1820        target = self.sql(expression, "target")
1821        target = f" {target}" if target else ""
1822        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1823
1824    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1825        if isinstance(expression.this, list):
1826            return f"IN ({self.expressions(expression, key='this', flat=True)})"
1827        if expression.this:
1828            modulus = self.sql(expression, "this")
1829            remainder = self.sql(expression, "expression")
1830            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1831
1832        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1833        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1834        return f"FROM ({from_expressions}) TO ({to_expressions})"
1835
1836    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1837        this = self.sql(expression, "this")
1838
1839        for_values_or_default = expression.expression
1840        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1841            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1842        else:
1843            for_values_or_default = " DEFAULT"
1844
1845        return f"PARTITION OF {this}{for_values_or_default}"
1846
1847    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1848        kind = expression.args.get("kind")
1849        this = f" {self.sql(expression, 'this')}" if expression.this else ""
1850        for_or_in = expression.args.get("for_or_in")
1851        for_or_in = f" {for_or_in}" if for_or_in else ""
1852        lock_type = expression.args.get("lock_type")
1853        override = " OVERRIDE" if expression.args.get("override") else ""
1854        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1855
1856    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1857        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1858        statistics = expression.args.get("statistics")
1859        statistics_sql = ""
1860        if statistics is not None:
1861            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1862        return f"{data_sql}{statistics_sql}"
1863
1864    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1865        this = self.sql(expression, "this")
1866        this = f"HISTORY_TABLE={this}" if this else ""
1867        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
1868        data_consistency = (
1869            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
1870        )
1871        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
1872        retention_period = (
1873            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
1874        )
1875
1876        if this:
1877            on_sql = self.func("ON", this, data_consistency, retention_period)
1878        else:
1879            on_sql = "ON" if expression.args.get("on") else "OFF"
1880
1881        sql = f"SYSTEM_VERSIONING={on_sql}"
1882
1883        return f"WITH({sql})" if expression.args.get("with") else sql
1884
1885    def insert_sql(self, expression: exp.Insert) -> str:
1886        hint = self.sql(expression, "hint")
1887        overwrite = expression.args.get("overwrite")
1888
1889        if isinstance(expression.this, exp.Directory):
1890            this = " OVERWRITE" if overwrite else " INTO"
1891        else:
1892            this = self.INSERT_OVERWRITE if overwrite else " INTO"
1893
1894        stored = self.sql(expression, "stored")
1895        stored = f" {stored}" if stored else ""
1896        alternative = expression.args.get("alternative")
1897        alternative = f" OR {alternative}" if alternative else ""
1898        ignore = " IGNORE" if expression.args.get("ignore") else ""
1899        is_function = expression.args.get("is_function")
1900        if is_function:
1901            this = f"{this} FUNCTION"
1902        this = f"{this} {self.sql(expression, 'this')}"
1903
1904        exists = " IF EXISTS" if expression.args.get("exists") else ""
1905        where = self.sql(expression, "where")
1906        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
1907        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
1908        on_conflict = self.sql(expression, "conflict")
1909        on_conflict = f" {on_conflict}" if on_conflict else ""
1910        by_name = " BY NAME" if expression.args.get("by_name") else ""
1911        returning = self.sql(expression, "returning")
1912
1913        if self.RETURNING_END:
1914            expression_sql = f"{expression_sql}{on_conflict}{returning}"
1915        else:
1916            expression_sql = f"{returning}{expression_sql}{on_conflict}"
1917
1918        partition_by = self.sql(expression, "partition")
1919        partition_by = f" {partition_by}" if partition_by else ""
1920        settings = self.sql(expression, "settings")
1921        settings = f" {settings}" if settings else ""
1922
1923        source = self.sql(expression, "source")
1924        source = f"TABLE {source}" if source else ""
1925
1926        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
1927        return self.prepend_ctes(expression, sql)
1928
1929    def introducer_sql(self, expression: exp.Introducer) -> str:
1930        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
1931
1932    def kill_sql(self, expression: exp.Kill) -> str:
1933        kind = self.sql(expression, "kind")
1934        kind = f" {kind}" if kind else ""
1935        this = self.sql(expression, "this")
1936        this = f" {this}" if this else ""
1937        return f"KILL{kind}{this}"
1938
1939    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
1940        return expression.name
1941
1942    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
1943        return expression.name
1944
1945    def onconflict_sql(self, expression: exp.OnConflict) -> str:
1946        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
1947
1948        constraint = self.sql(expression, "constraint")
1949        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
1950
1951        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
1952        conflict_keys = f"({conflict_keys}) " if conflict_keys else " "
1953        action = self.sql(expression, "action")
1954
1955        expressions = self.expressions(expression, flat=True)
1956        if expressions:
1957            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
1958            expressions = f" {set_keyword}{expressions}"
1959
1960        where = self.sql(expression, "where")
1961        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
1962
1963    def returning_sql(self, expression: exp.Returning) -> str:
1964        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
1965
1966    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
1967        fields = self.sql(expression, "fields")
1968        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
1969        escaped = self.sql(expression, "escaped")
1970        escaped = f" ESCAPED BY {escaped}" if escaped else ""
1971        items = self.sql(expression, "collection_items")
1972        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
1973        keys = self.sql(expression, "map_keys")
1974        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
1975        lines = self.sql(expression, "lines")
1976        lines = f" LINES TERMINATED BY {lines}" if lines else ""
1977        null = self.sql(expression, "null")
1978        null = f" NULL DEFINED AS {null}" if null else ""
1979        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
1980
1981    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
1982        return f"WITH ({self.expressions(expression, flat=True)})"
1983
1984    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
1985        this = f"{self.sql(expression, 'this')} INDEX"
1986        target = self.sql(expression, "target")
1987        target = f" FOR {target}" if target else ""
1988        return f"{this}{target} ({self.expressions(expression, flat=True)})"
1989
1990    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
1991        this = self.sql(expression, "this")
1992        kind = self.sql(expression, "kind")
1993        expr = self.sql(expression, "expression")
1994        return f"{this} ({kind} => {expr})"
1995
1996    def table_parts(self, expression: exp.Table) -> str:
1997        return ".".join(
1998            self.sql(part)
1999            for part in (
2000                expression.args.get("catalog"),
2001                expression.args.get("db"),
2002                expression.args.get("this"),
2003            )
2004            if part is not None
2005        )
2006
2007    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2008        table = self.table_parts(expression)
2009        only = "ONLY " if expression.args.get("only") else ""
2010        partition = self.sql(expression, "partition")
2011        partition = f" {partition}" if partition else ""
2012        version = self.sql(expression, "version")
2013        version = f" {version}" if version else ""
2014        alias = self.sql(expression, "alias")
2015        alias = f"{sep}{alias}" if alias else ""
2016
2017        sample = self.sql(expression, "sample")
2018        if self.dialect.ALIAS_POST_TABLESAMPLE:
2019            sample_pre_alias = sample
2020            sample_post_alias = ""
2021        else:
2022            sample_pre_alias = ""
2023            sample_post_alias = sample
2024
2025        hints = self.expressions(expression, key="hints", sep=" ")
2026        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2027        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2028        joins = self.indent(
2029            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2030        )
2031        laterals = self.expressions(expression, key="laterals", sep="")
2032
2033        file_format = self.sql(expression, "format")
2034        if file_format:
2035            pattern = self.sql(expression, "pattern")
2036            pattern = f", PATTERN => {pattern}" if pattern else ""
2037            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2038
2039        ordinality = expression.args.get("ordinality") or ""
2040        if ordinality:
2041            ordinality = f" WITH ORDINALITY{alias}"
2042            alias = ""
2043
2044        when = self.sql(expression, "when")
2045        if when:
2046            table = f"{table} {when}"
2047
2048        changes = self.sql(expression, "changes")
2049        changes = f" {changes}" if changes else ""
2050
2051        rows_from = self.expressions(expression, key="rows_from")
2052        if rows_from:
2053            table = f"ROWS FROM {self.wrap(rows_from)}"
2054
2055        return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2056
2057    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2058        table = self.func("TABLE", expression.this)
2059        alias = self.sql(expression, "alias")
2060        alias = f" AS {alias}" if alias else ""
2061        sample = self.sql(expression, "sample")
2062        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2063        joins = self.indent(
2064            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2065        )
2066        return f"{table}{alias}{pivots}{sample}{joins}"
2067
2068    def tablesample_sql(
2069        self,
2070        expression: exp.TableSample,
2071        tablesample_keyword: t.Optional[str] = None,
2072    ) -> str:
2073        method = self.sql(expression, "method")
2074        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2075        numerator = self.sql(expression, "bucket_numerator")
2076        denominator = self.sql(expression, "bucket_denominator")
2077        field = self.sql(expression, "bucket_field")
2078        field = f" ON {field}" if field else ""
2079        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2080        seed = self.sql(expression, "seed")
2081        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2082
2083        size = self.sql(expression, "size")
2084        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2085            size = f"{size} ROWS"
2086
2087        percent = self.sql(expression, "percent")
2088        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2089            percent = f"{percent} PERCENT"
2090
2091        expr = f"{bucket}{percent}{size}"
2092        if self.TABLESAMPLE_REQUIRES_PARENS:
2093            expr = f"({expr})"
2094
2095        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2096
2097    def pivot_sql(self, expression: exp.Pivot) -> str:
2098        expressions = self.expressions(expression, flat=True)
2099        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2100
2101        group = self.sql(expression, "group")
2102
2103        if expression.this:
2104            this = self.sql(expression, "this")
2105            if not expressions:
2106                return f"UNPIVOT {this}"
2107
2108            on = f"{self.seg('ON')} {expressions}"
2109            into = self.sql(expression, "into")
2110            into = f"{self.seg('INTO')} {into}" if into else ""
2111            using = self.expressions(expression, key="using", flat=True)
2112            using = f"{self.seg('USING')} {using}" if using else ""
2113            return f"{direction} {this}{on}{into}{using}{group}"
2114
2115        alias = self.sql(expression, "alias")
2116        alias = f" AS {alias}" if alias else ""
2117
2118        fields = self.expressions(
2119            expression,
2120            "fields",
2121            sep=" ",
2122            dynamic=True,
2123            new_line=True,
2124            skip_first=True,
2125            skip_last=True,
2126        )
2127
2128        include_nulls = expression.args.get("include_nulls")
2129        if include_nulls is not None:
2130            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2131        else:
2132            nulls = ""
2133
2134        default_on_null = self.sql(expression, "default_on_null")
2135        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2136        return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2137
2138    def version_sql(self, expression: exp.Version) -> str:
2139        this = f"FOR {expression.name}"
2140        kind = expression.text("kind")
2141        expr = self.sql(expression, "expression")
2142        return f"{this} {kind} {expr}"
2143
2144    def tuple_sql(self, expression: exp.Tuple) -> str:
2145        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2146
2147    def update_sql(self, expression: exp.Update) -> str:
2148        this = self.sql(expression, "this")
2149        set_sql = self.expressions(expression, flat=True)
2150        from_sql = self.sql(expression, "from")
2151        where_sql = self.sql(expression, "where")
2152        returning = self.sql(expression, "returning")
2153        order = self.sql(expression, "order")
2154        limit = self.sql(expression, "limit")
2155        if self.RETURNING_END:
2156            expression_sql = f"{from_sql}{where_sql}{returning}"
2157        else:
2158            expression_sql = f"{returning}{from_sql}{where_sql}"
2159        sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}"
2160        return self.prepend_ctes(expression, sql)
2161
2162    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2163        values_as_table = values_as_table and self.VALUES_AS_TABLE
2164
2165        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2166        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2167            args = self.expressions(expression)
2168            alias = self.sql(expression, "alias")
2169            values = f"VALUES{self.seg('')}{args}"
2170            values = (
2171                f"({values})"
2172                if self.WRAP_DERIVED_VALUES
2173                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2174                else values
2175            )
2176            return f"{values} AS {alias}" if alias else values
2177
2178        # Converts `VALUES...` expression into a series of select unions.
2179        alias_node = expression.args.get("alias")
2180        column_names = alias_node and alias_node.columns
2181
2182        selects: t.List[exp.Query] = []
2183
2184        for i, tup in enumerate(expression.expressions):
2185            row = tup.expressions
2186
2187            if i == 0 and column_names:
2188                row = [
2189                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2190                ]
2191
2192            selects.append(exp.Select(expressions=row))
2193
2194        if self.pretty:
2195            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2196            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2197            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2198            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2199            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2200
2201        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2202        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2203        return f"({unions}){alias}"
2204
2205    def var_sql(self, expression: exp.Var) -> str:
2206        return self.sql(expression, "this")
2207
2208    @unsupported_args("expressions")
2209    def into_sql(self, expression: exp.Into) -> str:
2210        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2211        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2212        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2213
2214    def from_sql(self, expression: exp.From) -> str:
2215        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2216
2217    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2218        grouping_sets = self.expressions(expression, indent=False)
2219        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2220
2221    def rollup_sql(self, expression: exp.Rollup) -> str:
2222        expressions = self.expressions(expression, indent=False)
2223        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2224
2225    def cube_sql(self, expression: exp.Cube) -> str:
2226        expressions = self.expressions(expression, indent=False)
2227        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2228
2229    def group_sql(self, expression: exp.Group) -> str:
2230        group_by_all = expression.args.get("all")
2231        if group_by_all is True:
2232            modifier = " ALL"
2233        elif group_by_all is False:
2234            modifier = " DISTINCT"
2235        else:
2236            modifier = ""
2237
2238        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2239
2240        grouping_sets = self.expressions(expression, key="grouping_sets")
2241        cube = self.expressions(expression, key="cube")
2242        rollup = self.expressions(expression, key="rollup")
2243
2244        groupings = csv(
2245            self.seg(grouping_sets) if grouping_sets else "",
2246            self.seg(cube) if cube else "",
2247            self.seg(rollup) if rollup else "",
2248            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2249            sep=self.GROUPINGS_SEP,
2250        )
2251
2252        if (
2253            expression.expressions
2254            and groupings
2255            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2256        ):
2257            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2258
2259        return f"{group_by}{groupings}"
2260
2261    def having_sql(self, expression: exp.Having) -> str:
2262        this = self.indent(self.sql(expression, "this"))
2263        return f"{self.seg('HAVING')}{self.sep()}{this}"
2264
2265    def connect_sql(self, expression: exp.Connect) -> str:
2266        start = self.sql(expression, "start")
2267        start = self.seg(f"START WITH {start}") if start else ""
2268        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2269        connect = self.sql(expression, "connect")
2270        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2271        return start + connect
2272
2273    def prior_sql(self, expression: exp.Prior) -> str:
2274        return f"PRIOR {self.sql(expression, 'this')}"
2275
2276    def join_sql(self, expression: exp.Join) -> str:
2277        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2278            side = None
2279        else:
2280            side = expression.side
2281
2282        op_sql = " ".join(
2283            op
2284            for op in (
2285                expression.method,
2286                "GLOBAL" if expression.args.get("global") else None,
2287                side,
2288                expression.kind,
2289                expression.hint if self.JOIN_HINTS else None,
2290            )
2291            if op
2292        )
2293        match_cond = self.sql(expression, "match_condition")
2294        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2295        on_sql = self.sql(expression, "on")
2296        using = expression.args.get("using")
2297
2298        if not on_sql and using:
2299            on_sql = csv(*(self.sql(column) for column in using))
2300
2301        this = expression.this
2302        this_sql = self.sql(this)
2303
2304        exprs = self.expressions(expression)
2305        if exprs:
2306            this_sql = f"{this_sql},{self.seg(exprs)}"
2307
2308        if on_sql:
2309            on_sql = self.indent(on_sql, skip_first=True)
2310            space = self.seg(" " * self.pad) if self.pretty else " "
2311            if using:
2312                on_sql = f"{space}USING ({on_sql})"
2313            else:
2314                on_sql = f"{space}ON {on_sql}"
2315        elif not op_sql:
2316            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2317                return f" {this_sql}"
2318
2319            return f", {this_sql}"
2320
2321        if op_sql != "STRAIGHT_JOIN":
2322            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2323
2324        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2325        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2326
2327    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2328        args = self.expressions(expression, flat=True)
2329        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2330        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2331
2332    def lateral_op(self, expression: exp.Lateral) -> str:
2333        cross_apply = expression.args.get("cross_apply")
2334
2335        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2336        if cross_apply is True:
2337            op = "INNER JOIN "
2338        elif cross_apply is False:
2339            op = "LEFT JOIN "
2340        else:
2341            op = ""
2342
2343        return f"{op}LATERAL"
2344
2345    def lateral_sql(self, expression: exp.Lateral) -> str:
2346        this = self.sql(expression, "this")
2347
2348        if expression.args.get("view"):
2349            alias = expression.args["alias"]
2350            columns = self.expressions(alias, key="columns", flat=True)
2351            table = f" {alias.name}" if alias.name else ""
2352            columns = f" AS {columns}" if columns else ""
2353            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2354            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2355
2356        alias = self.sql(expression, "alias")
2357        alias = f" AS {alias}" if alias else ""
2358
2359        ordinality = expression.args.get("ordinality") or ""
2360        if ordinality:
2361            ordinality = f" WITH ORDINALITY{alias}"
2362            alias = ""
2363
2364        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2365
2366    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2367        this = self.sql(expression, "this")
2368
2369        args = [
2370            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2371            for e in (expression.args.get(k) for k in ("offset", "expression"))
2372            if e
2373        ]
2374
2375        args_sql = ", ".join(self.sql(e) for e in args)
2376        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2377        expressions = self.expressions(expression, flat=True)
2378        limit_options = self.sql(expression, "limit_options")
2379        expressions = f" BY {expressions}" if expressions else ""
2380
2381        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2382
2383    def offset_sql(self, expression: exp.Offset) -> str:
2384        this = self.sql(expression, "this")
2385        value = expression.expression
2386        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2387        expressions = self.expressions(expression, flat=True)
2388        expressions = f" BY {expressions}" if expressions else ""
2389        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2390
2391    def setitem_sql(self, expression: exp.SetItem) -> str:
2392        kind = self.sql(expression, "kind")
2393        kind = f"{kind} " if kind else ""
2394        this = self.sql(expression, "this")
2395        expressions = self.expressions(expression)
2396        collate = self.sql(expression, "collate")
2397        collate = f" COLLATE {collate}" if collate else ""
2398        global_ = "GLOBAL " if expression.args.get("global") else ""
2399        return f"{global_}{kind}{this}{expressions}{collate}"
2400
2401    def set_sql(self, expression: exp.Set) -> str:
2402        expressions = f" {self.expressions(expression, flat=True)}"
2403        tag = " TAG" if expression.args.get("tag") else ""
2404        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2405
2406    def pragma_sql(self, expression: exp.Pragma) -> str:
2407        return f"PRAGMA {self.sql(expression, 'this')}"
2408
2409    def lock_sql(self, expression: exp.Lock) -> str:
2410        if not self.LOCKING_READS_SUPPORTED:
2411            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2412            return ""
2413
2414        update = expression.args["update"]
2415        key = expression.args.get("key")
2416        if update:
2417            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2418        else:
2419            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2420        expressions = self.expressions(expression, flat=True)
2421        expressions = f" OF {expressions}" if expressions else ""
2422        wait = expression.args.get("wait")
2423
2424        if wait is not None:
2425            if isinstance(wait, exp.Literal):
2426                wait = f" WAIT {self.sql(wait)}"
2427            else:
2428                wait = " NOWAIT" if wait else " SKIP LOCKED"
2429
2430        return f"{lock_type}{expressions}{wait or ''}"
2431
2432    def literal_sql(self, expression: exp.Literal) -> str:
2433        text = expression.this or ""
2434        if expression.is_string:
2435            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2436        return text
2437
2438    def escape_str(self, text: str, escape_backslash: bool = True) -> str:
2439        if self.dialect.ESCAPED_SEQUENCES:
2440            to_escaped = self.dialect.ESCAPED_SEQUENCES
2441            text = "".join(
2442                to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text
2443            )
2444
2445        return self._replace_line_breaks(text).replace(
2446            self.dialect.QUOTE_END, self._escaped_quote_end
2447        )
2448
2449    def loaddata_sql(self, expression: exp.LoadData) -> str:
2450        local = " LOCAL" if expression.args.get("local") else ""
2451        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2452        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2453        this = f" INTO TABLE {self.sql(expression, 'this')}"
2454        partition = self.sql(expression, "partition")
2455        partition = f" {partition}" if partition else ""
2456        input_format = self.sql(expression, "input_format")
2457        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2458        serde = self.sql(expression, "serde")
2459        serde = f" SERDE {serde}" if serde else ""
2460        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2461
2462    def null_sql(self, *_) -> str:
2463        return "NULL"
2464
2465    def boolean_sql(self, expression: exp.Boolean) -> str:
2466        return "TRUE" if expression.this else "FALSE"
2467
2468    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2469        this = self.sql(expression, "this")
2470        this = f"{this} " if this else this
2471        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2472        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
2473
2474    def withfill_sql(self, expression: exp.WithFill) -> str:
2475        from_sql = self.sql(expression, "from")
2476        from_sql = f" FROM {from_sql}" if from_sql else ""
2477        to_sql = self.sql(expression, "to")
2478        to_sql = f" TO {to_sql}" if to_sql else ""
2479        step_sql = self.sql(expression, "step")
2480        step_sql = f" STEP {step_sql}" if step_sql else ""
2481        interpolated_values = [
2482            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2483            if isinstance(e, exp.Alias)
2484            else self.sql(e, "this")
2485            for e in expression.args.get("interpolate") or []
2486        ]
2487        interpolate = (
2488            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2489        )
2490        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2491
2492    def cluster_sql(self, expression: exp.Cluster) -> str:
2493        return self.op_expressions("CLUSTER BY", expression)
2494
2495    def distribute_sql(self, expression: exp.Distribute) -> str:
2496        return self.op_expressions("DISTRIBUTE BY", expression)
2497
2498    def sort_sql(self, expression: exp.Sort) -> str:
2499        return self.op_expressions("SORT BY", expression)
2500
2501    def ordered_sql(self, expression: exp.Ordered) -> str:
2502        desc = expression.args.get("desc")
2503        asc = not desc
2504
2505        nulls_first = expression.args.get("nulls_first")
2506        nulls_last = not nulls_first
2507        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2508        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2509        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2510
2511        this = self.sql(expression, "this")
2512
2513        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2514        nulls_sort_change = ""
2515        if nulls_first and (
2516            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2517        ):
2518            nulls_sort_change = " NULLS FIRST"
2519        elif (
2520            nulls_last
2521            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2522            and not nulls_are_last
2523        ):
2524            nulls_sort_change = " NULLS LAST"
2525
2526        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2527        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2528            window = expression.find_ancestor(exp.Window, exp.Select)
2529            if isinstance(window, exp.Window) and window.args.get("spec"):
2530                self.unsupported(
2531                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2532                )
2533                nulls_sort_change = ""
2534            elif self.NULL_ORDERING_SUPPORTED is False and (
2535                (asc and nulls_sort_change == " NULLS LAST")
2536                or (desc and nulls_sort_change == " NULLS FIRST")
2537            ):
2538                # BigQuery does not allow these ordering/nulls combinations when used under
2539                # an aggregation func or under a window containing one
2540                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2541
2542                if isinstance(ancestor, exp.Window):
2543                    ancestor = ancestor.this
2544                if isinstance(ancestor, exp.AggFunc):
2545                    self.unsupported(
2546                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2547                    )
2548                    nulls_sort_change = ""
2549            elif self.NULL_ORDERING_SUPPORTED is None:
2550                if expression.this.is_int:
2551                    self.unsupported(
2552                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2553                    )
2554                elif not isinstance(expression.this, exp.Rand):
2555                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2556                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2557                nulls_sort_change = ""
2558
2559        with_fill = self.sql(expression, "with_fill")
2560        with_fill = f" {with_fill}" if with_fill else ""
2561
2562        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2563
2564    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2565        window_frame = self.sql(expression, "window_frame")
2566        window_frame = f"{window_frame} " if window_frame else ""
2567
2568        this = self.sql(expression, "this")
2569
2570        return f"{window_frame}{this}"
2571
2572    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2573        partition = self.partition_by_sql(expression)
2574        order = self.sql(expression, "order")
2575        measures = self.expressions(expression, key="measures")
2576        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2577        rows = self.sql(expression, "rows")
2578        rows = self.seg(rows) if rows else ""
2579        after = self.sql(expression, "after")
2580        after = self.seg(after) if after else ""
2581        pattern = self.sql(expression, "pattern")
2582        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2583        definition_sqls = [
2584            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2585            for definition in expression.args.get("define", [])
2586        ]
2587        definitions = self.expressions(sqls=definition_sqls)
2588        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2589        body = "".join(
2590            (
2591                partition,
2592                order,
2593                measures,
2594                rows,
2595                after,
2596                pattern,
2597                define,
2598            )
2599        )
2600        alias = self.sql(expression, "alias")
2601        alias = f" {alias}" if alias else ""
2602        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2603
2604    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2605        limit = expression.args.get("limit")
2606
2607        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2608            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2609        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2610            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2611
2612        return csv(
2613            *sqls,
2614            *[self.sql(join) for join in expression.args.get("joins") or []],
2615            self.sql(expression, "match"),
2616            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2617            self.sql(expression, "prewhere"),
2618            self.sql(expression, "where"),
2619            self.sql(expression, "connect"),
2620            self.sql(expression, "group"),
2621            self.sql(expression, "having"),
2622            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2623            self.sql(expression, "order"),
2624            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2625            *self.after_limit_modifiers(expression),
2626            self.options_modifier(expression),
2627            self.for_modifiers(expression),
2628            sep="",
2629        )
2630
2631    def options_modifier(self, expression: exp.Expression) -> str:
2632        options = self.expressions(expression, key="options")
2633        return f" {options}" if options else ""
2634
2635    def for_modifiers(self, expression: exp.Expression) -> str:
2636        for_modifiers = self.expressions(expression, key="for")
2637        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
2638
2639    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2640        self.unsupported("Unsupported query option.")
2641        return ""
2642
2643    def offset_limit_modifiers(
2644        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2645    ) -> t.List[str]:
2646        return [
2647            self.sql(expression, "offset") if fetch else self.sql(limit),
2648            self.sql(limit) if fetch else self.sql(expression, "offset"),
2649        ]
2650
2651    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2652        locks = self.expressions(expression, key="locks", sep=" ")
2653        locks = f" {locks}" if locks else ""
2654        return [locks, self.sql(expression, "sample")]
2655
2656    def select_sql(self, expression: exp.Select) -> str:
2657        into = expression.args.get("into")
2658        if not self.SUPPORTS_SELECT_INTO and into:
2659            into.pop()
2660
2661        hint = self.sql(expression, "hint")
2662        distinct = self.sql(expression, "distinct")
2663        distinct = f" {distinct}" if distinct else ""
2664        kind = self.sql(expression, "kind")
2665
2666        limit = expression.args.get("limit")
2667        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2668            top = self.limit_sql(limit, top=True)
2669            limit.pop()
2670        else:
2671            top = ""
2672
2673        expressions = self.expressions(expression)
2674
2675        if kind:
2676            if kind in self.SELECT_KINDS:
2677                kind = f" AS {kind}"
2678            else:
2679                if kind == "STRUCT":
2680                    expressions = self.expressions(
2681                        sqls=[
2682                            self.sql(
2683                                exp.Struct(
2684                                    expressions=[
2685                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2686                                        if isinstance(e, exp.Alias)
2687                                        else e
2688                                        for e in expression.expressions
2689                                    ]
2690                                )
2691                            )
2692                        ]
2693                    )
2694                kind = ""
2695
2696        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2697        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2698
2699        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2700        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2701        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2702        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2703        sql = self.query_modifiers(
2704            expression,
2705            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2706            self.sql(expression, "into", comment=False),
2707            self.sql(expression, "from", comment=False),
2708        )
2709
2710        # If both the CTE and SELECT clauses have comments, generate the latter earlier
2711        if expression.args.get("with"):
2712            sql = self.maybe_comment(sql, expression)
2713            expression.pop_comments()
2714
2715        sql = self.prepend_ctes(expression, sql)
2716
2717        if not self.SUPPORTS_SELECT_INTO and into:
2718            if into.args.get("temporary"):
2719                table_kind = " TEMPORARY"
2720            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
2721                table_kind = " UNLOGGED"
2722            else:
2723                table_kind = ""
2724            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
2725
2726        return sql
2727
2728    def schema_sql(self, expression: exp.Schema) -> str:
2729        this = self.sql(expression, "this")
2730        sql = self.schema_columns_sql(expression)
2731        return f"{this} {sql}" if this and sql else this or sql
2732
2733    def schema_columns_sql(self, expression: exp.Schema) -> str:
2734        if expression.expressions:
2735            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2736        return ""
2737
2738    def star_sql(self, expression: exp.Star) -> str:
2739        except_ = self.expressions(expression, key="except", flat=True)
2740        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2741        replace = self.expressions(expression, key="replace", flat=True)
2742        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
2743        rename = self.expressions(expression, key="rename", flat=True)
2744        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
2745        return f"*{except_}{replace}{rename}"
2746
2747    def parameter_sql(self, expression: exp.Parameter) -> str:
2748        this = self.sql(expression, "this")
2749        return f"{self.PARAMETER_TOKEN}{this}"
2750
2751    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2752        this = self.sql(expression, "this")
2753        kind = expression.text("kind")
2754        if kind:
2755            kind = f"{kind}."
2756        return f"@@{kind}{this}"
2757
2758    def placeholder_sql(self, expression: exp.Placeholder) -> str:
2759        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
2760
2761    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2762        alias = self.sql(expression, "alias")
2763        alias = f"{sep}{alias}" if alias else ""
2764        sample = self.sql(expression, "sample")
2765        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
2766            alias = f"{sample}{alias}"
2767
2768            # Set to None so it's not generated again by self.query_modifiers()
2769            expression.set("sample", None)
2770
2771        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2772        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2773        return self.prepend_ctes(expression, sql)
2774
2775    def qualify_sql(self, expression: exp.Qualify) -> str:
2776        this = self.indent(self.sql(expression, "this"))
2777        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
2778
2779    def unnest_sql(self, expression: exp.Unnest) -> str:
2780        args = self.expressions(expression, flat=True)
2781
2782        alias = expression.args.get("alias")
2783        offset = expression.args.get("offset")
2784
2785        if self.UNNEST_WITH_ORDINALITY:
2786            if alias and isinstance(offset, exp.Expression):
2787                alias.append("columns", offset)
2788
2789        if alias and self.dialect.UNNEST_COLUMN_ONLY:
2790            columns = alias.columns
2791            alias = self.sql(columns[0]) if columns else ""
2792        else:
2793            alias = self.sql(alias)
2794
2795        alias = f" AS {alias}" if alias else alias
2796        if self.UNNEST_WITH_ORDINALITY:
2797            suffix = f" WITH ORDINALITY{alias}" if offset else alias
2798        else:
2799            if isinstance(offset, exp.Expression):
2800                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
2801            elif offset:
2802                suffix = f"{alias} WITH OFFSET"
2803            else:
2804                suffix = alias
2805
2806        return f"UNNEST({args}){suffix}"
2807
2808    def prewhere_sql(self, expression: exp.PreWhere) -> str:
2809        return ""
2810
2811    def where_sql(self, expression: exp.Where) -> str:
2812        this = self.indent(self.sql(expression, "this"))
2813        return f"{self.seg('WHERE')}{self.sep()}{this}"
2814
2815    def window_sql(self, expression: exp.Window) -> str:
2816        this = self.sql(expression, "this")
2817        partition = self.partition_by_sql(expression)
2818        order = expression.args.get("order")
2819        order = self.order_sql(order, flat=True) if order else ""
2820        spec = self.sql(expression, "spec")
2821        alias = self.sql(expression, "alias")
2822        over = self.sql(expression, "over") or "OVER"
2823
2824        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
2825
2826        first = expression.args.get("first")
2827        if first is None:
2828            first = ""
2829        else:
2830            first = "FIRST" if first else "LAST"
2831
2832        if not partition and not order and not spec and alias:
2833            return f"{this} {alias}"
2834
2835        args = self.format_args(
2836            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
2837        )
2838        return f"{this} ({args})"
2839
2840    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
2841        partition = self.expressions(expression, key="partition_by", flat=True)
2842        return f"PARTITION BY {partition}" if partition else ""
2843
2844    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
2845        kind = self.sql(expression, "kind")
2846        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
2847        end = (
2848            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
2849            or "CURRENT ROW"
2850        )
2851
2852        window_spec = f"{kind} BETWEEN {start} AND {end}"
2853
2854        exclude = self.sql(expression, "exclude")
2855        if exclude:
2856            if self.SUPPORTS_WINDOW_EXCLUDE:
2857                window_spec += f" EXCLUDE {exclude}"
2858            else:
2859                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
2860
2861        return window_spec
2862
2863    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
2864        this = self.sql(expression, "this")
2865        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
2866        return f"{this} WITHIN GROUP ({expression_sql})"
2867
2868    def between_sql(self, expression: exp.Between) -> str:
2869        this = self.sql(expression, "this")
2870        low = self.sql(expression, "low")
2871        high = self.sql(expression, "high")
2872        symmetric = expression.args.get("symmetric")
2873
2874        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
2875            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
2876
2877        flag = (
2878            " SYMMETRIC"
2879            if symmetric
2880            else " ASYMMETRIC"
2881            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
2882            else ""  # silently drop ASYMMETRIC – semantics identical
2883        )
2884        return f"{this} BETWEEN{flag} {low} AND {high}"
2885
2886    def bracket_offset_expressions(
2887        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
2888    ) -> t.List[exp.Expression]:
2889        return apply_index_offset(
2890            expression.this,
2891            expression.expressions,
2892            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
2893            dialect=self.dialect,
2894        )
2895
2896    def bracket_sql(self, expression: exp.Bracket) -> str:
2897        expressions = self.bracket_offset_expressions(expression)
2898        expressions_sql = ", ".join(self.sql(e) for e in expressions)
2899        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
2900
2901    def all_sql(self, expression: exp.All) -> str:
2902        return f"ALL {self.wrap(expression)}"
2903
2904    def any_sql(self, expression: exp.Any) -> str:
2905        this = self.sql(expression, "this")
2906        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
2907            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
2908                this = self.wrap(this)
2909            return f"ANY{this}"
2910        return f"ANY {this}"
2911
2912    def exists_sql(self, expression: exp.Exists) -> str:
2913        return f"EXISTS{self.wrap(expression)}"
2914
2915    def case_sql(self, expression: exp.Case) -> str:
2916        this = self.sql(expression, "this")
2917        statements = [f"CASE {this}" if this else "CASE"]
2918
2919        for e in expression.args["ifs"]:
2920            statements.append(f"WHEN {self.sql(e, 'this')}")
2921            statements.append(f"THEN {self.sql(e, 'true')}")
2922
2923        default = self.sql(expression, "default")
2924
2925        if default:
2926            statements.append(f"ELSE {default}")
2927
2928        statements.append("END")
2929
2930        if self.pretty and self.too_wide(statements):
2931            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
2932
2933        return " ".join(statements)
2934
2935    def constraint_sql(self, expression: exp.Constraint) -> str:
2936        this = self.sql(expression, "this")
2937        expressions = self.expressions(expression, flat=True)
2938        return f"CONSTRAINT {this} {expressions}"
2939
2940    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
2941        order = expression.args.get("order")
2942        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
2943        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
2944
2945    def extract_sql(self, expression: exp.Extract) -> str:
2946        from sqlglot.dialects.dialect import map_date_part
2947
2948        this = (
2949            map_date_part(expression.this, self.dialect)
2950            if self.NORMALIZE_EXTRACT_DATE_PARTS
2951            else expression.this
2952        )
2953        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
2954        expression_sql = self.sql(expression, "expression")
2955
2956        return f"EXTRACT({this_sql} FROM {expression_sql})"
2957
2958    def trim_sql(self, expression: exp.Trim) -> str:
2959        trim_type = self.sql(expression, "position")
2960
2961        if trim_type == "LEADING":
2962            func_name = "LTRIM"
2963        elif trim_type == "TRAILING":
2964            func_name = "RTRIM"
2965        else:
2966            func_name = "TRIM"
2967
2968        return self.func(func_name, expression.this, expression.expression)
2969
2970    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
2971        args = expression.expressions
2972        if isinstance(expression, exp.ConcatWs):
2973            args = args[1:]  # Skip the delimiter
2974
2975        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
2976            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
2977
2978        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
2979            args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args]
2980
2981        return args
2982
2983    def concat_sql(self, expression: exp.Concat) -> str:
2984        expressions = self.convert_concat_args(expression)
2985
2986        # Some dialects don't allow a single-argument CONCAT call
2987        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
2988            return self.sql(expressions[0])
2989
2990        return self.func("CONCAT", *expressions)
2991
2992    def concatws_sql(self, expression: exp.ConcatWs) -> str:
2993        return self.func(
2994            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
2995        )
2996
2997    def check_sql(self, expression: exp.Check) -> str:
2998        this = self.sql(expression, key="this")
2999        return f"CHECK ({this})"
3000
3001    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3002        expressions = self.expressions(expression, flat=True)
3003        expressions = f" ({expressions})" if expressions else ""
3004        reference = self.sql(expression, "reference")
3005        reference = f" {reference}" if reference else ""
3006        delete = self.sql(expression, "delete")
3007        delete = f" ON DELETE {delete}" if delete else ""
3008        update = self.sql(expression, "update")
3009        update = f" ON UPDATE {update}" if update else ""
3010        options = self.expressions(expression, key="options", flat=True, sep=" ")
3011        options = f" {options}" if options else ""
3012        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3013
3014    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3015        expressions = self.expressions(expression, flat=True)
3016        include = self.sql(expression, "include")
3017        options = self.expressions(expression, key="options", flat=True, sep=" ")
3018        options = f" {options}" if options else ""
3019        return f"PRIMARY KEY ({expressions}){include}{options}"
3020
3021    def if_sql(self, expression: exp.If) -> str:
3022        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3023
3024    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3025        modifier = expression.args.get("modifier")
3026        modifier = f" {modifier}" if modifier else ""
3027        return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3028
3029    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3030        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3031
3032    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3033        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3034
3035        if expression.args.get("escape"):
3036            path = self.escape_str(path)
3037
3038        if self.QUOTE_JSON_PATH:
3039            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3040
3041        return path
3042
3043    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3044        if isinstance(expression, exp.JSONPathPart):
3045            transform = self.TRANSFORMS.get(expression.__class__)
3046            if not callable(transform):
3047                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3048                return ""
3049
3050            return transform(self, expression)
3051
3052        if isinstance(expression, int):
3053            return str(expression)
3054
3055        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3056            escaped = expression.replace("'", "\\'")
3057            escaped = f"\\'{expression}\\'"
3058        else:
3059            escaped = expression.replace('"', '\\"')
3060            escaped = f'"{escaped}"'
3061
3062        return escaped
3063
3064    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3065        return f"{self.sql(expression, 'this')} FORMAT JSON"
3066
3067    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3068        # Output the Teradata column FORMAT override.
3069        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3070        this = self.sql(expression, "this")
3071        fmt = self.sql(expression, "format")
3072        return f"{this} (FORMAT {fmt})"
3073
3074    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3075        null_handling = expression.args.get("null_handling")
3076        null_handling = f" {null_handling}" if null_handling else ""
3077
3078        unique_keys = expression.args.get("unique_keys")
3079        if unique_keys is not None:
3080            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3081        else:
3082            unique_keys = ""
3083
3084        return_type = self.sql(expression, "return_type")
3085        return_type = f" RETURNING {return_type}" if return_type else ""
3086        encoding = self.sql(expression, "encoding")
3087        encoding = f" ENCODING {encoding}" if encoding else ""
3088
3089        return self.func(
3090            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3091            *expression.expressions,
3092            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3093        )
3094
3095    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3096        return self.jsonobject_sql(expression)
3097
3098    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3099        null_handling = expression.args.get("null_handling")
3100        null_handling = f" {null_handling}" if null_handling else ""
3101        return_type = self.sql(expression, "return_type")
3102        return_type = f" RETURNING {return_type}" if return_type else ""
3103        strict = " STRICT" if expression.args.get("strict") else ""
3104        return self.func(
3105            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3106        )
3107
3108    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3109        this = self.sql(expression, "this")
3110        order = self.sql(expression, "order")
3111        null_handling = expression.args.get("null_handling")
3112        null_handling = f" {null_handling}" if null_handling else ""
3113        return_type = self.sql(expression, "return_type")
3114        return_type = f" RETURNING {return_type}" if return_type else ""
3115        strict = " STRICT" if expression.args.get("strict") else ""
3116        return self.func(
3117            "JSON_ARRAYAGG",
3118            this,
3119            suffix=f"{order}{null_handling}{return_type}{strict})",
3120        )
3121
3122    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3123        path = self.sql(expression, "path")
3124        path = f" PATH {path}" if path else ""
3125        nested_schema = self.sql(expression, "nested_schema")
3126
3127        if nested_schema:
3128            return f"NESTED{path} {nested_schema}"
3129
3130        this = self.sql(expression, "this")
3131        kind = self.sql(expression, "kind")
3132        kind = f" {kind}" if kind else ""
3133        return f"{this}{kind}{path}"
3134
3135    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3136        return self.func("COLUMNS", *expression.expressions)
3137
3138    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3139        this = self.sql(expression, "this")
3140        path = self.sql(expression, "path")
3141        path = f", {path}" if path else ""
3142        error_handling = expression.args.get("error_handling")
3143        error_handling = f" {error_handling}" if error_handling else ""
3144        empty_handling = expression.args.get("empty_handling")
3145        empty_handling = f" {empty_handling}" if empty_handling else ""
3146        schema = self.sql(expression, "schema")
3147        return self.func(
3148            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3149        )
3150
3151    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3152        this = self.sql(expression, "this")
3153        kind = self.sql(expression, "kind")
3154        path = self.sql(expression, "path")
3155        path = f" {path}" if path else ""
3156        as_json = " AS JSON" if expression.args.get("as_json") else ""
3157        return f"{this} {kind}{path}{as_json}"
3158
3159    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3160        this = self.sql(expression, "this")
3161        path = self.sql(expression, "path")
3162        path = f", {path}" if path else ""
3163        expressions = self.expressions(expression)
3164        with_ = (
3165            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3166            if expressions
3167            else ""
3168        )
3169        return f"OPENJSON({this}{path}){with_}"
3170
3171    def in_sql(self, expression: exp.In) -> str:
3172        query = expression.args.get("query")
3173        unnest = expression.args.get("unnest")
3174        field = expression.args.get("field")
3175        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3176
3177        if query:
3178            in_sql = self.sql(query)
3179        elif unnest:
3180            in_sql = self.in_unnest_op(unnest)
3181        elif field:
3182            in_sql = self.sql(field)
3183        else:
3184            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3185
3186        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3187
3188    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3189        return f"(SELECT {self.sql(unnest)})"
3190
3191    def interval_sql(self, expression: exp.Interval) -> str:
3192        unit = self.sql(expression, "unit")
3193        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3194            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3195        unit = f" {unit}" if unit else ""
3196
3197        if self.SINGLE_STRING_INTERVAL:
3198            this = expression.this.name if expression.this else ""
3199            return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}"
3200
3201        this = self.sql(expression, "this")
3202        if this:
3203            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3204            this = f" {this}" if unwrapped else f" ({this})"
3205
3206        return f"INTERVAL{this}{unit}"
3207
3208    def return_sql(self, expression: exp.Return) -> str:
3209        return f"RETURN {self.sql(expression, 'this')}"
3210
3211    def reference_sql(self, expression: exp.Reference) -> str:
3212        this = self.sql(expression, "this")
3213        expressions = self.expressions(expression, flat=True)
3214        expressions = f"({expressions})" if expressions else ""
3215        options = self.expressions(expression, key="options", flat=True, sep=" ")
3216        options = f" {options}" if options else ""
3217        return f"REFERENCES {this}{expressions}{options}"
3218
3219    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3220        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3221        parent = expression.parent
3222        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3223        return self.func(
3224            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3225        )
3226
3227    def paren_sql(self, expression: exp.Paren) -> str:
3228        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3229        return f"({sql}{self.seg(')', sep='')}"
3230
3231    def neg_sql(self, expression: exp.Neg) -> str:
3232        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3233        this_sql = self.sql(expression, "this")
3234        sep = " " if this_sql[0] == "-" else ""
3235        return f"-{sep}{this_sql}"
3236
3237    def not_sql(self, expression: exp.Not) -> str:
3238        return f"NOT {self.sql(expression, 'this')}"
3239
3240    def alias_sql(self, expression: exp.Alias) -> str:
3241        alias = self.sql(expression, "alias")
3242        alias = f" AS {alias}" if alias else ""
3243        return f"{self.sql(expression, 'this')}{alias}"
3244
3245    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3246        alias = expression.args["alias"]
3247
3248        parent = expression.parent
3249        pivot = parent and parent.parent
3250
3251        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3252            identifier_alias = isinstance(alias, exp.Identifier)
3253            literal_alias = isinstance(alias, exp.Literal)
3254
3255            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3256                alias.replace(exp.Literal.string(alias.output_name))
3257            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3258                alias.replace(exp.to_identifier(alias.output_name))
3259
3260        return self.alias_sql(expression)
3261
3262    def aliases_sql(self, expression: exp.Aliases) -> str:
3263        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3264
3265    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3266        this = self.sql(expression, "this")
3267        index = self.sql(expression, "expression")
3268        return f"{this} AT {index}"
3269
3270    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3271        this = self.sql(expression, "this")
3272        zone = self.sql(expression, "zone")
3273        return f"{this} AT TIME ZONE {zone}"
3274
3275    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3276        this = self.sql(expression, "this")
3277        zone = self.sql(expression, "zone")
3278        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3279
3280    def add_sql(self, expression: exp.Add) -> str:
3281        return self.binary(expression, "+")
3282
3283    def and_sql(
3284        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3285    ) -> str:
3286        return self.connector_sql(expression, "AND", stack)
3287
3288    def or_sql(
3289        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3290    ) -> str:
3291        return self.connector_sql(expression, "OR", stack)
3292
3293    def xor_sql(
3294        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3295    ) -> str:
3296        return self.connector_sql(expression, "XOR", stack)
3297
3298    def connector_sql(
3299        self,
3300        expression: exp.Connector,
3301        op: str,
3302        stack: t.Optional[t.List[str | exp.Expression]] = None,
3303    ) -> str:
3304        if stack is not None:
3305            if expression.expressions:
3306                stack.append(self.expressions(expression, sep=f" {op} "))
3307            else:
3308                stack.append(expression.right)
3309                if expression.comments and self.comments:
3310                    for comment in expression.comments:
3311                        if comment:
3312                            op += f" /*{self.sanitize_comment(comment)}*/"
3313                stack.extend((op, expression.left))
3314            return op
3315
3316        stack = [expression]
3317        sqls: t.List[str] = []
3318        ops = set()
3319
3320        while stack:
3321            node = stack.pop()
3322            if isinstance(node, exp.Connector):
3323                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3324            else:
3325                sql = self.sql(node)
3326                if sqls and sqls[-1] in ops:
3327                    sqls[-1] += f" {sql}"
3328                else:
3329                    sqls.append(sql)
3330
3331        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3332        return sep.join(sqls)
3333
3334    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3335        return self.binary(expression, "&")
3336
3337    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3338        return self.binary(expression, "<<")
3339
3340    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3341        return f"~{self.sql(expression, 'this')}"
3342
3343    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3344        return self.binary(expression, "|")
3345
3346    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3347        return self.binary(expression, ">>")
3348
3349    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3350        return self.binary(expression, "^")
3351
3352    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3353        format_sql = self.sql(expression, "format")
3354        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3355        to_sql = self.sql(expression, "to")
3356        to_sql = f" {to_sql}" if to_sql else ""
3357        action = self.sql(expression, "action")
3358        action = f" {action}" if action else ""
3359        default = self.sql(expression, "default")
3360        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3361        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3362
3363    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3364        zone = self.sql(expression, "this")
3365        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3366
3367    def collate_sql(self, expression: exp.Collate) -> str:
3368        if self.COLLATE_IS_FUNC:
3369            return self.function_fallback_sql(expression)
3370        return self.binary(expression, "COLLATE")
3371
3372    def command_sql(self, expression: exp.Command) -> str:
3373        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3374
3375    def comment_sql(self, expression: exp.Comment) -> str:
3376        this = self.sql(expression, "this")
3377        kind = expression.args["kind"]
3378        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3379        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3380        expression_sql = self.sql(expression, "expression")
3381        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3382
3383    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3384        this = self.sql(expression, "this")
3385        delete = " DELETE" if expression.args.get("delete") else ""
3386        recompress = self.sql(expression, "recompress")
3387        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3388        to_disk = self.sql(expression, "to_disk")
3389        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3390        to_volume = self.sql(expression, "to_volume")
3391        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3392        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3393
3394    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3395        where = self.sql(expression, "where")
3396        group = self.sql(expression, "group")
3397        aggregates = self.expressions(expression, key="aggregates")
3398        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3399
3400        if not (where or group or aggregates) and len(expression.expressions) == 1:
3401            return f"TTL {self.expressions(expression, flat=True)}"
3402
3403        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3404
3405    def transaction_sql(self, expression: exp.Transaction) -> str:
3406        return "BEGIN"
3407
3408    def commit_sql(self, expression: exp.Commit) -> str:
3409        chain = expression.args.get("chain")
3410        if chain is not None:
3411            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3412
3413        return f"COMMIT{chain or ''}"
3414
3415    def rollback_sql(self, expression: exp.Rollback) -> str:
3416        savepoint = expression.args.get("savepoint")
3417        savepoint = f" TO {savepoint}" if savepoint else ""
3418        return f"ROLLBACK{savepoint}"
3419
3420    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3421        this = self.sql(expression, "this")
3422
3423        dtype = self.sql(expression, "dtype")
3424        if dtype:
3425            collate = self.sql(expression, "collate")
3426            collate = f" COLLATE {collate}" if collate else ""
3427            using = self.sql(expression, "using")
3428            using = f" USING {using}" if using else ""
3429            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3430            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3431
3432        default = self.sql(expression, "default")
3433        if default:
3434            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3435
3436        comment = self.sql(expression, "comment")
3437        if comment:
3438            return f"ALTER COLUMN {this} COMMENT {comment}"
3439
3440        visible = expression.args.get("visible")
3441        if visible:
3442            return f"ALTER COLUMN {this} SET {visible}"
3443
3444        allow_null = expression.args.get("allow_null")
3445        drop = expression.args.get("drop")
3446
3447        if not drop and not allow_null:
3448            self.unsupported("Unsupported ALTER COLUMN syntax")
3449
3450        if allow_null is not None:
3451            keyword = "DROP" if drop else "SET"
3452            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3453
3454        return f"ALTER COLUMN {this} DROP DEFAULT"
3455
3456    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3457        this = self.sql(expression, "this")
3458
3459        visible = expression.args.get("visible")
3460        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3461
3462        return f"ALTER INDEX {this} {visible_sql}"
3463
3464    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3465        this = self.sql(expression, "this")
3466        if not isinstance(expression.this, exp.Var):
3467            this = f"KEY DISTKEY {this}"
3468        return f"ALTER DISTSTYLE {this}"
3469
3470    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3471        compound = " COMPOUND" if expression.args.get("compound") else ""
3472        this = self.sql(expression, "this")
3473        expressions = self.expressions(expression, flat=True)
3474        expressions = f"({expressions})" if expressions else ""
3475        return f"ALTER{compound} SORTKEY {this or expressions}"
3476
3477    def alterrename_sql(self, expression: exp.AlterRename) -> str:
3478        if not self.RENAME_TABLE_WITH_DB:
3479            # Remove db from tables
3480            expression = expression.transform(
3481                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3482            ).assert_is(exp.AlterRename)
3483        this = self.sql(expression, "this")
3484        return f"RENAME TO {this}"
3485
3486    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3487        exists = " IF EXISTS" if expression.args.get("exists") else ""
3488        old_column = self.sql(expression, "this")
3489        new_column = self.sql(expression, "to")
3490        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3491
3492    def alterset_sql(self, expression: exp.AlterSet) -> str:
3493        exprs = self.expressions(expression, flat=True)
3494        if self.ALTER_SET_WRAPPED:
3495            exprs = f"({exprs})"
3496
3497        return f"SET {exprs}"
3498
3499    def alter_sql(self, expression: exp.Alter) -> str:
3500        actions = expression.args["actions"]
3501
3502        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3503            actions[0], exp.ColumnDef
3504        ):
3505            actions_sql = self.expressions(expression, key="actions", flat=True)
3506            actions_sql = f"ADD {actions_sql}"
3507        else:
3508            actions_list = []
3509            for action in actions:
3510                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3511                    action_sql = self.add_column_sql(action)
3512                else:
3513                    action_sql = self.sql(action)
3514                    if isinstance(action, exp.Query):
3515                        action_sql = f"AS {action_sql}"
3516
3517                actions_list.append(action_sql)
3518
3519            actions_sql = self.format_args(*actions_list).lstrip("\n")
3520
3521        exists = " IF EXISTS" if expression.args.get("exists") else ""
3522        on_cluster = self.sql(expression, "cluster")
3523        on_cluster = f" {on_cluster}" if on_cluster else ""
3524        only = " ONLY" if expression.args.get("only") else ""
3525        options = self.expressions(expression, key="options")
3526        options = f", {options}" if options else ""
3527        kind = self.sql(expression, "kind")
3528        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3529
3530        return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
3531
3532    def add_column_sql(self, expression: exp.Expression) -> str:
3533        sql = self.sql(expression)
3534        if isinstance(expression, exp.Schema):
3535            column_text = " COLUMNS"
3536        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3537            column_text = " COLUMN"
3538        else:
3539            column_text = ""
3540
3541        return f"ADD{column_text} {sql}"
3542
3543    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3544        expressions = self.expressions(expression)
3545        exists = " IF EXISTS " if expression.args.get("exists") else " "
3546        return f"DROP{exists}{expressions}"
3547
3548    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3549        return f"ADD {self.expressions(expression, indent=False)}"
3550
3551    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3552        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3553        location = self.sql(expression, "location")
3554        location = f" {location}" if location else ""
3555        return f"ADD {exists}{self.sql(expression.this)}{location}"
3556
3557    def distinct_sql(self, expression: exp.Distinct) -> str:
3558        this = self.expressions(expression, flat=True)
3559
3560        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3561            case = exp.case()
3562            for arg in expression.expressions:
3563                case = case.when(arg.is_(exp.null()), exp.null())
3564            this = self.sql(case.else_(f"({this})"))
3565
3566        this = f" {this}" if this else ""
3567
3568        on = self.sql(expression, "on")
3569        on = f" ON {on}" if on else ""
3570        return f"DISTINCT{this}{on}"
3571
3572    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3573        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
3574
3575    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3576        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
3577
3578    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3579        this_sql = self.sql(expression, "this")
3580        expression_sql = self.sql(expression, "expression")
3581        kind = "MAX" if expression.args.get("max") else "MIN"
3582        return f"{this_sql} HAVING {kind} {expression_sql}"
3583
3584    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3585        return self.sql(
3586            exp.Cast(
3587                this=exp.Div(this=expression.this, expression=expression.expression),
3588                to=exp.DataType(this=exp.DataType.Type.INT),
3589            )
3590        )
3591
3592    def dpipe_sql(self, expression: exp.DPipe) -> str:
3593        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3594            return self.func(
3595                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3596            )
3597        return self.binary(expression, "||")
3598
3599    def div_sql(self, expression: exp.Div) -> str:
3600        l, r = expression.left, expression.right
3601
3602        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3603            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3604
3605        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3606            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3607                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3608
3609        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3610            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3611                return self.sql(
3612                    exp.cast(
3613                        l / r,
3614                        to=exp.DataType.Type.BIGINT,
3615                    )
3616                )
3617
3618        return self.binary(expression, "/")
3619
3620    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3621        n = exp._wrap(expression.this, exp.Binary)
3622        d = exp._wrap(expression.expression, exp.Binary)
3623        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
3624
3625    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3626        return self.binary(expression, "OVERLAPS")
3627
3628    def distance_sql(self, expression: exp.Distance) -> str:
3629        return self.binary(expression, "<->")
3630
3631    def dot_sql(self, expression: exp.Dot) -> str:
3632        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
3633
3634    def eq_sql(self, expression: exp.EQ) -> str:
3635        return self.binary(expression, "=")
3636
3637    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3638        return self.binary(expression, ":=")
3639
3640    def escape_sql(self, expression: exp.Escape) -> str:
3641        return self.binary(expression, "ESCAPE")
3642
3643    def glob_sql(self, expression: exp.Glob) -> str:
3644        return self.binary(expression, "GLOB")
3645
3646    def gt_sql(self, expression: exp.GT) -> str:
3647        return self.binary(expression, ">")
3648
3649    def gte_sql(self, expression: exp.GTE) -> str:
3650        return self.binary(expression, ">=")
3651
3652    def ilike_sql(self, expression: exp.ILike) -> str:
3653        return self.binary(expression, "ILIKE")
3654
3655    def ilikeany_sql(self, expression: exp.ILikeAny) -> str:
3656        return self.binary(expression, "ILIKE ANY")
3657
3658    def is_sql(self, expression: exp.Is) -> str:
3659        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
3660            return self.sql(
3661                expression.this if expression.expression.this else exp.not_(expression.this)
3662            )
3663        return self.binary(expression, "IS")
3664
3665    def like_sql(self, expression: exp.Like) -> str:
3666        return self.binary(expression, "LIKE")
3667
3668    def likeany_sql(self, expression: exp.LikeAny) -> str:
3669        return self.binary(expression, "LIKE ANY")
3670
3671    def similarto_sql(self, expression: exp.SimilarTo) -> str:
3672        return self.binary(expression, "SIMILAR TO")
3673
3674    def lt_sql(self, expression: exp.LT) -> str:
3675        return self.binary(expression, "<")
3676
3677    def lte_sql(self, expression: exp.LTE) -> str:
3678        return self.binary(expression, "<=")
3679
3680    def mod_sql(self, expression: exp.Mod) -> str:
3681        return self.binary(expression, "%")
3682
3683    def mul_sql(self, expression: exp.Mul) -> str:
3684        return self.binary(expression, "*")
3685
3686    def neq_sql(self, expression: exp.NEQ) -> str:
3687        return self.binary(expression, "<>")
3688
3689    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
3690        return self.binary(expression, "IS NOT DISTINCT FROM")
3691
3692    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3693        return self.binary(expression, "IS DISTINCT FROM")
3694
3695    def slice_sql(self, expression: exp.Slice) -> str:
3696        return self.binary(expression, ":")
3697
3698    def sub_sql(self, expression: exp.Sub) -> str:
3699        return self.binary(expression, "-")
3700
3701    def trycast_sql(self, expression: exp.TryCast) -> str:
3702        return self.cast_sql(expression, safe_prefix="TRY_")
3703
3704    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
3705        return self.cast_sql(expression)
3706
3707    def try_sql(self, expression: exp.Try) -> str:
3708        if not self.TRY_SUPPORTED:
3709            self.unsupported("Unsupported TRY function")
3710            return self.sql(expression, "this")
3711
3712        return self.func("TRY", expression.this)
3713
3714    def log_sql(self, expression: exp.Log) -> str:
3715        this = expression.this
3716        expr = expression.expression
3717
3718        if self.dialect.LOG_BASE_FIRST is False:
3719            this, expr = expr, this
3720        elif self.dialect.LOG_BASE_FIRST is None and expr:
3721            if this.name in ("2", "10"):
3722                return self.func(f"LOG{this.name}", expr)
3723
3724            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
3725
3726        return self.func("LOG", this, expr)
3727
3728    def use_sql(self, expression: exp.Use) -> str:
3729        kind = self.sql(expression, "kind")
3730        kind = f" {kind}" if kind else ""
3731        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
3732        this = f" {this}" if this else ""
3733        return f"USE{kind}{this}"
3734
3735    def binary(self, expression: exp.Binary, op: str) -> str:
3736        sqls: t.List[str] = []
3737        stack: t.List[t.Union[str, exp.Expression]] = [expression]
3738        binary_type = type(expression)
3739
3740        while stack:
3741            node = stack.pop()
3742
3743            if type(node) is binary_type:
3744                op_func = node.args.get("operator")
3745                if op_func:
3746                    op = f"OPERATOR({self.sql(op_func)})"
3747
3748                stack.append(node.right)
3749                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
3750                stack.append(node.left)
3751            else:
3752                sqls.append(self.sql(node))
3753
3754        return "".join(sqls)
3755
3756    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
3757        to_clause = self.sql(expression, "to")
3758        if to_clause:
3759            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
3760
3761        return self.function_fallback_sql(expression)
3762
3763    def function_fallback_sql(self, expression: exp.Func) -> str:
3764        args = []
3765
3766        for key in expression.arg_types:
3767            arg_value = expression.args.get(key)
3768
3769            if isinstance(arg_value, list):
3770                for value in arg_value:
3771                    args.append(value)
3772            elif arg_value is not None:
3773                args.append(arg_value)
3774
3775        if self.dialect.PRESERVE_ORIGINAL_NAMES:
3776            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
3777        else:
3778            name = expression.sql_name()
3779
3780        return self.func(name, *args)
3781
3782    def func(
3783        self,
3784        name: str,
3785        *args: t.Optional[exp.Expression | str],
3786        prefix: str = "(",
3787        suffix: str = ")",
3788        normalize: bool = True,
3789    ) -> str:
3790        name = self.normalize_func(name) if normalize else name
3791        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
3792
3793    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
3794        arg_sqls = tuple(
3795            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
3796        )
3797        if self.pretty and self.too_wide(arg_sqls):
3798            return self.indent(
3799                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
3800            )
3801        return sep.join(arg_sqls)
3802
3803    def too_wide(self, args: t.Iterable) -> bool:
3804        return sum(len(arg) for arg in args) > self.max_text_width
3805
3806    def format_time(
3807        self,
3808        expression: exp.Expression,
3809        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
3810        inverse_time_trie: t.Optional[t.Dict] = None,
3811    ) -> t.Optional[str]:
3812        return format_time(
3813            self.sql(expression, "format"),
3814            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
3815            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
3816        )
3817
3818    def expressions(
3819        self,
3820        expression: t.Optional[exp.Expression] = None,
3821        key: t.Optional[str] = None,
3822        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
3823        flat: bool = False,
3824        indent: bool = True,
3825        skip_first: bool = False,
3826        skip_last: bool = False,
3827        sep: str = ", ",
3828        prefix: str = "",
3829        dynamic: bool = False,
3830        new_line: bool = False,
3831    ) -> str:
3832        expressions = expression.args.get(key or "expressions") if expression else sqls
3833
3834        if not expressions:
3835            return ""
3836
3837        if flat:
3838            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
3839
3840        num_sqls = len(expressions)
3841        result_sqls = []
3842
3843        for i, e in enumerate(expressions):
3844            sql = self.sql(e, comment=False)
3845            if not sql:
3846                continue
3847
3848            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
3849
3850            if self.pretty:
3851                if self.leading_comma:
3852                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
3853                else:
3854                    result_sqls.append(
3855                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
3856                    )
3857            else:
3858                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
3859
3860        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
3861            if new_line:
3862                result_sqls.insert(0, "")
3863                result_sqls.append("")
3864            result_sql = "\n".join(s.rstrip() for s in result_sqls)
3865        else:
3866            result_sql = "".join(result_sqls)
3867
3868        return (
3869            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
3870            if indent
3871            else result_sql
3872        )
3873
3874    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
3875        flat = flat or isinstance(expression.parent, exp.Properties)
3876        expressions_sql = self.expressions(expression, flat=flat)
3877        if flat:
3878            return f"{op} {expressions_sql}"
3879        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3880
3881    def naked_property(self, expression: exp.Property) -> str:
3882        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
3883        if not property_name:
3884            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
3885        return f"{property_name} {self.sql(expression, 'this')}"
3886
3887    def tag_sql(self, expression: exp.Tag) -> str:
3888        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
3889
3890    def token_sql(self, token_type: TokenType) -> str:
3891        return self.TOKEN_MAPPING.get(token_type, token_type.name)
3892
3893    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
3894        this = self.sql(expression, "this")
3895        expressions = self.no_identify(self.expressions, expression)
3896        expressions = (
3897            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
3898        )
3899        return f"{this}{expressions}" if expressions.strip() != "" else this
3900
3901    def joinhint_sql(self, expression: exp.JoinHint) -> str:
3902        this = self.sql(expression, "this")
3903        expressions = self.expressions(expression, flat=True)
3904        return f"{this}({expressions})"
3905
3906    def kwarg_sql(self, expression: exp.Kwarg) -> str:
3907        return self.binary(expression, "=>")
3908
3909    def when_sql(self, expression: exp.When) -> str:
3910        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
3911        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
3912        condition = self.sql(expression, "condition")
3913        condition = f" AND {condition}" if condition else ""
3914
3915        then_expression = expression.args.get("then")
3916        if isinstance(then_expression, exp.Insert):
3917            this = self.sql(then_expression, "this")
3918            this = f"INSERT {this}" if this else "INSERT"
3919            then = self.sql(then_expression, "expression")
3920            then = f"{this} VALUES {then}" if then else this
3921        elif isinstance(then_expression, exp.Update):
3922            if isinstance(then_expression.args.get("expressions"), exp.Star):
3923                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
3924            else:
3925                then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}"
3926        else:
3927            then = self.sql(then_expression)
3928        return f"WHEN {matched}{source}{condition} THEN {then}"
3929
3930    def whens_sql(self, expression: exp.Whens) -> str:
3931        return self.expressions(expression, sep=" ", indent=False)
3932
3933    def merge_sql(self, expression: exp.Merge) -> str:
3934        table = expression.this
3935        table_alias = ""
3936
3937        hints = table.args.get("hints")
3938        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
3939            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
3940            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
3941
3942        this = self.sql(table)
3943        using = f"USING {self.sql(expression, 'using')}"
3944        on = f"ON {self.sql(expression, 'on')}"
3945        whens = self.sql(expression, "whens")
3946
3947        returning = self.sql(expression, "returning")
3948        if returning:
3949            whens = f"{whens}{returning}"
3950
3951        sep = self.sep()
3952
3953        return self.prepend_ctes(
3954            expression,
3955            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
3956        )
3957
3958    @unsupported_args("format")
3959    def tochar_sql(self, expression: exp.ToChar) -> str:
3960        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
3961
3962    def tonumber_sql(self, expression: exp.ToNumber) -> str:
3963        if not self.SUPPORTS_TO_NUMBER:
3964            self.unsupported("Unsupported TO_NUMBER function")
3965            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3966
3967        fmt = expression.args.get("format")
3968        if not fmt:
3969            self.unsupported("Conversion format is required for TO_NUMBER")
3970            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3971
3972        return self.func("TO_NUMBER", expression.this, fmt)
3973
3974    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
3975        this = self.sql(expression, "this")
3976        kind = self.sql(expression, "kind")
3977        settings_sql = self.expressions(expression, key="settings", sep=" ")
3978        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
3979        return f"{this}({kind}{args})"
3980
3981    def dictrange_sql(self, expression: exp.DictRange) -> str:
3982        this = self.sql(expression, "this")
3983        max = self.sql(expression, "max")
3984        min = self.sql(expression, "min")
3985        return f"{this}(MIN {min} MAX {max})"
3986
3987    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
3988        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
3989
3990    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
3991        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
3992
3993    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
3994    def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str:
3995        return f"UNIQUE KEY ({self.expressions(expression, flat=True)})"
3996
3997    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
3998    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
3999        expressions = self.expressions(expression, flat=True)
4000        expressions = f" {self.wrap(expressions)}" if expressions else ""
4001        buckets = self.sql(expression, "buckets")
4002        kind = self.sql(expression, "kind")
4003        buckets = f" BUCKETS {buckets}" if buckets else ""
4004        order = self.sql(expression, "order")
4005        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4006
4007    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4008        return ""
4009
4010    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4011        expressions = self.expressions(expression, key="expressions", flat=True)
4012        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4013        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4014        buckets = self.sql(expression, "buckets")
4015        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4016
4017    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4018        this = self.sql(expression, "this")
4019        having = self.sql(expression, "having")
4020
4021        if having:
4022            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4023
4024        return self.func("ANY_VALUE", this)
4025
4026    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4027        transform = self.func("TRANSFORM", *expression.expressions)
4028        row_format_before = self.sql(expression, "row_format_before")
4029        row_format_before = f" {row_format_before}" if row_format_before else ""
4030        record_writer = self.sql(expression, "record_writer")
4031        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4032        using = f" USING {self.sql(expression, 'command_script')}"
4033        schema = self.sql(expression, "schema")
4034        schema = f" AS {schema}" if schema else ""
4035        row_format_after = self.sql(expression, "row_format_after")
4036        row_format_after = f" {row_format_after}" if row_format_after else ""
4037        record_reader = self.sql(expression, "record_reader")
4038        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4039        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4040
4041    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4042        key_block_size = self.sql(expression, "key_block_size")
4043        if key_block_size:
4044            return f"KEY_BLOCK_SIZE = {key_block_size}"
4045
4046        using = self.sql(expression, "using")
4047        if using:
4048            return f"USING {using}"
4049
4050        parser = self.sql(expression, "parser")
4051        if parser:
4052            return f"WITH PARSER {parser}"
4053
4054        comment = self.sql(expression, "comment")
4055        if comment:
4056            return f"COMMENT {comment}"
4057
4058        visible = expression.args.get("visible")
4059        if visible is not None:
4060            return "VISIBLE" if visible else "INVISIBLE"
4061
4062        engine_attr = self.sql(expression, "engine_attr")
4063        if engine_attr:
4064            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4065
4066        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4067        if secondary_engine_attr:
4068            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4069
4070        self.unsupported("Unsupported index constraint option.")
4071        return ""
4072
4073    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4074        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4075        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4076
4077    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4078        kind = self.sql(expression, "kind")
4079        kind = f"{kind} INDEX" if kind else "INDEX"
4080        this = self.sql(expression, "this")
4081        this = f" {this}" if this else ""
4082        index_type = self.sql(expression, "index_type")
4083        index_type = f" USING {index_type}" if index_type else ""
4084        expressions = self.expressions(expression, flat=True)
4085        expressions = f" ({expressions})" if expressions else ""
4086        options = self.expressions(expression, key="options", sep=" ")
4087        options = f" {options}" if options else ""
4088        return f"{kind}{this}{index_type}{expressions}{options}"
4089
4090    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4091        if self.NVL2_SUPPORTED:
4092            return self.function_fallback_sql(expression)
4093
4094        case = exp.Case().when(
4095            expression.this.is_(exp.null()).not_(copy=False),
4096            expression.args["true"],
4097            copy=False,
4098        )
4099        else_cond = expression.args.get("false")
4100        if else_cond:
4101            case.else_(else_cond, copy=False)
4102
4103        return self.sql(case)
4104
4105    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4106        this = self.sql(expression, "this")
4107        expr = self.sql(expression, "expression")
4108        iterator = self.sql(expression, "iterator")
4109        condition = self.sql(expression, "condition")
4110        condition = f" IF {condition}" if condition else ""
4111        return f"{this} FOR {expr} IN {iterator}{condition}"
4112
4113    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4114        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4115
4116    def opclass_sql(self, expression: exp.Opclass) -> str:
4117        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4118
4119    def predict_sql(self, expression: exp.Predict) -> str:
4120        model = self.sql(expression, "this")
4121        model = f"MODEL {model}"
4122        table = self.sql(expression, "expression")
4123        table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table
4124        parameters = self.sql(expression, "params_struct")
4125        return self.func("PREDICT", model, table, parameters or None)
4126
4127    def forin_sql(self, expression: exp.ForIn) -> str:
4128        this = self.sql(expression, "this")
4129        expression_sql = self.sql(expression, "expression")
4130        return f"FOR {this} DO {expression_sql}"
4131
4132    def refresh_sql(self, expression: exp.Refresh) -> str:
4133        this = self.sql(expression, "this")
4134        table = "" if isinstance(expression.this, exp.Literal) else "TABLE "
4135        return f"REFRESH {table}{this}"
4136
4137    def toarray_sql(self, expression: exp.ToArray) -> str:
4138        arg = expression.this
4139        if not arg.type:
4140            from sqlglot.optimizer.annotate_types import annotate_types
4141
4142            arg = annotate_types(arg, dialect=self.dialect)
4143
4144        if arg.is_type(exp.DataType.Type.ARRAY):
4145            return self.sql(arg)
4146
4147        cond_for_null = arg.is_(exp.null())
4148        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4149
4150    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4151        this = expression.this
4152        time_format = self.format_time(expression)
4153
4154        if time_format:
4155            return self.sql(
4156                exp.cast(
4157                    exp.StrToTime(this=this, format=expression.args["format"]),
4158                    exp.DataType.Type.TIME,
4159                )
4160            )
4161
4162        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4163            return self.sql(this)
4164
4165        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4166
4167    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4168        this = expression.this
4169        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4170            return self.sql(this)
4171
4172        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4173
4174    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4175        this = expression.this
4176        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4177            return self.sql(this)
4178
4179        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4180
4181    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4182        this = expression.this
4183        time_format = self.format_time(expression)
4184
4185        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4186            return self.sql(
4187                exp.cast(
4188                    exp.StrToTime(this=this, format=expression.args["format"]),
4189                    exp.DataType.Type.DATE,
4190                )
4191            )
4192
4193        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4194            return self.sql(this)
4195
4196        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4197
4198    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4199        return self.sql(
4200            exp.func(
4201                "DATEDIFF",
4202                expression.this,
4203                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4204                "day",
4205            )
4206        )
4207
4208    def lastday_sql(self, expression: exp.LastDay) -> str:
4209        if self.LAST_DAY_SUPPORTS_DATE_PART:
4210            return self.function_fallback_sql(expression)
4211
4212        unit = expression.text("unit")
4213        if unit and unit != "MONTH":
4214            self.unsupported("Date parts are not supported in LAST_DAY.")
4215
4216        return self.func("LAST_DAY", expression.this)
4217
4218    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4219        from sqlglot.dialects.dialect import unit_to_str
4220
4221        return self.func(
4222            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4223        )
4224
4225    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4226        if self.CAN_IMPLEMENT_ARRAY_ANY:
4227            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4228            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4229            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4230            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4231
4232        from sqlglot.dialects import Dialect
4233
4234        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4235        if self.dialect.__class__ != Dialect:
4236            self.unsupported("ARRAY_ANY is unsupported")
4237
4238        return self.function_fallback_sql(expression)
4239
4240    def struct_sql(self, expression: exp.Struct) -> str:
4241        expression.set(
4242            "expressions",
4243            [
4244                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4245                if isinstance(e, exp.PropertyEQ)
4246                else e
4247                for e in expression.expressions
4248            ],
4249        )
4250
4251        return self.function_fallback_sql(expression)
4252
4253    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4254        low = self.sql(expression, "this")
4255        high = self.sql(expression, "expression")
4256
4257        return f"{low} TO {high}"
4258
4259    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4260        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4261        tables = f" {self.expressions(expression)}"
4262
4263        exists = " IF EXISTS" if expression.args.get("exists") else ""
4264
4265        on_cluster = self.sql(expression, "cluster")
4266        on_cluster = f" {on_cluster}" if on_cluster else ""
4267
4268        identity = self.sql(expression, "identity")
4269        identity = f" {identity} IDENTITY" if identity else ""
4270
4271        option = self.sql(expression, "option")
4272        option = f" {option}" if option else ""
4273
4274        partition = self.sql(expression, "partition")
4275        partition = f" {partition}" if partition else ""
4276
4277        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4278
4279    # This transpiles T-SQL's CONVERT function
4280    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4281    def convert_sql(self, expression: exp.Convert) -> str:
4282        to = expression.this
4283        value = expression.expression
4284        style = expression.args.get("style")
4285        safe = expression.args.get("safe")
4286        strict = expression.args.get("strict")
4287
4288        if not to or not value:
4289            return ""
4290
4291        # Retrieve length of datatype and override to default if not specified
4292        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4293            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4294
4295        transformed: t.Optional[exp.Expression] = None
4296        cast = exp.Cast if strict else exp.TryCast
4297
4298        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4299        if isinstance(style, exp.Literal) and style.is_int:
4300            from sqlglot.dialects.tsql import TSQL
4301
4302            style_value = style.name
4303            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4304            if not converted_style:
4305                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4306
4307            fmt = exp.Literal.string(converted_style)
4308
4309            if to.this == exp.DataType.Type.DATE:
4310                transformed = exp.StrToDate(this=value, format=fmt)
4311            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4312                transformed = exp.StrToTime(this=value, format=fmt)
4313            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4314                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4315            elif to.this == exp.DataType.Type.TEXT:
4316                transformed = exp.TimeToStr(this=value, format=fmt)
4317
4318        if not transformed:
4319            transformed = cast(this=value, to=to, safe=safe)
4320
4321        return self.sql(transformed)
4322
4323    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4324        this = expression.this
4325        if isinstance(this, exp.JSONPathWildcard):
4326            this = self.json_path_part(this)
4327            return f".{this}" if this else ""
4328
4329        if exp.SAFE_IDENTIFIER_RE.match(this):
4330            return f".{this}"
4331
4332        this = self.json_path_part(this)
4333        return (
4334            f"[{this}]"
4335            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
4336            else f".{this}"
4337        )
4338
4339    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
4340        this = self.json_path_part(expression.this)
4341        return f"[{this}]" if this else ""
4342
4343    def _simplify_unless_literal(self, expression: E) -> E:
4344        if not isinstance(expression, exp.Literal):
4345            from sqlglot.optimizer.simplify import simplify
4346
4347            expression = simplify(expression, dialect=self.dialect)
4348
4349        return expression
4350
4351    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
4352        this = expression.this
4353        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
4354            self.unsupported(
4355                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
4356            )
4357            return self.sql(this)
4358
4359        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
4360            # The first modifier here will be the one closest to the AggFunc's arg
4361            mods = sorted(
4362                expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
4363                key=lambda x: 0
4364                if isinstance(x, exp.HavingMax)
4365                else (1 if isinstance(x, exp.Order) else 2),
4366            )
4367
4368            if mods:
4369                mod = mods[0]
4370                this = expression.__class__(this=mod.this.copy())
4371                this.meta["inline"] = True
4372                mod.this.replace(this)
4373                return self.sql(expression.this)
4374
4375            agg_func = expression.find(exp.AggFunc)
4376
4377            if agg_func:
4378                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
4379                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
4380
4381        return f"{self.sql(expression, 'this')} {text}"
4382
4383    def _replace_line_breaks(self, string: str) -> str:
4384        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
4385        if self.pretty:
4386            return string.replace("\n", self.SENTINEL_LINE_BREAK)
4387        return string
4388
4389    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4390        option = self.sql(expression, "this")
4391
4392        if expression.expressions:
4393            upper = option.upper()
4394
4395            # Snowflake FILE_FORMAT options are separated by whitespace
4396            sep = " " if upper == "FILE_FORMAT" else ", "
4397
4398            # Databricks copy/format options do not set their list of values with EQ
4399            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4400            values = self.expressions(expression, flat=True, sep=sep)
4401            return f"{option}{op}({values})"
4402
4403        value = self.sql(expression, "expression")
4404
4405        if not value:
4406            return option
4407
4408        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4409
4410        return f"{option}{op}{value}"
4411
4412    def credentials_sql(self, expression: exp.Credentials) -> str:
4413        cred_expr = expression.args.get("credentials")
4414        if isinstance(cred_expr, exp.Literal):
4415            # Redshift case: CREDENTIALS <string>
4416            credentials = self.sql(expression, "credentials")
4417            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4418        else:
4419            # Snowflake case: CREDENTIALS = (...)
4420            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4421            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4422
4423        storage = self.sql(expression, "storage")
4424        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4425
4426        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4427        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4428
4429        iam_role = self.sql(expression, "iam_role")
4430        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4431
4432        region = self.sql(expression, "region")
4433        region = f" REGION {region}" if region else ""
4434
4435        return f"{credentials}{storage}{encryption}{iam_role}{region}"
4436
4437    def copy_sql(self, expression: exp.Copy) -> str:
4438        this = self.sql(expression, "this")
4439        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4440
4441        credentials = self.sql(expression, "credentials")
4442        credentials = self.seg(credentials) if credentials else ""
4443        kind = self.seg("FROM" if expression.args.get("kind") else "TO")
4444        files = self.expressions(expression, key="files", flat=True)
4445
4446        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4447        params = self.expressions(
4448            expression,
4449            key="params",
4450            sep=sep,
4451            new_line=True,
4452            skip_last=True,
4453            skip_first=True,
4454            indent=self.COPY_PARAMS_ARE_WRAPPED,
4455        )
4456
4457        if params:
4458            if self.COPY_PARAMS_ARE_WRAPPED:
4459                params = f" WITH ({params})"
4460            elif not self.pretty:
4461                params = f" {params}"
4462
4463        return f"COPY{this}{kind} {files}{credentials}{params}"
4464
4465    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4466        return ""
4467
4468    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4469        on_sql = "ON" if expression.args.get("on") else "OFF"
4470        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4471        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4472        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4473        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4474
4475        if filter_col or retention_period:
4476            on_sql = self.func("ON", filter_col, retention_period)
4477
4478        return f"DATA_DELETION={on_sql}"
4479
4480    def maskingpolicycolumnconstraint_sql(
4481        self, expression: exp.MaskingPolicyColumnConstraint
4482    ) -> str:
4483        this = self.sql(expression, "this")
4484        expressions = self.expressions(expression, flat=True)
4485        expressions = f" USING ({expressions})" if expressions else ""
4486        return f"MASKING POLICY {this}{expressions}"
4487
4488    def gapfill_sql(self, expression: exp.GapFill) -> str:
4489        this = self.sql(expression, "this")
4490        this = f"TABLE {this}"
4491        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
4492
4493    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4494        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
4495
4496    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4497        this = self.sql(expression, "this")
4498        expr = expression.expression
4499
4500        if isinstance(expr, exp.Func):
4501            # T-SQL's CLR functions are case sensitive
4502            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4503        else:
4504            expr = self.sql(expression, "expression")
4505
4506        return self.scope_resolution(expr, this)
4507
4508    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4509        if self.PARSE_JSON_NAME is None:
4510            return self.sql(expression.this)
4511
4512        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
4513
4514    def rand_sql(self, expression: exp.Rand) -> str:
4515        lower = self.sql(expression, "lower")
4516        upper = self.sql(expression, "upper")
4517
4518        if lower and upper:
4519            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4520        return self.func("RAND", expression.this)
4521
4522    def changes_sql(self, expression: exp.Changes) -> str:
4523        information = self.sql(expression, "information")
4524        information = f"INFORMATION => {information}"
4525        at_before = self.sql(expression, "at_before")
4526        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4527        end = self.sql(expression, "end")
4528        end = f"{self.seg('')}{end}" if end else ""
4529
4530        return f"CHANGES ({information}){at_before}{end}"
4531
4532    def pad_sql(self, expression: exp.Pad) -> str:
4533        prefix = "L" if expression.args.get("is_left") else "R"
4534
4535        fill_pattern = self.sql(expression, "fill_pattern") or None
4536        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4537            fill_pattern = "' '"
4538
4539        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
4540
4541    def summarize_sql(self, expression: exp.Summarize) -> str:
4542        table = " TABLE" if expression.args.get("table") else ""
4543        return f"SUMMARIZE{table} {self.sql(expression.this)}"
4544
4545    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4546        generate_series = exp.GenerateSeries(**expression.args)
4547
4548        parent = expression.parent
4549        if isinstance(parent, (exp.Alias, exp.TableAlias)):
4550            parent = parent.parent
4551
4552        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
4553            return self.sql(exp.Unnest(expressions=[generate_series]))
4554
4555        if isinstance(parent, exp.Select):
4556            self.unsupported("GenerateSeries projection unnesting is not supported.")
4557
4558        return self.sql(generate_series)
4559
4560    def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str:
4561        exprs = expression.expressions
4562        if not self.ARRAY_CONCAT_IS_VAR_LEN:
4563            rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs)
4564        else:
4565            rhs = self.expressions(expression)
4566
4567        return self.func(name, expression.this, rhs or None)
4568
4569    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
4570        if self.SUPPORTS_CONVERT_TIMEZONE:
4571            return self.function_fallback_sql(expression)
4572
4573        source_tz = expression.args.get("source_tz")
4574        target_tz = expression.args.get("target_tz")
4575        timestamp = expression.args.get("timestamp")
4576
4577        if source_tz and timestamp:
4578            timestamp = exp.AtTimeZone(
4579                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
4580            )
4581
4582        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
4583
4584        return self.sql(expr)
4585
4586    def json_sql(self, expression: exp.JSON) -> str:
4587        this = self.sql(expression, "this")
4588        this = f" {this}" if this else ""
4589
4590        _with = expression.args.get("with")
4591
4592        if _with is None:
4593            with_sql = ""
4594        elif not _with:
4595            with_sql = " WITHOUT"
4596        else:
4597            with_sql = " WITH"
4598
4599        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
4600
4601        return f"JSON{this}{with_sql}{unique_sql}"
4602
4603    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
4604        def _generate_on_options(arg: t.Any) -> str:
4605            return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}"
4606
4607        path = self.sql(expression, "path")
4608        returning = self.sql(expression, "returning")
4609        returning = f" RETURNING {returning}" if returning else ""
4610
4611        on_condition = self.sql(expression, "on_condition")
4612        on_condition = f" {on_condition}" if on_condition else ""
4613
4614        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4615
4616    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
4617        else_ = "ELSE " if expression.args.get("else_") else ""
4618        condition = self.sql(expression, "expression")
4619        condition = f"WHEN {condition} THEN " if condition else else_
4620        insert = self.sql(expression, "this")[len("INSERT") :].strip()
4621        return f"{condition}{insert}"
4622
4623    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
4624        kind = self.sql(expression, "kind")
4625        expressions = self.seg(self.expressions(expression, sep=" "))
4626        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
4627        return res
4628
4629    def oncondition_sql(self, expression: exp.OnCondition) -> str:
4630        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
4631        empty = expression.args.get("empty")
4632        empty = (
4633            f"DEFAULT {empty} ON EMPTY"
4634            if isinstance(empty, exp.Expression)
4635            else self.sql(expression, "empty")
4636        )
4637
4638        error = expression.args.get("error")
4639        error = (
4640            f"DEFAULT {error} ON ERROR"
4641            if isinstance(error, exp.Expression)
4642            else self.sql(expression, "error")
4643        )
4644
4645        if error and empty:
4646            error = (
4647                f"{empty} {error}"
4648                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
4649                else f"{error} {empty}"
4650            )
4651            empty = ""
4652
4653        null = self.sql(expression, "null")
4654
4655        return f"{empty}{error}{null}"
4656
4657    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
4658        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
4659        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
4660
4661    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
4662        this = self.sql(expression, "this")
4663        path = self.sql(expression, "path")
4664
4665        passing = self.expressions(expression, "passing")
4666        passing = f" PASSING {passing}" if passing else ""
4667
4668        on_condition = self.sql(expression, "on_condition")
4669        on_condition = f" {on_condition}" if on_condition else ""
4670
4671        path = f"{path}{passing}{on_condition}"
4672
4673        return self.func("JSON_EXISTS", this, path)
4674
4675    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
4676        array_agg = self.function_fallback_sql(expression)
4677
4678        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
4679        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
4680        if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"):
4681            parent = expression.parent
4682            if isinstance(parent, exp.Filter):
4683                parent_cond = parent.expression.this
4684                parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_()))
4685            else:
4686                this = expression.this
4687                # Do not add the filter if the input is not a column (e.g. literal, struct etc)
4688                if this.find(exp.Column):
4689                    # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
4690                    this_sql = (
4691                        self.expressions(this)
4692                        if isinstance(this, exp.Distinct)
4693                        else self.sql(expression, "this")
4694                    )
4695
4696                    array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)"
4697
4698        return array_agg
4699
4700    def apply_sql(self, expression: exp.Apply) -> str:
4701        this = self.sql(expression, "this")
4702        expr = self.sql(expression, "expression")
4703
4704        return f"{this} APPLY({expr})"
4705
4706    def grant_sql(self, expression: exp.Grant) -> str:
4707        privileges_sql = self.expressions(expression, key="privileges", flat=True)
4708
4709        kind = self.sql(expression, "kind")
4710        kind = f" {kind}" if kind else ""
4711
4712        securable = self.sql(expression, "securable")
4713        securable = f" {securable}" if securable else ""
4714
4715        principals = self.expressions(expression, key="principals", flat=True)
4716
4717        grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else ""
4718
4719        return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4720
4721    def grantprivilege_sql(self, expression: exp.GrantPrivilege):
4722        this = self.sql(expression, "this")
4723        columns = self.expressions(expression, flat=True)
4724        columns = f"({columns})" if columns else ""
4725
4726        return f"{this}{columns}"
4727
4728    def grantprincipal_sql(self, expression: exp.GrantPrincipal):
4729        this = self.sql(expression, "this")
4730
4731        kind = self.sql(expression, "kind")
4732        kind = f"{kind} " if kind else ""
4733
4734        return f"{kind}{this}"
4735
4736    def columns_sql(self, expression: exp.Columns):
4737        func = self.function_fallback_sql(expression)
4738        if expression.args.get("unpack"):
4739            func = f"*{func}"
4740
4741        return func
4742
4743    def overlay_sql(self, expression: exp.Overlay):
4744        this = self.sql(expression, "this")
4745        expr = self.sql(expression, "expression")
4746        from_sql = self.sql(expression, "from")
4747        for_sql = self.sql(expression, "for")
4748        for_sql = f" FOR {for_sql}" if for_sql else ""
4749
4750        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
4751
4752    @unsupported_args("format")
4753    def todouble_sql(self, expression: exp.ToDouble) -> str:
4754        return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4755
4756    def string_sql(self, expression: exp.String) -> str:
4757        this = expression.this
4758        zone = expression.args.get("zone")
4759
4760        if zone:
4761            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
4762            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
4763            # set for source_tz to transpile the time conversion before the STRING cast
4764            this = exp.ConvertTimezone(
4765                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
4766            )
4767
4768        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
4769
4770    def median_sql(self, expression: exp.Median):
4771        if not self.SUPPORTS_MEDIAN:
4772            return self.sql(
4773                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
4774            )
4775
4776        return self.function_fallback_sql(expression)
4777
4778    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
4779        filler = self.sql(expression, "this")
4780        filler = f" {filler}" if filler else ""
4781        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
4782        return f"TRUNCATE{filler} {with_count}"
4783
4784    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
4785        if self.SUPPORTS_UNIX_SECONDS:
4786            return self.function_fallback_sql(expression)
4787
4788        start_ts = exp.cast(
4789            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
4790        )
4791
4792        return self.sql(
4793            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
4794        )
4795
4796    def arraysize_sql(self, expression: exp.ArraySize) -> str:
4797        dim = expression.expression
4798
4799        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
4800        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
4801            if not (dim.is_int and dim.name == "1"):
4802                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
4803            dim = None
4804
4805        # If dimension is required but not specified, default initialize it
4806        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
4807            dim = exp.Literal.number(1)
4808
4809        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4810
4811    def attach_sql(self, expression: exp.Attach) -> str:
4812        this = self.sql(expression, "this")
4813        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
4814        expressions = self.expressions(expression)
4815        expressions = f" ({expressions})" if expressions else ""
4816
4817        return f"ATTACH{exists_sql} {this}{expressions}"
4818
4819    def detach_sql(self, expression: exp.Detach) -> str:
4820        this = self.sql(expression, "this")
4821        # the DATABASE keyword is required if IF EXISTS is set
4822        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
4823        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
4824        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
4825
4826        return f"DETACH{exists_sql} {this}"
4827
4828    def attachoption_sql(self, expression: exp.AttachOption) -> str:
4829        this = self.sql(expression, "this")
4830        value = self.sql(expression, "expression")
4831        value = f" {value}" if value else ""
4832        return f"{this}{value}"
4833
4834    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4835        this_sql = self.sql(expression, "this")
4836        if isinstance(expression.this, exp.Table):
4837            this_sql = f"TABLE {this_sql}"
4838
4839        return self.func(
4840            "FEATURES_AT_TIME",
4841            this_sql,
4842            expression.args.get("time"),
4843            expression.args.get("num_rows"),
4844            expression.args.get("ignore_feature_nulls"),
4845        )
4846
4847    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
4848        return (
4849            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
4850        )
4851
4852    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
4853        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
4854        encode = f"{encode} {self.sql(expression, 'this')}"
4855
4856        properties = expression.args.get("properties")
4857        if properties:
4858            encode = f"{encode} {self.properties(properties)}"
4859
4860        return encode
4861
4862    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
4863        this = self.sql(expression, "this")
4864        include = f"INCLUDE {this}"
4865
4866        column_def = self.sql(expression, "column_def")
4867        if column_def:
4868            include = f"{include} {column_def}"
4869
4870        alias = self.sql(expression, "alias")
4871        if alias:
4872            include = f"{include} AS {alias}"
4873
4874        return include
4875
4876    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
4877        name = f"NAME {self.sql(expression, 'this')}"
4878        return self.func("XMLELEMENT", name, *expression.expressions)
4879
4880    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
4881        this = self.sql(expression, "this")
4882        expr = self.sql(expression, "expression")
4883        expr = f"({expr})" if expr else ""
4884        return f"{this}{expr}"
4885
4886    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
4887        partitions = self.expressions(expression, "partition_expressions")
4888        create = self.expressions(expression, "create_expressions")
4889        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
4890
4891    def partitionbyrangepropertydynamic_sql(
4892        self, expression: exp.PartitionByRangePropertyDynamic
4893    ) -> str:
4894        start = self.sql(expression, "start")
4895        end = self.sql(expression, "end")
4896
4897        every = expression.args["every"]
4898        if isinstance(every, exp.Interval) and every.this.is_string:
4899            every.this.replace(exp.Literal.number(every.name))
4900
4901        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
4902
4903    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
4904        name = self.sql(expression, "this")
4905        values = self.expressions(expression, flat=True)
4906
4907        return f"NAME {name} VALUE {values}"
4908
4909    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
4910        kind = self.sql(expression, "kind")
4911        sample = self.sql(expression, "sample")
4912        return f"SAMPLE {sample} {kind}"
4913
4914    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
4915        kind = self.sql(expression, "kind")
4916        option = self.sql(expression, "option")
4917        option = f" {option}" if option else ""
4918        this = self.sql(expression, "this")
4919        this = f" {this}" if this else ""
4920        columns = self.expressions(expression)
4921        columns = f" {columns}" if columns else ""
4922        return f"{kind}{option} STATISTICS{this}{columns}"
4923
4924    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
4925        this = self.sql(expression, "this")
4926        columns = self.expressions(expression)
4927        inner_expression = self.sql(expression, "expression")
4928        inner_expression = f" {inner_expression}" if inner_expression else ""
4929        update_options = self.sql(expression, "update_options")
4930        update_options = f" {update_options} UPDATE" if update_options else ""
4931        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
4932
4933    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
4934        kind = self.sql(expression, "kind")
4935        kind = f" {kind}" if kind else ""
4936        return f"DELETE{kind} STATISTICS"
4937
4938    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
4939        inner_expression = self.sql(expression, "expression")
4940        return f"LIST CHAINED ROWS{inner_expression}"
4941
4942    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
4943        kind = self.sql(expression, "kind")
4944        this = self.sql(expression, "this")
4945        this = f" {this}" if this else ""
4946        inner_expression = self.sql(expression, "expression")
4947        return f"VALIDATE {kind}{this}{inner_expression}"
4948
4949    def analyze_sql(self, expression: exp.Analyze) -> str:
4950        options = self.expressions(expression, key="options", sep=" ")
4951        options = f" {options}" if options else ""
4952        kind = self.sql(expression, "kind")
4953        kind = f" {kind}" if kind else ""
4954        this = self.sql(expression, "this")
4955        this = f" {this}" if this else ""
4956        mode = self.sql(expression, "mode")
4957        mode = f" {mode}" if mode else ""
4958        properties = self.sql(expression, "properties")
4959        properties = f" {properties}" if properties else ""
4960        partition = self.sql(expression, "partition")
4961        partition = f" {partition}" if partition else ""
4962        inner_expression = self.sql(expression, "expression")
4963        inner_expression = f" {inner_expression}" if inner_expression else ""
4964        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
4965
4966    def xmltable_sql(self, expression: exp.XMLTable) -> str:
4967        this = self.sql(expression, "this")
4968        namespaces = self.expressions(expression, key="namespaces")
4969        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
4970        passing = self.expressions(expression, key="passing")
4971        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
4972        columns = self.expressions(expression, key="columns")
4973        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
4974        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
4975        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
4976
4977    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
4978        this = self.sql(expression, "this")
4979        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
4980
4981    def export_sql(self, expression: exp.Export) -> str:
4982        this = self.sql(expression, "this")
4983        connection = self.sql(expression, "connection")
4984        connection = f"WITH CONNECTION {connection} " if connection else ""
4985        options = self.sql(expression, "options")
4986        return f"EXPORT DATA {connection}{options} AS {this}"
4987
4988    def declare_sql(self, expression: exp.Declare) -> str:
4989        return f"DECLARE {self.expressions(expression, flat=True)}"
4990
4991    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
4992        variable = self.sql(expression, "this")
4993        default = self.sql(expression, "default")
4994        default = f" = {default}" if default else ""
4995
4996        kind = self.sql(expression, "kind")
4997        if isinstance(expression.args.get("kind"), exp.Schema):
4998            kind = f"TABLE {kind}"
4999
5000        return f"{variable} AS {kind}{default}"
5001
5002    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5003        kind = self.sql(expression, "kind")
5004        this = self.sql(expression, "this")
5005        set = self.sql(expression, "expression")
5006        using = self.sql(expression, "using")
5007        using = f" USING {using}" if using else ""
5008
5009        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5010
5011        return f"{kind_sql} {this} SET {set}{using}"
5012
5013    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5014        params = self.expressions(expression, key="params", flat=True)
5015        return self.func(expression.name, *expression.expressions) + f"({params})"
5016
5017    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5018        return self.func(expression.name, *expression.expressions)
5019
5020    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5021        return self.anonymousaggfunc_sql(expression)
5022
5023    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5024        return self.parameterizedagg_sql(expression)
5025
5026    def show_sql(self, expression: exp.Show) -> str:
5027        self.unsupported("Unsupported SHOW statement")
5028        return ""
5029
5030    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5031        # Snowflake GET/PUT statements:
5032        #   PUT <file> <internalStage> <properties>
5033        #   GET <internalStage> <file> <properties>
5034        props = expression.args.get("properties")
5035        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5036        this = self.sql(expression, "this")
5037        target = self.sql(expression, "target")
5038
5039        if isinstance(expression, exp.Put):
5040            return f"PUT {this} {target}{props_sql}"
5041        else:
5042            return f"GET {target} {this}{props_sql}"
5043
5044    def translatecharacters_sql(self, expression: exp.TranslateCharacters):
5045        this = self.sql(expression, "this")
5046        expr = self.sql(expression, "expression")
5047        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5048        return f"TRANSLATE({this} USING {expr}{with_error})"
5049
5050    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5051        if self.SUPPORTS_DECODE_CASE:
5052            return self.func("DECODE", *expression.expressions)
5053
5054        expression, *expressions = expression.expressions
5055
5056        ifs = []
5057        for search, result in zip(expressions[::2], expressions[1::2]):
5058            if isinstance(search, exp.Literal):
5059                ifs.append(exp.If(this=expression.eq(search), true=result))
5060            elif isinstance(search, exp.Null):
5061                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5062            else:
5063                if isinstance(search, exp.Binary):
5064                    search = exp.paren(search)
5065
5066                cond = exp.or_(
5067                    expression.eq(search),
5068                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5069                    copy=False,
5070                )
5071                ifs.append(exp.If(this=cond, true=result))
5072
5073        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5074        return self.sql(case)
5075
5076    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5077        this = self.sql(expression, "this")
5078        this = self.seg(this, sep="")
5079        dimensions = self.expressions(
5080            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5081        )
5082        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5083        metrics = self.expressions(
5084            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5085        )
5086        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5087        where = self.sql(expression, "where")
5088        where = self.seg(f"WHERE {where}") if where else ""
5089        return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"

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 or 'always': Always quote. '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)
716    def __init__(
717        self,
718        pretty: t.Optional[bool] = None,
719        identify: str | bool = False,
720        normalize: bool = False,
721        pad: int = 2,
722        indent: int = 2,
723        normalize_functions: t.Optional[str | bool] = None,
724        unsupported_level: ErrorLevel = ErrorLevel.WARN,
725        max_unsupported: int = 3,
726        leading_comma: bool = False,
727        max_text_width: int = 80,
728        comments: bool = True,
729        dialect: DialectType = None,
730    ):
731        import sqlglot
732        from sqlglot.dialects import Dialect
733
734        self.pretty = pretty if pretty is not None else sqlglot.pretty
735        self.identify = identify
736        self.normalize = normalize
737        self.pad = pad
738        self._indent = indent
739        self.unsupported_level = unsupported_level
740        self.max_unsupported = max_unsupported
741        self.leading_comma = leading_comma
742        self.max_text_width = max_text_width
743        self.comments = comments
744        self.dialect = Dialect.get_or_raise(dialect)
745
746        # This is both a Dialect property and a Generator argument, so we prioritize the latter
747        self.normalize_functions = (
748            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
749        )
750
751        self.unsupported_messages: t.List[str] = []
752        self._escaped_quote_end: str = (
753            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
754        )
755        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
756
757        self._next_name = name_sequence("_t")
758
759        self._identifier_start = self.dialect.IDENTIFIER_START
760        self._identifier_end = self.dialect.IDENTIFIER_END
761
762        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.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.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.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.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.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.Uuid'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <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
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
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'}
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.NCHAR: 'NCHAR'>, <Type.CHAR: 'CHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.VARCHAR: 'VARCHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: Set[Type[sqlglot.expressions.Expression]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: Tuple[Type[sqlglot.expressions.Expression], ...] = ()
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:
764    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
765        """
766        Generates the SQL string corresponding to the given syntax tree.
767
768        Args:
769            expression: The syntax tree.
770            copy: Whether to copy the expression. The generator performs mutations so
771                it is safer to copy.
772
773        Returns:
774            The SQL string corresponding to `expression`.
775        """
776        if copy:
777            expression = expression.copy()
778
779        expression = self.preprocess(expression)
780
781        self.unsupported_messages = []
782        sql = self.sql(expression).strip()
783
784        if self.pretty:
785            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
786
787        if self.unsupported_level == ErrorLevel.IGNORE:
788            return sql
789
790        if self.unsupported_level == ErrorLevel.WARN:
791            for msg in self.unsupported_messages:
792                logger.warning(msg)
793        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
794            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
795
796        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:
798    def preprocess(self, expression: exp.Expression) -> exp.Expression:
799        """Apply generic preprocessing transformations to a given expression."""
800        expression = self._move_ctes_to_top_level(expression)
801
802        if self.ENSURE_BOOLS:
803            from sqlglot.transforms import ensure_bools
804
805            expression = ensure_bools(expression)
806
807        return expression

Apply generic preprocessing transformations to a given expression.

def unsupported(self, message: str) -> None:
820    def unsupported(self, message: str) -> None:
821        if self.unsupported_level == ErrorLevel.IMMEDIATE:
822            raise UnsupportedError(message)
823        self.unsupported_messages.append(message)
def sep(self, sep: str = ' ') -> str:
825    def sep(self, sep: str = " ") -> str:
826        return f"{sep.strip()}\n" if self.pretty else sep
def seg(self, sql: str, sep: str = ' ') -> str:
828    def seg(self, sql: str, sep: str = " ") -> str:
829        return f"{self.sep(sep)}{sql}"
def sanitize_comment(self, comment: str) -> str:
831    def sanitize_comment(self, comment: str) -> str:
832        comment = " " + comment if comment[0].strip() else comment
833        comment = comment + " " if comment[-1].strip() else comment
834
835        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
836            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
837            comment = comment.replace("*/", "* /")
838
839        return comment
def maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
841    def maybe_comment(
842        self,
843        sql: str,
844        expression: t.Optional[exp.Expression] = None,
845        comments: t.Optional[t.List[str]] = None,
846        separated: bool = False,
847    ) -> str:
848        comments = (
849            ((expression and expression.comments) if comments is None else comments)  # type: ignore
850            if self.comments
851            else None
852        )
853
854        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
855            return sql
856
857        comments_sql = " ".join(
858            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
859        )
860
861        if not comments_sql:
862            return sql
863
864        comments_sql = self._replace_line_breaks(comments_sql)
865
866        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
867            return (
868                f"{self.sep()}{comments_sql}{sql}"
869                if not sql or sql[0].isspace()
870                else f"{comments_sql}{self.sep()}{sql}"
871            )
872
873        return f"{sql} {comments_sql}"
def wrap(self, expression: sqlglot.expressions.Expression | str) -> str:
875    def wrap(self, expression: exp.Expression | str) -> str:
876        this_sql = (
877            self.sql(expression)
878            if isinstance(expression, exp.UNWRAPPED_QUERIES)
879            else self.sql(expression, "this")
880        )
881        if not this_sql:
882            return "()"
883
884        this_sql = self.indent(this_sql, level=1, pad=0)
885        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def no_identify(self, func: Callable[..., str], *args, **kwargs) -> str:
887    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
888        original = self.identify
889        self.identify = False
890        result = func(*args, **kwargs)
891        self.identify = original
892        return result
def normalize_func(self, name: str) -> str:
894    def normalize_func(self, name: str) -> str:
895        if self.normalize_functions == "upper" or self.normalize_functions is True:
896            return name.upper()
897        if self.normalize_functions == "lower":
898            return name.lower()
899        return name
def indent( self, sql: str, level: int = 0, pad: Optional[int] = None, skip_first: bool = False, skip_last: bool = False) -> str:
901    def indent(
902        self,
903        sql: str,
904        level: int = 0,
905        pad: t.Optional[int] = None,
906        skip_first: bool = False,
907        skip_last: bool = False,
908    ) -> str:
909        if not self.pretty or not sql:
910            return sql
911
912        pad = self.pad if pad is None else pad
913        lines = sql.split("\n")
914
915        return "\n".join(
916            (
917                line
918                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
919                else f"{' ' * (level * self._indent + pad)}{line}"
920            )
921            for i, line in enumerate(lines)
922        )
def sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
924    def sql(
925        self,
926        expression: t.Optional[str | exp.Expression],
927        key: t.Optional[str] = None,
928        comment: bool = True,
929    ) -> str:
930        if not expression:
931            return ""
932
933        if isinstance(expression, str):
934            return expression
935
936        if key:
937            value = expression.args.get(key)
938            if value:
939                return self.sql(value)
940            return ""
941
942        transform = self.TRANSFORMS.get(expression.__class__)
943
944        if callable(transform):
945            sql = transform(self, expression)
946        elif isinstance(expression, exp.Expression):
947            exp_handler_name = f"{expression.key}_sql"
948
949            if hasattr(self, exp_handler_name):
950                sql = getattr(self, exp_handler_name)(expression)
951            elif isinstance(expression, exp.Func):
952                sql = self.function_fallback_sql(expression)
953            elif isinstance(expression, exp.Property):
954                sql = self.property_sql(expression)
955            else:
956                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
957        else:
958            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
959
960        return self.maybe_comment(sql, expression) if self.comments and comment else sql
def uncache_sql(self, expression: sqlglot.expressions.Uncache) -> str:
962    def uncache_sql(self, expression: exp.Uncache) -> str:
963        table = self.sql(expression, "this")
964        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
965        return f"UNCACHE TABLE{exists_sql} {table}"
def cache_sql(self, expression: sqlglot.expressions.Cache) -> str:
967    def cache_sql(self, expression: exp.Cache) -> str:
968        lazy = " LAZY" if expression.args.get("lazy") else ""
969        table = self.sql(expression, "this")
970        options = expression.args.get("options")
971        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
972        sql = self.sql(expression, "expression")
973        sql = f" AS{self.sep()}{sql}" if sql else ""
974        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
975        return self.prepend_ctes(expression, sql)
def characterset_sql(self, expression: sqlglot.expressions.CharacterSet) -> str:
977    def characterset_sql(self, expression: exp.CharacterSet) -> str:
978        if isinstance(expression.parent, exp.Cast):
979            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
980        default = "DEFAULT " if expression.args.get("default") else ""
981        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
def column_parts(self, expression: sqlglot.expressions.Column) -> str:
983    def column_parts(self, expression: exp.Column) -> str:
984        return ".".join(
985            self.sql(part)
986            for part in (
987                expression.args.get("catalog"),
988                expression.args.get("db"),
989                expression.args.get("table"),
990                expression.args.get("this"),
991            )
992            if part
993        )
def column_sql(self, expression: sqlglot.expressions.Column) -> str:
 995    def column_sql(self, expression: exp.Column) -> str:
 996        join_mark = " (+)" if expression.args.get("join_mark") else ""
 997
 998        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
 999            join_mark = ""
1000            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1001
1002        return f"{self.column_parts(expression)}{join_mark}"
def columnposition_sql(self, expression: sqlglot.expressions.ColumnPosition) -> str:
1004    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1005        this = self.sql(expression, "this")
1006        this = f" {this}" if this else ""
1007        position = self.sql(expression, "position")
1008        return f"{position}{this}"
def columndef_sql(self, expression: sqlglot.expressions.ColumnDef, sep: str = ' ') -> str:
1010    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1011        column = self.sql(expression, "this")
1012        kind = self.sql(expression, "kind")
1013        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1014        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1015        kind = f"{sep}{kind}" if kind else ""
1016        constraints = f" {constraints}" if constraints else ""
1017        position = self.sql(expression, "position")
1018        position = f" {position}" if position else ""
1019
1020        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1021            kind = ""
1022
1023        return f"{exists}{column}{kind}{constraints}{position}"
def columnconstraint_sql(self, expression: sqlglot.expressions.ColumnConstraint) -> str:
1025    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1026        this = self.sql(expression, "this")
1027        kind_sql = self.sql(expression, "kind").strip()
1028        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
def computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1030    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1031        this = self.sql(expression, "this")
1032        if expression.args.get("not_null"):
1033            persisted = " PERSISTED NOT NULL"
1034        elif expression.args.get("persisted"):
1035            persisted = " PERSISTED"
1036        else:
1037            persisted = ""
1038
1039        return f"AS {this}{persisted}"
def autoincrementcolumnconstraint_sql(self, _) -> str:
1041    def autoincrementcolumnconstraint_sql(self, _) -> str:
1042        return self.token_sql(TokenType.AUTO_INCREMENT)
def compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
1044    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1045        if isinstance(expression.this, list):
1046            this = self.wrap(self.expressions(expression, key="this", flat=True))
1047        else:
1048            this = self.sql(expression, "this")
1049
1050        return f"COMPRESS {this}"
def generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1052    def generatedasidentitycolumnconstraint_sql(
1053        self, expression: exp.GeneratedAsIdentityColumnConstraint
1054    ) -> str:
1055        this = ""
1056        if expression.this is not None:
1057            on_null = " ON NULL" if expression.args.get("on_null") else ""
1058            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1059
1060        start = expression.args.get("start")
1061        start = f"START WITH {start}" if start else ""
1062        increment = expression.args.get("increment")
1063        increment = f" INCREMENT BY {increment}" if increment else ""
1064        minvalue = expression.args.get("minvalue")
1065        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1066        maxvalue = expression.args.get("maxvalue")
1067        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1068        cycle = expression.args.get("cycle")
1069        cycle_sql = ""
1070
1071        if cycle is not None:
1072            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1073            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1074
1075        sequence_opts = ""
1076        if start or increment or cycle_sql:
1077            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1078            sequence_opts = f" ({sequence_opts.strip()})"
1079
1080        expr = self.sql(expression, "expression")
1081        expr = f"({expr})" if expr else "IDENTITY"
1082
1083        return f"GENERATED{this} AS {expr}{sequence_opts}"
def generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1085    def generatedasrowcolumnconstraint_sql(
1086        self, expression: exp.GeneratedAsRowColumnConstraint
1087    ) -> str:
1088        start = "START" if expression.args.get("start") else "END"
1089        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1090        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
def periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.PeriodForSystemTimeConstraint) -> str:
1092    def periodforsystemtimeconstraint_sql(
1093        self, expression: exp.PeriodForSystemTimeConstraint
1094    ) -> str:
1095        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
def notnullcolumnconstraint_sql(self, expression: sqlglot.expressions.NotNullColumnConstraint) -> str:
1097    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1098        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
def primarykeycolumnconstraint_sql(self, expression: sqlglot.expressions.PrimaryKeyColumnConstraint) -> str:
1100    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1101        desc = expression.args.get("desc")
1102        if desc is not None:
1103            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1104        options = self.expressions(expression, key="options", flat=True, sep=" ")
1105        options = f" {options}" if options else ""
1106        return f"PRIMARY KEY{options}"
def uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1108    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1109        this = self.sql(expression, "this")
1110        this = f" {this}" if this else ""
1111        index_type = expression.args.get("index_type")
1112        index_type = f" USING {index_type}" if index_type else ""
1113        on_conflict = self.sql(expression, "on_conflict")
1114        on_conflict = f" {on_conflict}" if on_conflict else ""
1115        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1116        options = self.expressions(expression, key="options", flat=True, sep=" ")
1117        options = f" {options}" if options else ""
1118        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def createable_sql( self, expression: sqlglot.expressions.Create, locations: DefaultDict) -> str:
1120    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1121        return self.sql(expression, "this")
def create_sql(self, expression: sqlglot.expressions.Create) -> str:
1123    def create_sql(self, expression: exp.Create) -> str:
1124        kind = self.sql(expression, "kind")
1125        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1126        properties = expression.args.get("properties")
1127        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1128
1129        this = self.createable_sql(expression, properties_locs)
1130
1131        properties_sql = ""
1132        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1133            exp.Properties.Location.POST_WITH
1134        ):
1135            properties_sql = self.sql(
1136                exp.Properties(
1137                    expressions=[
1138                        *properties_locs[exp.Properties.Location.POST_SCHEMA],
1139                        *properties_locs[exp.Properties.Location.POST_WITH],
1140                    ]
1141                )
1142            )
1143
1144            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1145                properties_sql = self.sep() + properties_sql
1146            elif not self.pretty:
1147                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1148                properties_sql = f" {properties_sql}"
1149
1150        begin = " BEGIN" if expression.args.get("begin") else ""
1151        end = " END" if expression.args.get("end") else ""
1152
1153        expression_sql = self.sql(expression, "expression")
1154        if expression_sql:
1155            expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
1156
1157            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1158                postalias_props_sql = ""
1159                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1160                    postalias_props_sql = self.properties(
1161                        exp.Properties(
1162                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1163                        ),
1164                        wrapped=False,
1165                    )
1166                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1167                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1168
1169        postindex_props_sql = ""
1170        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1171            postindex_props_sql = self.properties(
1172                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1173                wrapped=False,
1174                prefix=" ",
1175            )
1176
1177        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1178        indexes = f" {indexes}" if indexes else ""
1179        index_sql = indexes + postindex_props_sql
1180
1181        replace = " OR REPLACE" if expression.args.get("replace") else ""
1182        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1183        unique = " UNIQUE" if expression.args.get("unique") else ""
1184
1185        clustered = expression.args.get("clustered")
1186        if clustered is None:
1187            clustered_sql = ""
1188        elif clustered:
1189            clustered_sql = " CLUSTERED COLUMNSTORE"
1190        else:
1191            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1192
1193        postcreate_props_sql = ""
1194        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1195            postcreate_props_sql = self.properties(
1196                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1197                sep=" ",
1198                prefix=" ",
1199                wrapped=False,
1200            )
1201
1202        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1203
1204        postexpression_props_sql = ""
1205        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1206            postexpression_props_sql = self.properties(
1207                exp.Properties(
1208                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1209                ),
1210                sep=" ",
1211                prefix=" ",
1212                wrapped=False,
1213            )
1214
1215        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1216        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1217        no_schema_binding = (
1218            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1219        )
1220
1221        clone = self.sql(expression, "clone")
1222        clone = f" {clone}" if clone else ""
1223
1224        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1225            properties_expression = f"{expression_sql}{properties_sql}"
1226        else:
1227            properties_expression = f"{properties_sql}{expression_sql}"
1228
1229        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1230        return self.prepend_ctes(expression, expression_sql)
def sequenceproperties_sql(self, expression: sqlglot.expressions.SequenceProperties) -> str:
1232    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1233        start = self.sql(expression, "start")
1234        start = f"START WITH {start}" if start else ""
1235        increment = self.sql(expression, "increment")
1236        increment = f" INCREMENT BY {increment}" if increment else ""
1237        minvalue = self.sql(expression, "minvalue")
1238        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1239        maxvalue = self.sql(expression, "maxvalue")
1240        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1241        owned = self.sql(expression, "owned")
1242        owned = f" OWNED BY {owned}" if owned else ""
1243
1244        cache = expression.args.get("cache")
1245        if cache is None:
1246            cache_str = ""
1247        elif cache is True:
1248            cache_str = " CACHE"
1249        else:
1250            cache_str = f" CACHE {cache}"
1251
1252        options = self.expressions(expression, key="options", flat=True, sep=" ")
1253        options = f" {options}" if options else ""
1254
1255        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
def clone_sql(self, expression: sqlglot.expressions.Clone) -> str:
1257    def clone_sql(self, expression: exp.Clone) -> str:
1258        this = self.sql(expression, "this")
1259        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1260        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1261        return f"{shallow}{keyword} {this}"
def describe_sql(self, expression: sqlglot.expressions.Describe) -> str:
1263    def describe_sql(self, expression: exp.Describe) -> str:
1264        style = expression.args.get("style")
1265        style = f" {style}" if style else ""
1266        partition = self.sql(expression, "partition")
1267        partition = f" {partition}" if partition else ""
1268        format = self.sql(expression, "format")
1269        format = f" {format}" if format else ""
1270
1271        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
def heredoc_sql(self, expression: sqlglot.expressions.Heredoc) -> str:
1273    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1274        tag = self.sql(expression, "tag")
1275        return f"${tag}${self.sql(expression, 'this')}${tag}$"
def prepend_ctes(self, expression: sqlglot.expressions.Expression, sql: str) -> str:
1277    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1278        with_ = self.sql(expression, "with")
1279        if with_:
1280            sql = f"{with_}{self.sep()}{sql}"
1281        return sql
def with_sql(self, expression: sqlglot.expressions.With) -> str:
1283    def with_sql(self, expression: exp.With) -> str:
1284        sql = self.expressions(expression, flat=True)
1285        recursive = (
1286            "RECURSIVE "
1287            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1288            else ""
1289        )
1290        search = self.sql(expression, "search")
1291        search = f" {search}" if search else ""
1292
1293        return f"WITH {recursive}{sql}{search}"
def cte_sql(self, expression: sqlglot.expressions.CTE) -> str:
1295    def cte_sql(self, expression: exp.CTE) -> str:
1296        alias = expression.args.get("alias")
1297        if alias:
1298            alias.add_comments(expression.pop_comments())
1299
1300        alias_sql = self.sql(expression, "alias")
1301
1302        materialized = expression.args.get("materialized")
1303        if materialized is False:
1304            materialized = "NOT MATERIALIZED "
1305        elif materialized:
1306            materialized = "MATERIALIZED "
1307
1308        return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
def tablealias_sql(self, expression: sqlglot.expressions.TableAlias) -> str:
1310    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1311        alias = self.sql(expression, "this")
1312        columns = self.expressions(expression, key="columns", flat=True)
1313        columns = f"({columns})" if columns else ""
1314
1315        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1316            columns = ""
1317            self.unsupported("Named columns are not supported in table alias.")
1318
1319        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1320            alias = self._next_name()
1321
1322        return f"{alias}{columns}"
def bitstring_sql(self, expression: sqlglot.expressions.BitString) -> str:
1324    def bitstring_sql(self, expression: exp.BitString) -> str:
1325        this = self.sql(expression, "this")
1326        if self.dialect.BIT_START:
1327            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1328        return f"{int(this, 2)}"
def hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1330    def hexstring_sql(
1331        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1332    ) -> str:
1333        this = self.sql(expression, "this")
1334        is_integer_type = expression.args.get("is_integer")
1335
1336        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1337            not self.dialect.HEX_START and not binary_function_repr
1338        ):
1339            # Integer representation will be returned if:
1340            # - The read dialect treats the hex value as integer literal but not the write
1341            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1342            return f"{int(this, 16)}"
1343
1344        if not is_integer_type:
1345            # Read dialect treats the hex value as BINARY/BLOB
1346            if binary_function_repr:
1347                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1348                return self.func(binary_function_repr, exp.Literal.string(this))
1349            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1350                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1351                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1352
1353        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
def bytestring_sql(self, expression: sqlglot.expressions.ByteString) -> str:
1355    def bytestring_sql(self, expression: exp.ByteString) -> str:
1356        this = self.sql(expression, "this")
1357        if self.dialect.BYTE_START:
1358            return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}"
1359        return this
def unicodestring_sql(self, expression: sqlglot.expressions.UnicodeString) -> str:
1361    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1362        this = self.sql(expression, "this")
1363        escape = expression.args.get("escape")
1364
1365        if self.dialect.UNICODE_START:
1366            escape_substitute = r"\\\1"
1367            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1368        else:
1369            escape_substitute = r"\\u\1"
1370            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1371
1372        if escape:
1373            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1374            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1375        else:
1376            escape_pattern = ESCAPED_UNICODE_RE
1377            escape_sql = ""
1378
1379        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1380            this = escape_pattern.sub(escape_substitute, this)
1381
1382        return f"{left_quote}{this}{right_quote}{escape_sql}"
def rawstring_sql(self, expression: sqlglot.expressions.RawString) -> str:
1384    def rawstring_sql(self, expression: exp.RawString) -> str:
1385        string = expression.this
1386        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1387            string = string.replace("\\", "\\\\")
1388
1389        string = self.escape_str(string, escape_backslash=False)
1390        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
def datatypeparam_sql(self, expression: sqlglot.expressions.DataTypeParam) -> str:
1392    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1393        this = self.sql(expression, "this")
1394        specifier = self.sql(expression, "expression")
1395        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1396        return f"{this}{specifier}"
def datatype_sql(self, expression: sqlglot.expressions.DataType) -> str:
1398    def datatype_sql(self, expression: exp.DataType) -> str:
1399        nested = ""
1400        values = ""
1401        interior = self.expressions(expression, flat=True)
1402
1403        type_value = expression.this
1404        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1405            type_sql = self.sql(expression, "kind")
1406        else:
1407            type_sql = (
1408                self.TYPE_MAPPING.get(type_value, type_value.value)
1409                if isinstance(type_value, exp.DataType.Type)
1410                else type_value
1411            )
1412
1413        if interior:
1414            if expression.args.get("nested"):
1415                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1416                if expression.args.get("values") is not None:
1417                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1418                    values = self.expressions(expression, key="values", flat=True)
1419                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1420            elif type_value == exp.DataType.Type.INTERVAL:
1421                nested = f" {interior}"
1422            else:
1423                nested = f"({interior})"
1424
1425        type_sql = f"{type_sql}{nested}{values}"
1426        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1427            exp.DataType.Type.TIMETZ,
1428            exp.DataType.Type.TIMESTAMPTZ,
1429        ):
1430            type_sql = f"{type_sql} WITH TIME ZONE"
1431
1432        return type_sql
def directory_sql(self, expression: sqlglot.expressions.Directory) -> str:
1434    def directory_sql(self, expression: exp.Directory) -> str:
1435        local = "LOCAL " if expression.args.get("local") else ""
1436        row_format = self.sql(expression, "row_format")
1437        row_format = f" {row_format}" if row_format else ""
1438        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
def delete_sql(self, expression: sqlglot.expressions.Delete) -> str:
1440    def delete_sql(self, expression: exp.Delete) -> str:
1441        this = self.sql(expression, "this")
1442        this = f" FROM {this}" if this else ""
1443        using = self.sql(expression, "using")
1444        using = f" USING {using}" if using else ""
1445        cluster = self.sql(expression, "cluster")
1446        cluster = f" {cluster}" if cluster else ""
1447        where = self.sql(expression, "where")
1448        returning = self.sql(expression, "returning")
1449        limit = self.sql(expression, "limit")
1450        tables = self.expressions(expression, key="tables")
1451        tables = f" {tables}" if tables else ""
1452        if self.RETURNING_END:
1453            expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}"
1454        else:
1455            expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}"
1456        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
def drop_sql(self, expression: sqlglot.expressions.Drop) -> str:
1458    def drop_sql(self, expression: exp.Drop) -> str:
1459        this = self.sql(expression, "this")
1460        expressions = self.expressions(expression, flat=True)
1461        expressions = f" ({expressions})" if expressions else ""
1462        kind = expression.args["kind"]
1463        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1464        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1465        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1466        on_cluster = self.sql(expression, "cluster")
1467        on_cluster = f" {on_cluster}" if on_cluster else ""
1468        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1469        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1470        cascade = " CASCADE" if expression.args.get("cascade") else ""
1471        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1472        purge = " PURGE" if expression.args.get("purge") else ""
1473        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:
1475    def set_operation(self, expression: exp.SetOperation) -> str:
1476        op_type = type(expression)
1477        op_name = op_type.key.upper()
1478
1479        distinct = expression.args.get("distinct")
1480        if (
1481            distinct is False
1482            and op_type in (exp.Except, exp.Intersect)
1483            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1484        ):
1485            self.unsupported(f"{op_name} ALL is not supported")
1486
1487        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1488
1489        if distinct is None:
1490            distinct = default_distinct
1491            if distinct is None:
1492                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1493
1494        if distinct is default_distinct:
1495            distinct_or_all = ""
1496        else:
1497            distinct_or_all = " DISTINCT" if distinct else " ALL"
1498
1499        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1500        side_kind = f"{side_kind} " if side_kind else ""
1501
1502        by_name = " BY NAME" if expression.args.get("by_name") else ""
1503        on = self.expressions(expression, key="on", flat=True)
1504        on = f" ON ({on})" if on else ""
1505
1506        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
def set_operations(self, expression: sqlglot.expressions.SetOperation) -> str:
1508    def set_operations(self, expression: exp.SetOperation) -> str:
1509        if not self.SET_OP_MODIFIERS:
1510            limit = expression.args.get("limit")
1511            order = expression.args.get("order")
1512
1513            if limit or order:
1514                select = self._move_ctes_to_top_level(
1515                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1516                )
1517
1518                if limit:
1519                    select = select.limit(limit.pop(), copy=False)
1520                if order:
1521                    select = select.order_by(order.pop(), copy=False)
1522                return self.sql(select)
1523
1524        sqls: t.List[str] = []
1525        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1526
1527        while stack:
1528            node = stack.pop()
1529
1530            if isinstance(node, exp.SetOperation):
1531                stack.append(node.expression)
1532                stack.append(
1533                    self.maybe_comment(
1534                        self.set_operation(node), comments=node.comments, separated=True
1535                    )
1536                )
1537                stack.append(node.this)
1538            else:
1539                sqls.append(self.sql(node))
1540
1541        this = self.sep().join(sqls)
1542        this = self.query_modifiers(expression, this)
1543        return self.prepend_ctes(expression, this)
def fetch_sql(self, expression: sqlglot.expressions.Fetch) -> str:
1545    def fetch_sql(self, expression: exp.Fetch) -> str:
1546        direction = expression.args.get("direction")
1547        direction = f" {direction}" if direction else ""
1548        count = self.sql(expression, "count")
1549        count = f" {count}" if count else ""
1550        limit_options = self.sql(expression, "limit_options")
1551        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1552        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
def limitoptions_sql(self, expression: sqlglot.expressions.LimitOptions) -> str:
1554    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1555        percent = " PERCENT" if expression.args.get("percent") else ""
1556        rows = " ROWS" if expression.args.get("rows") else ""
1557        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1558        if not with_ties and rows:
1559            with_ties = " ONLY"
1560        return f"{percent}{rows}{with_ties}"
def filter_sql(self, expression: sqlglot.expressions.Filter) -> str:
1562    def filter_sql(self, expression: exp.Filter) -> str:
1563        if self.AGGREGATE_FILTER_SUPPORTED:
1564            this = self.sql(expression, "this")
1565            where = self.sql(expression, "expression").strip()
1566            return f"{this} FILTER({where})"
1567
1568        agg = expression.this
1569        agg_arg = agg.this
1570        cond = expression.expression.this
1571        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1572        return self.sql(agg)
def hint_sql(self, expression: sqlglot.expressions.Hint) -> str:
1574    def hint_sql(self, expression: exp.Hint) -> str:
1575        if not self.QUERY_HINTS:
1576            self.unsupported("Hints are not supported")
1577            return ""
1578
1579        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
def indexparameters_sql(self, expression: sqlglot.expressions.IndexParameters) -> str:
1581    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1582        using = self.sql(expression, "using")
1583        using = f" USING {using}" if using else ""
1584        columns = self.expressions(expression, key="columns", flat=True)
1585        columns = f"({columns})" if columns else ""
1586        partition_by = self.expressions(expression, key="partition_by", flat=True)
1587        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1588        where = self.sql(expression, "where")
1589        include = self.expressions(expression, key="include", flat=True)
1590        if include:
1591            include = f" INCLUDE ({include})"
1592        with_storage = self.expressions(expression, key="with_storage", flat=True)
1593        with_storage = f" WITH ({with_storage})" if with_storage else ""
1594        tablespace = self.sql(expression, "tablespace")
1595        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1596        on = self.sql(expression, "on")
1597        on = f" ON {on}" if on else ""
1598
1599        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
def index_sql(self, expression: sqlglot.expressions.Index) -> str:
1601    def index_sql(self, expression: exp.Index) -> str:
1602        unique = "UNIQUE " if expression.args.get("unique") else ""
1603        primary = "PRIMARY " if expression.args.get("primary") else ""
1604        amp = "AMP " if expression.args.get("amp") else ""
1605        name = self.sql(expression, "this")
1606        name = f"{name} " if name else ""
1607        table = self.sql(expression, "table")
1608        table = f"{self.INDEX_ON} {table}" if table else ""
1609
1610        index = "INDEX " if not table else ""
1611
1612        params = self.sql(expression, "params")
1613        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
def identifier_sql(self, expression: sqlglot.expressions.Identifier) -> str:
1615    def identifier_sql(self, expression: exp.Identifier) -> str:
1616        text = expression.name
1617        lower = text.lower()
1618        text = lower if self.normalize and not expression.quoted else text
1619        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1620        if (
1621            expression.quoted
1622            or self.dialect.can_identify(text, self.identify)
1623            or lower in self.RESERVED_KEYWORDS
1624            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1625        ):
1626            text = f"{self._identifier_start}{text}{self._identifier_end}"
1627        return text
def hex_sql(self, expression: sqlglot.expressions.Hex) -> str:
1629    def hex_sql(self, expression: exp.Hex) -> str:
1630        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1631        if self.dialect.HEX_LOWERCASE:
1632            text = self.func("LOWER", text)
1633
1634        return text
def lowerhex_sql(self, expression: sqlglot.expressions.LowerHex) -> str:
1636    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1637        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1638        if not self.dialect.HEX_LOWERCASE:
1639            text = self.func("LOWER", text)
1640        return text
def inputoutputformat_sql(self, expression: sqlglot.expressions.InputOutputFormat) -> str:
1642    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1643        input_format = self.sql(expression, "input_format")
1644        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1645        output_format = self.sql(expression, "output_format")
1646        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1647        return self.sep().join((input_format, output_format))
def national_sql(self, expression: sqlglot.expressions.National, prefix: str = 'N') -> str:
1649    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1650        string = self.sql(exp.Literal.string(expression.name))
1651        return f"{prefix}{string}"
def partition_sql(self, expression: sqlglot.expressions.Partition) -> str:
1653    def partition_sql(self, expression: exp.Partition) -> str:
1654        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1655        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
def properties_sql(self, expression: sqlglot.expressions.Properties) -> str:
1657    def properties_sql(self, expression: exp.Properties) -> str:
1658        root_properties = []
1659        with_properties = []
1660
1661        for p in expression.expressions:
1662            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1663            if p_loc == exp.Properties.Location.POST_WITH:
1664                with_properties.append(p)
1665            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1666                root_properties.append(p)
1667
1668        root_props = self.root_properties(exp.Properties(expressions=root_properties))
1669        with_props = self.with_properties(exp.Properties(expressions=with_properties))
1670
1671        if root_props and with_props and not self.pretty:
1672            with_props = " " + with_props
1673
1674        return root_props + with_props
def root_properties(self, properties: sqlglot.expressions.Properties) -> str:
1676    def root_properties(self, properties: exp.Properties) -> str:
1677        if properties.expressions:
1678            return self.expressions(properties, indent=False, sep=" ")
1679        return ""
def properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1681    def properties(
1682        self,
1683        properties: exp.Properties,
1684        prefix: str = "",
1685        sep: str = ", ",
1686        suffix: str = "",
1687        wrapped: bool = True,
1688    ) -> str:
1689        if properties.expressions:
1690            expressions = self.expressions(properties, sep=sep, indent=False)
1691            if expressions:
1692                expressions = self.wrap(expressions) if wrapped else expressions
1693                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1694        return ""
def with_properties(self, properties: sqlglot.expressions.Properties) -> str:
1696    def with_properties(self, properties: exp.Properties) -> str:
1697        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
def locate_properties(self, properties: sqlglot.expressions.Properties) -> DefaultDict:
1699    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1700        properties_locs = defaultdict(list)
1701        for p in properties.expressions:
1702            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1703            if p_loc != exp.Properties.Location.UNSUPPORTED:
1704                properties_locs[p_loc].append(p)
1705            else:
1706                self.unsupported(f"Unsupported property {p.key}")
1707
1708        return properties_locs
def property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1710    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1711        if isinstance(expression.this, exp.Dot):
1712            return self.sql(expression, "this")
1713        return f"'{expression.name}'" if string_key else expression.name
def property_sql(self, expression: sqlglot.expressions.Property) -> str:
1715    def property_sql(self, expression: exp.Property) -> str:
1716        property_cls = expression.__class__
1717        if property_cls == exp.Property:
1718            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1719
1720        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1721        if not property_name:
1722            self.unsupported(f"Unsupported property {expression.key}")
1723
1724        return f"{property_name}={self.sql(expression, 'this')}"
def likeproperty_sql(self, expression: sqlglot.expressions.LikeProperty) -> str:
1726    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1727        if self.SUPPORTS_CREATE_TABLE_LIKE:
1728            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1729            options = f" {options}" if options else ""
1730
1731            like = f"LIKE {self.sql(expression, 'this')}{options}"
1732            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1733                like = f"({like})"
1734
1735            return like
1736
1737        if expression.expressions:
1738            self.unsupported("Transpilation of LIKE property options is unsupported")
1739
1740        select = exp.select("*").from_(expression.this).limit(0)
1741        return f"AS {self.sql(select)}"
def fallbackproperty_sql(self, expression: sqlglot.expressions.FallbackProperty) -> str:
1743    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1744        no = "NO " if expression.args.get("no") else ""
1745        protection = " PROTECTION" if expression.args.get("protection") else ""
1746        return f"{no}FALLBACK{protection}"
def journalproperty_sql(self, expression: sqlglot.expressions.JournalProperty) -> str:
1748    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1749        no = "NO " if expression.args.get("no") else ""
1750        local = expression.args.get("local")
1751        local = f"{local} " if local else ""
1752        dual = "DUAL " if expression.args.get("dual") else ""
1753        before = "BEFORE " if expression.args.get("before") else ""
1754        after = "AFTER " if expression.args.get("after") else ""
1755        return f"{no}{local}{dual}{before}{after}JOURNAL"
def freespaceproperty_sql(self, expression: sqlglot.expressions.FreespaceProperty) -> str:
1757    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1758        freespace = self.sql(expression, "this")
1759        percent = " PERCENT" if expression.args.get("percent") else ""
1760        return f"FREESPACE={freespace}{percent}"
def checksumproperty_sql(self, expression: sqlglot.expressions.ChecksumProperty) -> str:
1762    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1763        if expression.args.get("default"):
1764            property = "DEFAULT"
1765        elif expression.args.get("on"):
1766            property = "ON"
1767        else:
1768            property = "OFF"
1769        return f"CHECKSUM={property}"
def mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1771    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1772        if expression.args.get("no"):
1773            return "NO MERGEBLOCKRATIO"
1774        if expression.args.get("default"):
1775            return "DEFAULT MERGEBLOCKRATIO"
1776
1777        percent = " PERCENT" if expression.args.get("percent") else ""
1778        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def datablocksizeproperty_sql(self, expression: sqlglot.expressions.DataBlocksizeProperty) -> str:
1780    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1781        default = expression.args.get("default")
1782        minimum = expression.args.get("minimum")
1783        maximum = expression.args.get("maximum")
1784        if default or minimum or maximum:
1785            if default:
1786                prop = "DEFAULT"
1787            elif minimum:
1788                prop = "MINIMUM"
1789            else:
1790                prop = "MAXIMUM"
1791            return f"{prop} DATABLOCKSIZE"
1792        units = expression.args.get("units")
1793        units = f" {units}" if units else ""
1794        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1796    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1797        autotemp = expression.args.get("autotemp")
1798        always = expression.args.get("always")
1799        default = expression.args.get("default")
1800        manual = expression.args.get("manual")
1801        never = expression.args.get("never")
1802
1803        if autotemp is not None:
1804            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1805        elif always:
1806            prop = "ALWAYS"
1807        elif default:
1808            prop = "DEFAULT"
1809        elif manual:
1810            prop = "MANUAL"
1811        elif never:
1812            prop = "NEVER"
1813        return f"BLOCKCOMPRESSION={prop}"
def isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1815    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1816        no = expression.args.get("no")
1817        no = " NO" if no else ""
1818        concurrent = expression.args.get("concurrent")
1819        concurrent = " CONCURRENT" if concurrent else ""
1820        target = self.sql(expression, "target")
1821        target = f" {target}" if target else ""
1822        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def partitionboundspec_sql(self, expression: sqlglot.expressions.PartitionBoundSpec) -> str:
1824    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1825        if isinstance(expression.this, list):
1826            return f"IN ({self.expressions(expression, key='this', flat=True)})"
1827        if expression.this:
1828            modulus = self.sql(expression, "this")
1829            remainder = self.sql(expression, "expression")
1830            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1831
1832        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1833        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1834        return f"FROM ({from_expressions}) TO ({to_expressions})"
def partitionedofproperty_sql(self, expression: sqlglot.expressions.PartitionedOfProperty) -> str:
1836    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1837        this = self.sql(expression, "this")
1838
1839        for_values_or_default = expression.expression
1840        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1841            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1842        else:
1843            for_values_or_default = " DEFAULT"
1844
1845        return f"PARTITION OF {this}{for_values_or_default}"
def lockingproperty_sql(self, expression: sqlglot.expressions.LockingProperty) -> str:
1847    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1848        kind = expression.args.get("kind")
1849        this = f" {self.sql(expression, 'this')}" if expression.this else ""
1850        for_or_in = expression.args.get("for_or_in")
1851        for_or_in = f" {for_or_in}" if for_or_in else ""
1852        lock_type = expression.args.get("lock_type")
1853        override = " OVERRIDE" if expression.args.get("override") else ""
1854        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
def withdataproperty_sql(self, expression: sqlglot.expressions.WithDataProperty) -> str:
1856    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1857        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1858        statistics = expression.args.get("statistics")
1859        statistics_sql = ""
1860        if statistics is not None:
1861            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1862        return f"{data_sql}{statistics_sql}"
def withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1864    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1865        this = self.sql(expression, "this")
1866        this = f"HISTORY_TABLE={this}" if this else ""
1867        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
1868        data_consistency = (
1869            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
1870        )
1871        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
1872        retention_period = (
1873            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
1874        )
1875
1876        if this:
1877            on_sql = self.func("ON", this, data_consistency, retention_period)
1878        else:
1879            on_sql = "ON" if expression.args.get("on") else "OFF"
1880
1881        sql = f"SYSTEM_VERSIONING={on_sql}"
1882
1883        return f"WITH({sql})" if expression.args.get("with") else sql
def insert_sql(self, expression: sqlglot.expressions.Insert) -> str:
1885    def insert_sql(self, expression: exp.Insert) -> str:
1886        hint = self.sql(expression, "hint")
1887        overwrite = expression.args.get("overwrite")
1888
1889        if isinstance(expression.this, exp.Directory):
1890            this = " OVERWRITE" if overwrite else " INTO"
1891        else:
1892            this = self.INSERT_OVERWRITE if overwrite else " INTO"
1893
1894        stored = self.sql(expression, "stored")
1895        stored = f" {stored}" if stored else ""
1896        alternative = expression.args.get("alternative")
1897        alternative = f" OR {alternative}" if alternative else ""
1898        ignore = " IGNORE" if expression.args.get("ignore") else ""
1899        is_function = expression.args.get("is_function")
1900        if is_function:
1901            this = f"{this} FUNCTION"
1902        this = f"{this} {self.sql(expression, 'this')}"
1903
1904        exists = " IF EXISTS" if expression.args.get("exists") else ""
1905        where = self.sql(expression, "where")
1906        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
1907        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
1908        on_conflict = self.sql(expression, "conflict")
1909        on_conflict = f" {on_conflict}" if on_conflict else ""
1910        by_name = " BY NAME" if expression.args.get("by_name") else ""
1911        returning = self.sql(expression, "returning")
1912
1913        if self.RETURNING_END:
1914            expression_sql = f"{expression_sql}{on_conflict}{returning}"
1915        else:
1916            expression_sql = f"{returning}{expression_sql}{on_conflict}"
1917
1918        partition_by = self.sql(expression, "partition")
1919        partition_by = f" {partition_by}" if partition_by else ""
1920        settings = self.sql(expression, "settings")
1921        settings = f" {settings}" if settings else ""
1922
1923        source = self.sql(expression, "source")
1924        source = f"TABLE {source}" if source else ""
1925
1926        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
1927        return self.prepend_ctes(expression, sql)
def introducer_sql(self, expression: sqlglot.expressions.Introducer) -> str:
1929    def introducer_sql(self, expression: exp.Introducer) -> str:
1930        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def kill_sql(self, expression: sqlglot.expressions.Kill) -> str:
1932    def kill_sql(self, expression: exp.Kill) -> str:
1933        kind = self.sql(expression, "kind")
1934        kind = f" {kind}" if kind else ""
1935        this = self.sql(expression, "this")
1936        this = f" {this}" if this else ""
1937        return f"KILL{kind}{this}"
def pseudotype_sql(self, expression: sqlglot.expressions.PseudoType) -> str:
1939    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
1940        return expression.name
def objectidentifier_sql(self, expression: sqlglot.expressions.ObjectIdentifier) -> str:
1942    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
1943        return expression.name
def onconflict_sql(self, expression: sqlglot.expressions.OnConflict) -> str:
1945    def onconflict_sql(self, expression: exp.OnConflict) -> str:
1946        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
1947
1948        constraint = self.sql(expression, "constraint")
1949        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
1950
1951        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
1952        conflict_keys = f"({conflict_keys}) " if conflict_keys else " "
1953        action = self.sql(expression, "action")
1954
1955        expressions = self.expressions(expression, flat=True)
1956        if expressions:
1957            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
1958            expressions = f" {set_keyword}{expressions}"
1959
1960        where = self.sql(expression, "where")
1961        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def returning_sql(self, expression: sqlglot.expressions.Returning) -> str:
1963    def returning_sql(self, expression: exp.Returning) -> str:
1964        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
def rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1966    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
1967        fields = self.sql(expression, "fields")
1968        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
1969        escaped = self.sql(expression, "escaped")
1970        escaped = f" ESCAPED BY {escaped}" if escaped else ""
1971        items = self.sql(expression, "collection_items")
1972        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
1973        keys = self.sql(expression, "map_keys")
1974        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
1975        lines = self.sql(expression, "lines")
1976        lines = f" LINES TERMINATED BY {lines}" if lines else ""
1977        null = self.sql(expression, "null")
1978        null = f" NULL DEFINED AS {null}" if null else ""
1979        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
def withtablehint_sql(self, expression: sqlglot.expressions.WithTableHint) -> str:
1981    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
1982        return f"WITH ({self.expressions(expression, flat=True)})"
def indextablehint_sql(self, expression: sqlglot.expressions.IndexTableHint) -> str:
1984    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
1985        this = f"{self.sql(expression, 'this')} INDEX"
1986        target = self.sql(expression, "target")
1987        target = f" FOR {target}" if target else ""
1988        return f"{this}{target} ({self.expressions(expression, flat=True)})"
def historicaldata_sql(self, expression: sqlglot.expressions.HistoricalData) -> str:
1990    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
1991        this = self.sql(expression, "this")
1992        kind = self.sql(expression, "kind")
1993        expr = self.sql(expression, "expression")
1994        return f"{this} ({kind} => {expr})"
def table_parts(self, expression: sqlglot.expressions.Table) -> str:
1996    def table_parts(self, expression: exp.Table) -> str:
1997        return ".".join(
1998            self.sql(part)
1999            for part in (
2000                expression.args.get("catalog"),
2001                expression.args.get("db"),
2002                expression.args.get("this"),
2003            )
2004            if part is not None
2005        )
def table_sql(self, expression: sqlglot.expressions.Table, sep: str = ' AS ') -> str:
2007    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2008        table = self.table_parts(expression)
2009        only = "ONLY " if expression.args.get("only") else ""
2010        partition = self.sql(expression, "partition")
2011        partition = f" {partition}" if partition else ""
2012        version = self.sql(expression, "version")
2013        version = f" {version}" if version else ""
2014        alias = self.sql(expression, "alias")
2015        alias = f"{sep}{alias}" if alias else ""
2016
2017        sample = self.sql(expression, "sample")
2018        if self.dialect.ALIAS_POST_TABLESAMPLE:
2019            sample_pre_alias = sample
2020            sample_post_alias = ""
2021        else:
2022            sample_pre_alias = ""
2023            sample_post_alias = sample
2024
2025        hints = self.expressions(expression, key="hints", sep=" ")
2026        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2027        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2028        joins = self.indent(
2029            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2030        )
2031        laterals = self.expressions(expression, key="laterals", sep="")
2032
2033        file_format = self.sql(expression, "format")
2034        if file_format:
2035            pattern = self.sql(expression, "pattern")
2036            pattern = f", PATTERN => {pattern}" if pattern else ""
2037            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2038
2039        ordinality = expression.args.get("ordinality") or ""
2040        if ordinality:
2041            ordinality = f" WITH ORDINALITY{alias}"
2042            alias = ""
2043
2044        when = self.sql(expression, "when")
2045        if when:
2046            table = f"{table} {when}"
2047
2048        changes = self.sql(expression, "changes")
2049        changes = f" {changes}" if changes else ""
2050
2051        rows_from = self.expressions(expression, key="rows_from")
2052        if rows_from:
2053            table = f"ROWS FROM {self.wrap(rows_from)}"
2054
2055        return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
def tablefromrows_sql(self, expression: sqlglot.expressions.TableFromRows) -> str:
2057    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2058        table = self.func("TABLE", expression.this)
2059        alias = self.sql(expression, "alias")
2060        alias = f" AS {alias}" if alias else ""
2061        sample = self.sql(expression, "sample")
2062        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2063        joins = self.indent(
2064            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2065        )
2066        return f"{table}{alias}{pivots}{sample}{joins}"
def tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2068    def tablesample_sql(
2069        self,
2070        expression: exp.TableSample,
2071        tablesample_keyword: t.Optional[str] = None,
2072    ) -> str:
2073        method = self.sql(expression, "method")
2074        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2075        numerator = self.sql(expression, "bucket_numerator")
2076        denominator = self.sql(expression, "bucket_denominator")
2077        field = self.sql(expression, "bucket_field")
2078        field = f" ON {field}" if field else ""
2079        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2080        seed = self.sql(expression, "seed")
2081        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2082
2083        size = self.sql(expression, "size")
2084        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2085            size = f"{size} ROWS"
2086
2087        percent = self.sql(expression, "percent")
2088        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2089            percent = f"{percent} PERCENT"
2090
2091        expr = f"{bucket}{percent}{size}"
2092        if self.TABLESAMPLE_REQUIRES_PARENS:
2093            expr = f"({expr})"
2094
2095        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
def pivot_sql(self, expression: sqlglot.expressions.Pivot) -> str:
2097    def pivot_sql(self, expression: exp.Pivot) -> str:
2098        expressions = self.expressions(expression, flat=True)
2099        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2100
2101        group = self.sql(expression, "group")
2102
2103        if expression.this:
2104            this = self.sql(expression, "this")
2105            if not expressions:
2106                return f"UNPIVOT {this}"
2107
2108            on = f"{self.seg('ON')} {expressions}"
2109            into = self.sql(expression, "into")
2110            into = f"{self.seg('INTO')} {into}" if into else ""
2111            using = self.expressions(expression, key="using", flat=True)
2112            using = f"{self.seg('USING')} {using}" if using else ""
2113            return f"{direction} {this}{on}{into}{using}{group}"
2114
2115        alias = self.sql(expression, "alias")
2116        alias = f" AS {alias}" if alias else ""
2117
2118        fields = self.expressions(
2119            expression,
2120            "fields",
2121            sep=" ",
2122            dynamic=True,
2123            new_line=True,
2124            skip_first=True,
2125            skip_last=True,
2126        )
2127
2128        include_nulls = expression.args.get("include_nulls")
2129        if include_nulls is not None:
2130            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2131        else:
2132            nulls = ""
2133
2134        default_on_null = self.sql(expression, "default_on_null")
2135        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2136        return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
def version_sql(self, expression: sqlglot.expressions.Version) -> str:
2138    def version_sql(self, expression: exp.Version) -> str:
2139        this = f"FOR {expression.name}"
2140        kind = expression.text("kind")
2141        expr = self.sql(expression, "expression")
2142        return f"{this} {kind} {expr}"
def tuple_sql(self, expression: sqlglot.expressions.Tuple) -> str:
2144    def tuple_sql(self, expression: exp.Tuple) -> str:
2145        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:
2147    def update_sql(self, expression: exp.Update) -> str:
2148        this = self.sql(expression, "this")
2149        set_sql = self.expressions(expression, flat=True)
2150        from_sql = self.sql(expression, "from")
2151        where_sql = self.sql(expression, "where")
2152        returning = self.sql(expression, "returning")
2153        order = self.sql(expression, "order")
2154        limit = self.sql(expression, "limit")
2155        if self.RETURNING_END:
2156            expression_sql = f"{from_sql}{where_sql}{returning}"
2157        else:
2158            expression_sql = f"{returning}{from_sql}{where_sql}"
2159        sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}"
2160        return self.prepend_ctes(expression, sql)
def values_sql( self, expression: sqlglot.expressions.Values, values_as_table: bool = True) -> str:
2162    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2163        values_as_table = values_as_table and self.VALUES_AS_TABLE
2164
2165        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2166        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2167            args = self.expressions(expression)
2168            alias = self.sql(expression, "alias")
2169            values = f"VALUES{self.seg('')}{args}"
2170            values = (
2171                f"({values})"
2172                if self.WRAP_DERIVED_VALUES
2173                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2174                else values
2175            )
2176            return f"{values} AS {alias}" if alias else values
2177
2178        # Converts `VALUES...` expression into a series of select unions.
2179        alias_node = expression.args.get("alias")
2180        column_names = alias_node and alias_node.columns
2181
2182        selects: t.List[exp.Query] = []
2183
2184        for i, tup in enumerate(expression.expressions):
2185            row = tup.expressions
2186
2187            if i == 0 and column_names:
2188                row = [
2189                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2190                ]
2191
2192            selects.append(exp.Select(expressions=row))
2193
2194        if self.pretty:
2195            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2196            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2197            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2198            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2199            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2200
2201        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2202        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2203        return f"({unions}){alias}"
def var_sql(self, expression: sqlglot.expressions.Var) -> str:
2205    def var_sql(self, expression: exp.Var) -> str:
2206        return self.sql(expression, "this")
@unsupported_args('expressions')
def into_sql(self, expression: sqlglot.expressions.Into) -> str:
2208    @unsupported_args("expressions")
2209    def into_sql(self, expression: exp.Into) -> str:
2210        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2211        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2212        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
def from_sql(self, expression: sqlglot.expressions.From) -> str:
2214    def from_sql(self, expression: exp.From) -> str:
2215        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
def groupingsets_sql(self, expression: sqlglot.expressions.GroupingSets) -> str:
2217    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2218        grouping_sets = self.expressions(expression, indent=False)
2219        return f"GROUPING SETS {self.wrap(grouping_sets)}"
def rollup_sql(self, expression: sqlglot.expressions.Rollup) -> str:
2221    def rollup_sql(self, expression: exp.Rollup) -> str:
2222        expressions = self.expressions(expression, indent=False)
2223        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
def cube_sql(self, expression: sqlglot.expressions.Cube) -> str:
2225    def cube_sql(self, expression: exp.Cube) -> str:
2226        expressions = self.expressions(expression, indent=False)
2227        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
def group_sql(self, expression: sqlglot.expressions.Group) -> str:
2229    def group_sql(self, expression: exp.Group) -> str:
2230        group_by_all = expression.args.get("all")
2231        if group_by_all is True:
2232            modifier = " ALL"
2233        elif group_by_all is False:
2234            modifier = " DISTINCT"
2235        else:
2236            modifier = ""
2237
2238        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2239
2240        grouping_sets = self.expressions(expression, key="grouping_sets")
2241        cube = self.expressions(expression, key="cube")
2242        rollup = self.expressions(expression, key="rollup")
2243
2244        groupings = csv(
2245            self.seg(grouping_sets) if grouping_sets else "",
2246            self.seg(cube) if cube else "",
2247            self.seg(rollup) if rollup else "",
2248            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2249            sep=self.GROUPINGS_SEP,
2250        )
2251
2252        if (
2253            expression.expressions
2254            and groupings
2255            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2256        ):
2257            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2258
2259        return f"{group_by}{groupings}"
def having_sql(self, expression: sqlglot.expressions.Having) -> str:
2261    def having_sql(self, expression: exp.Having) -> str:
2262        this = self.indent(self.sql(expression, "this"))
2263        return f"{self.seg('HAVING')}{self.sep()}{this}"
def connect_sql(self, expression: sqlglot.expressions.Connect) -> str:
2265    def connect_sql(self, expression: exp.Connect) -> str:
2266        start = self.sql(expression, "start")
2267        start = self.seg(f"START WITH {start}") if start else ""
2268        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2269        connect = self.sql(expression, "connect")
2270        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2271        return start + connect
def prior_sql(self, expression: sqlglot.expressions.Prior) -> str:
2273    def prior_sql(self, expression: exp.Prior) -> str:
2274        return f"PRIOR {self.sql(expression, 'this')}"
def join_sql(self, expression: sqlglot.expressions.Join) -> str:
2276    def join_sql(self, expression: exp.Join) -> str:
2277        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2278            side = None
2279        else:
2280            side = expression.side
2281
2282        op_sql = " ".join(
2283            op
2284            for op in (
2285                expression.method,
2286                "GLOBAL" if expression.args.get("global") else None,
2287                side,
2288                expression.kind,
2289                expression.hint if self.JOIN_HINTS else None,
2290            )
2291            if op
2292        )
2293        match_cond = self.sql(expression, "match_condition")
2294        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2295        on_sql = self.sql(expression, "on")
2296        using = expression.args.get("using")
2297
2298        if not on_sql and using:
2299            on_sql = csv(*(self.sql(column) for column in using))
2300
2301        this = expression.this
2302        this_sql = self.sql(this)
2303
2304        exprs = self.expressions(expression)
2305        if exprs:
2306            this_sql = f"{this_sql},{self.seg(exprs)}"
2307
2308        if on_sql:
2309            on_sql = self.indent(on_sql, skip_first=True)
2310            space = self.seg(" " * self.pad) if self.pretty else " "
2311            if using:
2312                on_sql = f"{space}USING ({on_sql})"
2313            else:
2314                on_sql = f"{space}ON {on_sql}"
2315        elif not op_sql:
2316            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2317                return f" {this_sql}"
2318
2319            return f", {this_sql}"
2320
2321        if op_sql != "STRAIGHT_JOIN":
2322            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2323
2324        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2325        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:
2327    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2328        args = self.expressions(expression, flat=True)
2329        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2330        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
def lateral_op(self, expression: sqlglot.expressions.Lateral) -> str:
2332    def lateral_op(self, expression: exp.Lateral) -> str:
2333        cross_apply = expression.args.get("cross_apply")
2334
2335        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2336        if cross_apply is True:
2337            op = "INNER JOIN "
2338        elif cross_apply is False:
2339            op = "LEFT JOIN "
2340        else:
2341            op = ""
2342
2343        return f"{op}LATERAL"
def lateral_sql(self, expression: sqlglot.expressions.Lateral) -> str:
2345    def lateral_sql(self, expression: exp.Lateral) -> str:
2346        this = self.sql(expression, "this")
2347
2348        if expression.args.get("view"):
2349            alias = expression.args["alias"]
2350            columns = self.expressions(alias, key="columns", flat=True)
2351            table = f" {alias.name}" if alias.name else ""
2352            columns = f" AS {columns}" if columns else ""
2353            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2354            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2355
2356        alias = self.sql(expression, "alias")
2357        alias = f" AS {alias}" if alias else ""
2358
2359        ordinality = expression.args.get("ordinality") or ""
2360        if ordinality:
2361            ordinality = f" WITH ORDINALITY{alias}"
2362            alias = ""
2363
2364        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
def limit_sql(self, expression: sqlglot.expressions.Limit, top: bool = False) -> str:
2366    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2367        this = self.sql(expression, "this")
2368
2369        args = [
2370            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2371            for e in (expression.args.get(k) for k in ("offset", "expression"))
2372            if e
2373        ]
2374
2375        args_sql = ", ".join(self.sql(e) for e in args)
2376        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2377        expressions = self.expressions(expression, flat=True)
2378        limit_options = self.sql(expression, "limit_options")
2379        expressions = f" BY {expressions}" if expressions else ""
2380
2381        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
def offset_sql(self, expression: sqlglot.expressions.Offset) -> str:
2383    def offset_sql(self, expression: exp.Offset) -> str:
2384        this = self.sql(expression, "this")
2385        value = expression.expression
2386        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2387        expressions = self.expressions(expression, flat=True)
2388        expressions = f" BY {expressions}" if expressions else ""
2389        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
def setitem_sql(self, expression: sqlglot.expressions.SetItem) -> str:
2391    def setitem_sql(self, expression: exp.SetItem) -> str:
2392        kind = self.sql(expression, "kind")
2393        kind = f"{kind} " if kind else ""
2394        this = self.sql(expression, "this")
2395        expressions = self.expressions(expression)
2396        collate = self.sql(expression, "collate")
2397        collate = f" COLLATE {collate}" if collate else ""
2398        global_ = "GLOBAL " if expression.args.get("global") else ""
2399        return f"{global_}{kind}{this}{expressions}{collate}"
def set_sql(self, expression: sqlglot.expressions.Set) -> str:
2401    def set_sql(self, expression: exp.Set) -> str:
2402        expressions = f" {self.expressions(expression, flat=True)}"
2403        tag = " TAG" if expression.args.get("tag") else ""
2404        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
def pragma_sql(self, expression: sqlglot.expressions.Pragma) -> str:
2406    def pragma_sql(self, expression: exp.Pragma) -> str:
2407        return f"PRAGMA {self.sql(expression, 'this')}"
def lock_sql(self, expression: sqlglot.expressions.Lock) -> str:
2409    def lock_sql(self, expression: exp.Lock) -> str:
2410        if not self.LOCKING_READS_SUPPORTED:
2411            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2412            return ""
2413
2414        update = expression.args["update"]
2415        key = expression.args.get("key")
2416        if update:
2417            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2418        else:
2419            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2420        expressions = self.expressions(expression, flat=True)
2421        expressions = f" OF {expressions}" if expressions else ""
2422        wait = expression.args.get("wait")
2423
2424        if wait is not None:
2425            if isinstance(wait, exp.Literal):
2426                wait = f" WAIT {self.sql(wait)}"
2427            else:
2428                wait = " NOWAIT" if wait else " SKIP LOCKED"
2429
2430        return f"{lock_type}{expressions}{wait or ''}"
def literal_sql(self, expression: sqlglot.expressions.Literal) -> str:
2432    def literal_sql(self, expression: exp.Literal) -> str:
2433        text = expression.this or ""
2434        if expression.is_string:
2435            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2436        return text
def escape_str(self, text: str, escape_backslash: bool = True) -> str:
2438    def escape_str(self, text: str, escape_backslash: bool = True) -> str:
2439        if self.dialect.ESCAPED_SEQUENCES:
2440            to_escaped = self.dialect.ESCAPED_SEQUENCES
2441            text = "".join(
2442                to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text
2443            )
2444
2445        return self._replace_line_breaks(text).replace(
2446            self.dialect.QUOTE_END, self._escaped_quote_end
2447        )
def loaddata_sql(self, expression: sqlglot.expressions.LoadData) -> str:
2449    def loaddata_sql(self, expression: exp.LoadData) -> str:
2450        local = " LOCAL" if expression.args.get("local") else ""
2451        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2452        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2453        this = f" INTO TABLE {self.sql(expression, 'this')}"
2454        partition = self.sql(expression, "partition")
2455        partition = f" {partition}" if partition else ""
2456        input_format = self.sql(expression, "input_format")
2457        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2458        serde = self.sql(expression, "serde")
2459        serde = f" SERDE {serde}" if serde else ""
2460        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
def null_sql(self, *_) -> str:
2462    def null_sql(self, *_) -> str:
2463        return "NULL"
def boolean_sql(self, expression: sqlglot.expressions.Boolean) -> str:
2465    def boolean_sql(self, expression: exp.Boolean) -> str:
2466        return "TRUE" if expression.this else "FALSE"
def order_sql(self, expression: sqlglot.expressions.Order, flat: bool = False) -> str:
2468    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2469        this = self.sql(expression, "this")
2470        this = f"{this} " if this else this
2471        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2472        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:
2474    def withfill_sql(self, expression: exp.WithFill) -> str:
2475        from_sql = self.sql(expression, "from")
2476        from_sql = f" FROM {from_sql}" if from_sql else ""
2477        to_sql = self.sql(expression, "to")
2478        to_sql = f" TO {to_sql}" if to_sql else ""
2479        step_sql = self.sql(expression, "step")
2480        step_sql = f" STEP {step_sql}" if step_sql else ""
2481        interpolated_values = [
2482            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2483            if isinstance(e, exp.Alias)
2484            else self.sql(e, "this")
2485            for e in expression.args.get("interpolate") or []
2486        ]
2487        interpolate = (
2488            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2489        )
2490        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
def cluster_sql(self, expression: sqlglot.expressions.Cluster) -> str:
2492    def cluster_sql(self, expression: exp.Cluster) -> str:
2493        return self.op_expressions("CLUSTER BY", expression)
def distribute_sql(self, expression: sqlglot.expressions.Distribute) -> str:
2495    def distribute_sql(self, expression: exp.Distribute) -> str:
2496        return self.op_expressions("DISTRIBUTE BY", expression)
def sort_sql(self, expression: sqlglot.expressions.Sort) -> str:
2498    def sort_sql(self, expression: exp.Sort) -> str:
2499        return self.op_expressions("SORT BY", expression)
def ordered_sql(self, expression: sqlglot.expressions.Ordered) -> str:
2501    def ordered_sql(self, expression: exp.Ordered) -> str:
2502        desc = expression.args.get("desc")
2503        asc = not desc
2504
2505        nulls_first = expression.args.get("nulls_first")
2506        nulls_last = not nulls_first
2507        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2508        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2509        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2510
2511        this = self.sql(expression, "this")
2512
2513        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2514        nulls_sort_change = ""
2515        if nulls_first and (
2516            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2517        ):
2518            nulls_sort_change = " NULLS FIRST"
2519        elif (
2520            nulls_last
2521            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2522            and not nulls_are_last
2523        ):
2524            nulls_sort_change = " NULLS LAST"
2525
2526        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2527        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2528            window = expression.find_ancestor(exp.Window, exp.Select)
2529            if isinstance(window, exp.Window) and window.args.get("spec"):
2530                self.unsupported(
2531                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2532                )
2533                nulls_sort_change = ""
2534            elif self.NULL_ORDERING_SUPPORTED is False and (
2535                (asc and nulls_sort_change == " NULLS LAST")
2536                or (desc and nulls_sort_change == " NULLS FIRST")
2537            ):
2538                # BigQuery does not allow these ordering/nulls combinations when used under
2539                # an aggregation func or under a window containing one
2540                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2541
2542                if isinstance(ancestor, exp.Window):
2543                    ancestor = ancestor.this
2544                if isinstance(ancestor, exp.AggFunc):
2545                    self.unsupported(
2546                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2547                    )
2548                    nulls_sort_change = ""
2549            elif self.NULL_ORDERING_SUPPORTED is None:
2550                if expression.this.is_int:
2551                    self.unsupported(
2552                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2553                    )
2554                elif not isinstance(expression.this, exp.Rand):
2555                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2556                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2557                nulls_sort_change = ""
2558
2559        with_fill = self.sql(expression, "with_fill")
2560        with_fill = f" {with_fill}" if with_fill else ""
2561
2562        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def matchrecognizemeasure_sql(self, expression: sqlglot.expressions.MatchRecognizeMeasure) -> str:
2564    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2565        window_frame = self.sql(expression, "window_frame")
2566        window_frame = f"{window_frame} " if window_frame else ""
2567
2568        this = self.sql(expression, "this")
2569
2570        return f"{window_frame}{this}"
def matchrecognize_sql(self, expression: sqlglot.expressions.MatchRecognize) -> str:
2572    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2573        partition = self.partition_by_sql(expression)
2574        order = self.sql(expression, "order")
2575        measures = self.expressions(expression, key="measures")
2576        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2577        rows = self.sql(expression, "rows")
2578        rows = self.seg(rows) if rows else ""
2579        after = self.sql(expression, "after")
2580        after = self.seg(after) if after else ""
2581        pattern = self.sql(expression, "pattern")
2582        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2583        definition_sqls = [
2584            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2585            for definition in expression.args.get("define", [])
2586        ]
2587        definitions = self.expressions(sqls=definition_sqls)
2588        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2589        body = "".join(
2590            (
2591                partition,
2592                order,
2593                measures,
2594                rows,
2595                after,
2596                pattern,
2597                define,
2598            )
2599        )
2600        alias = self.sql(expression, "alias")
2601        alias = f" {alias}" if alias else ""
2602        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
def query_modifiers(self, expression: sqlglot.expressions.Expression, *sqls: str) -> str:
2604    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2605        limit = expression.args.get("limit")
2606
2607        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2608            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2609        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2610            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2611
2612        return csv(
2613            *sqls,
2614            *[self.sql(join) for join in expression.args.get("joins") or []],
2615            self.sql(expression, "match"),
2616            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2617            self.sql(expression, "prewhere"),
2618            self.sql(expression, "where"),
2619            self.sql(expression, "connect"),
2620            self.sql(expression, "group"),
2621            self.sql(expression, "having"),
2622            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2623            self.sql(expression, "order"),
2624            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2625            *self.after_limit_modifiers(expression),
2626            self.options_modifier(expression),
2627            self.for_modifiers(expression),
2628            sep="",
2629        )
def options_modifier(self, expression: sqlglot.expressions.Expression) -> str:
2631    def options_modifier(self, expression: exp.Expression) -> str:
2632        options = self.expressions(expression, key="options")
2633        return f" {options}" if options else ""
def for_modifiers(self, expression: sqlglot.expressions.Expression) -> str:
2635    def for_modifiers(self, expression: exp.Expression) -> str:
2636        for_modifiers = self.expressions(expression, key="for")
2637        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
def queryoption_sql(self, expression: sqlglot.expressions.QueryOption) -> str:
2639    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2640        self.unsupported("Unsupported query option.")
2641        return ""
def offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2643    def offset_limit_modifiers(
2644        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2645    ) -> t.List[str]:
2646        return [
2647            self.sql(expression, "offset") if fetch else self.sql(limit),
2648            self.sql(limit) if fetch else self.sql(expression, "offset"),
2649        ]
def after_limit_modifiers(self, expression: sqlglot.expressions.Expression) -> List[str]:
2651    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2652        locks = self.expressions(expression, key="locks", sep=" ")
2653        locks = f" {locks}" if locks else ""
2654        return [locks, self.sql(expression, "sample")]
def select_sql(self, expression: sqlglot.expressions.Select) -> str:
2656    def select_sql(self, expression: exp.Select) -> str:
2657        into = expression.args.get("into")
2658        if not self.SUPPORTS_SELECT_INTO and into:
2659            into.pop()
2660
2661        hint = self.sql(expression, "hint")
2662        distinct = self.sql(expression, "distinct")
2663        distinct = f" {distinct}" if distinct else ""
2664        kind = self.sql(expression, "kind")
2665
2666        limit = expression.args.get("limit")
2667        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2668            top = self.limit_sql(limit, top=True)
2669            limit.pop()
2670        else:
2671            top = ""
2672
2673        expressions = self.expressions(expression)
2674
2675        if kind:
2676            if kind in self.SELECT_KINDS:
2677                kind = f" AS {kind}"
2678            else:
2679                if kind == "STRUCT":
2680                    expressions = self.expressions(
2681                        sqls=[
2682                            self.sql(
2683                                exp.Struct(
2684                                    expressions=[
2685                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2686                                        if isinstance(e, exp.Alias)
2687                                        else e
2688                                        for e in expression.expressions
2689                                    ]
2690                                )
2691                            )
2692                        ]
2693                    )
2694                kind = ""
2695
2696        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2697        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2698
2699        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2700        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2701        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2702        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2703        sql = self.query_modifiers(
2704            expression,
2705            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2706            self.sql(expression, "into", comment=False),
2707            self.sql(expression, "from", comment=False),
2708        )
2709
2710        # If both the CTE and SELECT clauses have comments, generate the latter earlier
2711        if expression.args.get("with"):
2712            sql = self.maybe_comment(sql, expression)
2713            expression.pop_comments()
2714
2715        sql = self.prepend_ctes(expression, sql)
2716
2717        if not self.SUPPORTS_SELECT_INTO and into:
2718            if into.args.get("temporary"):
2719                table_kind = " TEMPORARY"
2720            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
2721                table_kind = " UNLOGGED"
2722            else:
2723                table_kind = ""
2724            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
2725
2726        return sql
def schema_sql(self, expression: sqlglot.expressions.Schema) -> str:
2728    def schema_sql(self, expression: exp.Schema) -> str:
2729        this = self.sql(expression, "this")
2730        sql = self.schema_columns_sql(expression)
2731        return f"{this} {sql}" if this and sql else this or sql
def schema_columns_sql(self, expression: sqlglot.expressions.Schema) -> str:
2733    def schema_columns_sql(self, expression: exp.Schema) -> str:
2734        if expression.expressions:
2735            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2736        return ""
def star_sql(self, expression: sqlglot.expressions.Star) -> str:
2738    def star_sql(self, expression: exp.Star) -> str:
2739        except_ = self.expressions(expression, key="except", flat=True)
2740        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2741        replace = self.expressions(expression, key="replace", flat=True)
2742        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
2743        rename = self.expressions(expression, key="rename", flat=True)
2744        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
2745        return f"*{except_}{replace}{rename}"
def parameter_sql(self, expression: sqlglot.expressions.Parameter) -> str:
2747    def parameter_sql(self, expression: exp.Parameter) -> str:
2748        this = self.sql(expression, "this")
2749        return f"{self.PARAMETER_TOKEN}{this}"
def sessionparameter_sql(self, expression: sqlglot.expressions.SessionParameter) -> str:
2751    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2752        this = self.sql(expression, "this")
2753        kind = expression.text("kind")
2754        if kind:
2755            kind = f"{kind}."
2756        return f"@@{kind}{this}"
def placeholder_sql(self, expression: sqlglot.expressions.Placeholder) -> str:
2758    def placeholder_sql(self, expression: exp.Placeholder) -> str:
2759        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
def subquery_sql(self, expression: sqlglot.expressions.Subquery, sep: str = ' AS ') -> str:
2761    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2762        alias = self.sql(expression, "alias")
2763        alias = f"{sep}{alias}" if alias else ""
2764        sample = self.sql(expression, "sample")
2765        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
2766            alias = f"{sample}{alias}"
2767
2768            # Set to None so it's not generated again by self.query_modifiers()
2769            expression.set("sample", None)
2770
2771        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2772        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2773        return self.prepend_ctes(expression, sql)
def qualify_sql(self, expression: sqlglot.expressions.Qualify) -> str:
2775    def qualify_sql(self, expression: exp.Qualify) -> str:
2776        this = self.indent(self.sql(expression, "this"))
2777        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
def unnest_sql(self, expression: sqlglot.expressions.Unnest) -> str:
2779    def unnest_sql(self, expression: exp.Unnest) -> str:
2780        args = self.expressions(expression, flat=True)
2781
2782        alias = expression.args.get("alias")
2783        offset = expression.args.get("offset")
2784
2785        if self.UNNEST_WITH_ORDINALITY:
2786            if alias and isinstance(offset, exp.Expression):
2787                alias.append("columns", offset)
2788
2789        if alias and self.dialect.UNNEST_COLUMN_ONLY:
2790            columns = alias.columns
2791            alias = self.sql(columns[0]) if columns else ""
2792        else:
2793            alias = self.sql(alias)
2794
2795        alias = f" AS {alias}" if alias else alias
2796        if self.UNNEST_WITH_ORDINALITY:
2797            suffix = f" WITH ORDINALITY{alias}" if offset else alias
2798        else:
2799            if isinstance(offset, exp.Expression):
2800                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
2801            elif offset:
2802                suffix = f"{alias} WITH OFFSET"
2803            else:
2804                suffix = alias
2805
2806        return f"UNNEST({args}){suffix}"
def prewhere_sql(self, expression: sqlglot.expressions.PreWhere) -> str:
2808    def prewhere_sql(self, expression: exp.PreWhere) -> str:
2809        return ""
def where_sql(self, expression: sqlglot.expressions.Where) -> str:
2811    def where_sql(self, expression: exp.Where) -> str:
2812        this = self.indent(self.sql(expression, "this"))
2813        return f"{self.seg('WHERE')}{self.sep()}{this}"
def window_sql(self, expression: sqlglot.expressions.Window) -> str:
2815    def window_sql(self, expression: exp.Window) -> str:
2816        this = self.sql(expression, "this")
2817        partition = self.partition_by_sql(expression)
2818        order = expression.args.get("order")
2819        order = self.order_sql(order, flat=True) if order else ""
2820        spec = self.sql(expression, "spec")
2821        alias = self.sql(expression, "alias")
2822        over = self.sql(expression, "over") or "OVER"
2823
2824        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
2825
2826        first = expression.args.get("first")
2827        if first is None:
2828            first = ""
2829        else:
2830            first = "FIRST" if first else "LAST"
2831
2832        if not partition and not order and not spec and alias:
2833            return f"{this} {alias}"
2834
2835        args = self.format_args(
2836            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
2837        )
2838        return f"{this} ({args})"
def partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2840    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
2841        partition = self.expressions(expression, key="partition_by", flat=True)
2842        return f"PARTITION BY {partition}" if partition else ""
def windowspec_sql(self, expression: sqlglot.expressions.WindowSpec) -> str:
2844    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
2845        kind = self.sql(expression, "kind")
2846        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
2847        end = (
2848            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
2849            or "CURRENT ROW"
2850        )
2851
2852        window_spec = f"{kind} BETWEEN {start} AND {end}"
2853
2854        exclude = self.sql(expression, "exclude")
2855        if exclude:
2856            if self.SUPPORTS_WINDOW_EXCLUDE:
2857                window_spec += f" EXCLUDE {exclude}"
2858            else:
2859                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
2860
2861        return window_spec
def withingroup_sql(self, expression: sqlglot.expressions.WithinGroup) -> str:
2863    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
2864        this = self.sql(expression, "this")
2865        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
2866        return f"{this} WITHIN GROUP ({expression_sql})"
def between_sql(self, expression: sqlglot.expressions.Between) -> str:
2868    def between_sql(self, expression: exp.Between) -> str:
2869        this = self.sql(expression, "this")
2870        low = self.sql(expression, "low")
2871        high = self.sql(expression, "high")
2872        symmetric = expression.args.get("symmetric")
2873
2874        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
2875            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
2876
2877        flag = (
2878            " SYMMETRIC"
2879            if symmetric
2880            else " ASYMMETRIC"
2881            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
2882            else ""  # silently drop ASYMMETRIC – semantics identical
2883        )
2884        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]:
2886    def bracket_offset_expressions(
2887        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
2888    ) -> t.List[exp.Expression]:
2889        return apply_index_offset(
2890            expression.this,
2891            expression.expressions,
2892            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
2893            dialect=self.dialect,
2894        )
def bracket_sql(self, expression: sqlglot.expressions.Bracket) -> str:
2896    def bracket_sql(self, expression: exp.Bracket) -> str:
2897        expressions = self.bracket_offset_expressions(expression)
2898        expressions_sql = ", ".join(self.sql(e) for e in expressions)
2899        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
def all_sql(self, expression: sqlglot.expressions.All) -> str:
2901    def all_sql(self, expression: exp.All) -> str:
2902        return f"ALL {self.wrap(expression)}"
def any_sql(self, expression: sqlglot.expressions.Any) -> str:
2904    def any_sql(self, expression: exp.Any) -> str:
2905        this = self.sql(expression, "this")
2906        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
2907            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
2908                this = self.wrap(this)
2909            return f"ANY{this}"
2910        return f"ANY {this}"
def exists_sql(self, expression: sqlglot.expressions.Exists) -> str:
2912    def exists_sql(self, expression: exp.Exists) -> str:
2913        return f"EXISTS{self.wrap(expression)}"
def case_sql(self, expression: sqlglot.expressions.Case) -> str:
2915    def case_sql(self, expression: exp.Case) -> str:
2916        this = self.sql(expression, "this")
2917        statements = [f"CASE {this}" if this else "CASE"]
2918
2919        for e in expression.args["ifs"]:
2920            statements.append(f"WHEN {self.sql(e, 'this')}")
2921            statements.append(f"THEN {self.sql(e, 'true')}")
2922
2923        default = self.sql(expression, "default")
2924
2925        if default:
2926            statements.append(f"ELSE {default}")
2927
2928        statements.append("END")
2929
2930        if self.pretty and self.too_wide(statements):
2931            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
2932
2933        return " ".join(statements)
def constraint_sql(self, expression: sqlglot.expressions.Constraint) -> str:
2935    def constraint_sql(self, expression: exp.Constraint) -> str:
2936        this = self.sql(expression, "this")
2937        expressions = self.expressions(expression, flat=True)
2938        return f"CONSTRAINT {this} {expressions}"
def nextvaluefor_sql(self, expression: sqlglot.expressions.NextValueFor) -> str:
2940    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
2941        order = expression.args.get("order")
2942        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
2943        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
def extract_sql(self, expression: sqlglot.expressions.Extract) -> str:
2945    def extract_sql(self, expression: exp.Extract) -> str:
2946        from sqlglot.dialects.dialect import map_date_part
2947
2948        this = (
2949            map_date_part(expression.this, self.dialect)
2950            if self.NORMALIZE_EXTRACT_DATE_PARTS
2951            else expression.this
2952        )
2953        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
2954        expression_sql = self.sql(expression, "expression")
2955
2956        return f"EXTRACT({this_sql} FROM {expression_sql})"
def trim_sql(self, expression: sqlglot.expressions.Trim) -> str:
2958    def trim_sql(self, expression: exp.Trim) -> str:
2959        trim_type = self.sql(expression, "position")
2960
2961        if trim_type == "LEADING":
2962            func_name = "LTRIM"
2963        elif trim_type == "TRAILING":
2964            func_name = "RTRIM"
2965        else:
2966            func_name = "TRIM"
2967
2968        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]:
2970    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
2971        args = expression.expressions
2972        if isinstance(expression, exp.ConcatWs):
2973            args = args[1:]  # Skip the delimiter
2974
2975        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
2976            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
2977
2978        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
2979            args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args]
2980
2981        return args
def concat_sql(self, expression: sqlglot.expressions.Concat) -> str:
2983    def concat_sql(self, expression: exp.Concat) -> str:
2984        expressions = self.convert_concat_args(expression)
2985
2986        # Some dialects don't allow a single-argument CONCAT call
2987        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
2988            return self.sql(expressions[0])
2989
2990        return self.func("CONCAT", *expressions)
def concatws_sql(self, expression: sqlglot.expressions.ConcatWs) -> str:
2992    def concatws_sql(self, expression: exp.ConcatWs) -> str:
2993        return self.func(
2994            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
2995        )
def check_sql(self, expression: sqlglot.expressions.Check) -> str:
2997    def check_sql(self, expression: exp.Check) -> str:
2998        this = self.sql(expression, key="this")
2999        return f"CHECK ({this})"
def foreignkey_sql(self, expression: sqlglot.expressions.ForeignKey) -> str:
3001    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3002        expressions = self.expressions(expression, flat=True)
3003        expressions = f" ({expressions})" if expressions else ""
3004        reference = self.sql(expression, "reference")
3005        reference = f" {reference}" if reference else ""
3006        delete = self.sql(expression, "delete")
3007        delete = f" ON DELETE {delete}" if delete else ""
3008        update = self.sql(expression, "update")
3009        update = f" ON UPDATE {update}" if update else ""
3010        options = self.expressions(expression, key="options", flat=True, sep=" ")
3011        options = f" {options}" if options else ""
3012        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
def primarykey_sql(self, expression: sqlglot.expressions.PrimaryKey) -> str:
3014    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3015        expressions = self.expressions(expression, flat=True)
3016        include = self.sql(expression, "include")
3017        options = self.expressions(expression, key="options", flat=True, sep=" ")
3018        options = f" {options}" if options else ""
3019        return f"PRIMARY KEY ({expressions}){include}{options}"
def if_sql(self, expression: sqlglot.expressions.If) -> str:
3021    def if_sql(self, expression: exp.If) -> str:
3022        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
def matchagainst_sql(self, expression: sqlglot.expressions.MatchAgainst) -> str:
3024    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3025        modifier = expression.args.get("modifier")
3026        modifier = f" {modifier}" if modifier else ""
3027        return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
def jsonkeyvalue_sql(self, expression: sqlglot.expressions.JSONKeyValue) -> str:
3029    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3030        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:
3032    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3033        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3034
3035        if expression.args.get("escape"):
3036            path = self.escape_str(path)
3037
3038        if self.QUOTE_JSON_PATH:
3039            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3040
3041        return path
def json_path_part(self, expression: int | str | sqlglot.expressions.JSONPathPart) -> str:
3043    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3044        if isinstance(expression, exp.JSONPathPart):
3045            transform = self.TRANSFORMS.get(expression.__class__)
3046            if not callable(transform):
3047                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3048                return ""
3049
3050            return transform(self, expression)
3051
3052        if isinstance(expression, int):
3053            return str(expression)
3054
3055        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3056            escaped = expression.replace("'", "\\'")
3057            escaped = f"\\'{expression}\\'"
3058        else:
3059            escaped = expression.replace('"', '\\"')
3060            escaped = f'"{escaped}"'
3061
3062        return escaped
def formatjson_sql(self, expression: sqlglot.expressions.FormatJson) -> str:
3064    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3065        return f"{self.sql(expression, 'this')} FORMAT JSON"
def formatphrase_sql(self, expression: sqlglot.expressions.FormatPhrase) -> str:
3067    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3068        # Output the Teradata column FORMAT override.
3069        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3070        this = self.sql(expression, "this")
3071        fmt = self.sql(expression, "format")
3072        return f"{this} (FORMAT {fmt})"
def jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3074    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3075        null_handling = expression.args.get("null_handling")
3076        null_handling = f" {null_handling}" if null_handling else ""
3077
3078        unique_keys = expression.args.get("unique_keys")
3079        if unique_keys is not None:
3080            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3081        else:
3082            unique_keys = ""
3083
3084        return_type = self.sql(expression, "return_type")
3085        return_type = f" RETURNING {return_type}" if return_type else ""
3086        encoding = self.sql(expression, "encoding")
3087        encoding = f" ENCODING {encoding}" if encoding else ""
3088
3089        return self.func(
3090            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3091            *expression.expressions,
3092            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3093        )
def jsonobjectagg_sql(self, expression: sqlglot.expressions.JSONObjectAgg) -> str:
3095    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3096        return self.jsonobject_sql(expression)
def jsonarray_sql(self, expression: sqlglot.expressions.JSONArray) -> str:
3098    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3099        null_handling = expression.args.get("null_handling")
3100        null_handling = f" {null_handling}" if null_handling else ""
3101        return_type = self.sql(expression, "return_type")
3102        return_type = f" RETURNING {return_type}" if return_type else ""
3103        strict = " STRICT" if expression.args.get("strict") else ""
3104        return self.func(
3105            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3106        )
def jsonarrayagg_sql(self, expression: sqlglot.expressions.JSONArrayAgg) -> str:
3108    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3109        this = self.sql(expression, "this")
3110        order = self.sql(expression, "order")
3111        null_handling = expression.args.get("null_handling")
3112        null_handling = f" {null_handling}" if null_handling else ""
3113        return_type = self.sql(expression, "return_type")
3114        return_type = f" RETURNING {return_type}" if return_type else ""
3115        strict = " STRICT" if expression.args.get("strict") else ""
3116        return self.func(
3117            "JSON_ARRAYAGG",
3118            this,
3119            suffix=f"{order}{null_handling}{return_type}{strict})",
3120        )
def jsoncolumndef_sql(self, expression: sqlglot.expressions.JSONColumnDef) -> str:
3122    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3123        path = self.sql(expression, "path")
3124        path = f" PATH {path}" if path else ""
3125        nested_schema = self.sql(expression, "nested_schema")
3126
3127        if nested_schema:
3128            return f"NESTED{path} {nested_schema}"
3129
3130        this = self.sql(expression, "this")
3131        kind = self.sql(expression, "kind")
3132        kind = f" {kind}" if kind else ""
3133        return f"{this}{kind}{path}"
def jsonschema_sql(self, expression: sqlglot.expressions.JSONSchema) -> str:
3135    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3136        return self.func("COLUMNS", *expression.expressions)
def jsontable_sql(self, expression: sqlglot.expressions.JSONTable) -> str:
3138    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3139        this = self.sql(expression, "this")
3140        path = self.sql(expression, "path")
3141        path = f", {path}" if path else ""
3142        error_handling = expression.args.get("error_handling")
3143        error_handling = f" {error_handling}" if error_handling else ""
3144        empty_handling = expression.args.get("empty_handling")
3145        empty_handling = f" {empty_handling}" if empty_handling else ""
3146        schema = self.sql(expression, "schema")
3147        return self.func(
3148            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3149        )
def openjsoncolumndef_sql(self, expression: sqlglot.expressions.OpenJSONColumnDef) -> str:
3151    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3152        this = self.sql(expression, "this")
3153        kind = self.sql(expression, "kind")
3154        path = self.sql(expression, "path")
3155        path = f" {path}" if path else ""
3156        as_json = " AS JSON" if expression.args.get("as_json") else ""
3157        return f"{this} {kind}{path}{as_json}"
def openjson_sql(self, expression: sqlglot.expressions.OpenJSON) -> str:
3159    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3160        this = self.sql(expression, "this")
3161        path = self.sql(expression, "path")
3162        path = f", {path}" if path else ""
3163        expressions = self.expressions(expression)
3164        with_ = (
3165            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3166            if expressions
3167            else ""
3168        )
3169        return f"OPENJSON({this}{path}){with_}"
def in_sql(self, expression: sqlglot.expressions.In) -> str:
3171    def in_sql(self, expression: exp.In) -> str:
3172        query = expression.args.get("query")
3173        unnest = expression.args.get("unnest")
3174        field = expression.args.get("field")
3175        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3176
3177        if query:
3178            in_sql = self.sql(query)
3179        elif unnest:
3180            in_sql = self.in_unnest_op(unnest)
3181        elif field:
3182            in_sql = self.sql(field)
3183        else:
3184            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3185
3186        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
def in_unnest_op(self, unnest: sqlglot.expressions.Unnest) -> str:
3188    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3189        return f"(SELECT {self.sql(unnest)})"
def interval_sql(self, expression: sqlglot.expressions.Interval) -> str:
3191    def interval_sql(self, expression: exp.Interval) -> str:
3192        unit = self.sql(expression, "unit")
3193        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3194            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3195        unit = f" {unit}" if unit else ""
3196
3197        if self.SINGLE_STRING_INTERVAL:
3198            this = expression.this.name if expression.this else ""
3199            return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}"
3200
3201        this = self.sql(expression, "this")
3202        if this:
3203            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3204            this = f" {this}" if unwrapped else f" ({this})"
3205
3206        return f"INTERVAL{this}{unit}"
def return_sql(self, expression: sqlglot.expressions.Return) -> str:
3208    def return_sql(self, expression: exp.Return) -> str:
3209        return f"RETURN {self.sql(expression, 'this')}"
def reference_sql(self, expression: sqlglot.expressions.Reference) -> str:
3211    def reference_sql(self, expression: exp.Reference) -> str:
3212        this = self.sql(expression, "this")
3213        expressions = self.expressions(expression, flat=True)
3214        expressions = f"({expressions})" if expressions else ""
3215        options = self.expressions(expression, key="options", flat=True, sep=" ")
3216        options = f" {options}" if options else ""
3217        return f"REFERENCES {this}{expressions}{options}"
def anonymous_sql(self, expression: sqlglot.expressions.Anonymous) -> str:
3219    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3220        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3221        parent = expression.parent
3222        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3223        return self.func(
3224            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3225        )
def paren_sql(self, expression: sqlglot.expressions.Paren) -> str:
3227    def paren_sql(self, expression: exp.Paren) -> str:
3228        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3229        return f"({sql}{self.seg(')', sep='')}"
def neg_sql(self, expression: sqlglot.expressions.Neg) -> str:
3231    def neg_sql(self, expression: exp.Neg) -> str:
3232        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3233        this_sql = self.sql(expression, "this")
3234        sep = " " if this_sql[0] == "-" else ""
3235        return f"-{sep}{this_sql}"
def not_sql(self, expression: sqlglot.expressions.Not) -> str:
3237    def not_sql(self, expression: exp.Not) -> str:
3238        return f"NOT {self.sql(expression, 'this')}"
def alias_sql(self, expression: sqlglot.expressions.Alias) -> str:
3240    def alias_sql(self, expression: exp.Alias) -> str:
3241        alias = self.sql(expression, "alias")
3242        alias = f" AS {alias}" if alias else ""
3243        return f"{self.sql(expression, 'this')}{alias}"
def pivotalias_sql(self, expression: sqlglot.expressions.PivotAlias) -> str:
3245    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3246        alias = expression.args["alias"]
3247
3248        parent = expression.parent
3249        pivot = parent and parent.parent
3250
3251        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3252            identifier_alias = isinstance(alias, exp.Identifier)
3253            literal_alias = isinstance(alias, exp.Literal)
3254
3255            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3256                alias.replace(exp.Literal.string(alias.output_name))
3257            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3258                alias.replace(exp.to_identifier(alias.output_name))
3259
3260        return self.alias_sql(expression)
def aliases_sql(self, expression: sqlglot.expressions.Aliases) -> str:
3262    def aliases_sql(self, expression: exp.Aliases) -> str:
3263        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
def atindex_sql(self, expression: sqlglot.expressions.AtTimeZone) -> str:
3265    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3266        this = self.sql(expression, "this")
3267        index = self.sql(expression, "expression")
3268        return f"{this} AT {index}"
def attimezone_sql(self, expression: sqlglot.expressions.AtTimeZone) -> str:
3270    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3271        this = self.sql(expression, "this")
3272        zone = self.sql(expression, "zone")
3273        return f"{this} AT TIME ZONE {zone}"
def fromtimezone_sql(self, expression: sqlglot.expressions.FromTimeZone) -> str:
3275    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3276        this = self.sql(expression, "this")
3277        zone = self.sql(expression, "zone")
3278        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
def add_sql(self, expression: sqlglot.expressions.Add) -> str:
3280    def add_sql(self, expression: exp.Add) -> str:
3281        return self.binary(expression, "+")
def and_sql( self, expression: sqlglot.expressions.And, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3283    def and_sql(
3284        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3285    ) -> str:
3286        return self.connector_sql(expression, "AND", stack)
def or_sql( self, expression: sqlglot.expressions.Or, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3288    def or_sql(
3289        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3290    ) -> str:
3291        return self.connector_sql(expression, "OR", stack)
def xor_sql( self, expression: sqlglot.expressions.Xor, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3293    def xor_sql(
3294        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3295    ) -> str:
3296        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:
3298    def connector_sql(
3299        self,
3300        expression: exp.Connector,
3301        op: str,
3302        stack: t.Optional[t.List[str | exp.Expression]] = None,
3303    ) -> str:
3304        if stack is not None:
3305            if expression.expressions:
3306                stack.append(self.expressions(expression, sep=f" {op} "))
3307            else:
3308                stack.append(expression.right)
3309                if expression.comments and self.comments:
3310                    for comment in expression.comments:
3311                        if comment:
3312                            op += f" /*{self.sanitize_comment(comment)}*/"
3313                stack.extend((op, expression.left))
3314            return op
3315
3316        stack = [expression]
3317        sqls: t.List[str] = []
3318        ops = set()
3319
3320        while stack:
3321            node = stack.pop()
3322            if isinstance(node, exp.Connector):
3323                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3324            else:
3325                sql = self.sql(node)
3326                if sqls and sqls[-1] in ops:
3327                    sqls[-1] += f" {sql}"
3328                else:
3329                    sqls.append(sql)
3330
3331        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3332        return sep.join(sqls)
def bitwiseand_sql(self, expression: sqlglot.expressions.BitwiseAnd) -> str:
3334    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3335        return self.binary(expression, "&")
def bitwiseleftshift_sql(self, expression: sqlglot.expressions.BitwiseLeftShift) -> str:
3337    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3338        return self.binary(expression, "<<")
def bitwisenot_sql(self, expression: sqlglot.expressions.BitwiseNot) -> str:
3340    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3341        return f"~{self.sql(expression, 'this')}"
def bitwiseor_sql(self, expression: sqlglot.expressions.BitwiseOr) -> str:
3343    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3344        return self.binary(expression, "|")
def bitwiserightshift_sql(self, expression: sqlglot.expressions.BitwiseRightShift) -> str:
3346    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3347        return self.binary(expression, ">>")
def bitwisexor_sql(self, expression: sqlglot.expressions.BitwiseXor) -> str:
3349    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3350        return self.binary(expression, "^")
def cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3352    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3353        format_sql = self.sql(expression, "format")
3354        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3355        to_sql = self.sql(expression, "to")
3356        to_sql = f" {to_sql}" if to_sql else ""
3357        action = self.sql(expression, "action")
3358        action = f" {action}" if action else ""
3359        default = self.sql(expression, "default")
3360        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3361        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
def currentdate_sql(self, expression: sqlglot.expressions.CurrentDate) -> str:
3363    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3364        zone = self.sql(expression, "this")
3365        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
def collate_sql(self, expression: sqlglot.expressions.Collate) -> str:
3367    def collate_sql(self, expression: exp.Collate) -> str:
3368        if self.COLLATE_IS_FUNC:
3369            return self.function_fallback_sql(expression)
3370        return self.binary(expression, "COLLATE")
def command_sql(self, expression: sqlglot.expressions.Command) -> str:
3372    def command_sql(self, expression: exp.Command) -> str:
3373        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
def comment_sql(self, expression: sqlglot.expressions.Comment) -> str:
3375    def comment_sql(self, expression: exp.Comment) -> str:
3376        this = self.sql(expression, "this")
3377        kind = expression.args["kind"]
3378        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3379        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3380        expression_sql = self.sql(expression, "expression")
3381        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
def mergetreettlaction_sql(self, expression: sqlglot.expressions.MergeTreeTTLAction) -> str:
3383    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3384        this = self.sql(expression, "this")
3385        delete = " DELETE" if expression.args.get("delete") else ""
3386        recompress = self.sql(expression, "recompress")
3387        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3388        to_disk = self.sql(expression, "to_disk")
3389        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3390        to_volume = self.sql(expression, "to_volume")
3391        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3392        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
def mergetreettl_sql(self, expression: sqlglot.expressions.MergeTreeTTL) -> str:
3394    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3395        where = self.sql(expression, "where")
3396        group = self.sql(expression, "group")
3397        aggregates = self.expressions(expression, key="aggregates")
3398        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3399
3400        if not (where or group or aggregates) and len(expression.expressions) == 1:
3401            return f"TTL {self.expressions(expression, flat=True)}"
3402
3403        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
def transaction_sql(self, expression: sqlglot.expressions.Transaction) -> str:
3405    def transaction_sql(self, expression: exp.Transaction) -> str:
3406        return "BEGIN"
def commit_sql(self, expression: sqlglot.expressions.Commit) -> str:
3408    def commit_sql(self, expression: exp.Commit) -> str:
3409        chain = expression.args.get("chain")
3410        if chain is not None:
3411            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3412
3413        return f"COMMIT{chain or ''}"
def rollback_sql(self, expression: sqlglot.expressions.Rollback) -> str:
3415    def rollback_sql(self, expression: exp.Rollback) -> str:
3416        savepoint = expression.args.get("savepoint")
3417        savepoint = f" TO {savepoint}" if savepoint else ""
3418        return f"ROLLBACK{savepoint}"
def altercolumn_sql(self, expression: sqlglot.expressions.AlterColumn) -> str:
3420    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3421        this = self.sql(expression, "this")
3422
3423        dtype = self.sql(expression, "dtype")
3424        if dtype:
3425            collate = self.sql(expression, "collate")
3426            collate = f" COLLATE {collate}" if collate else ""
3427            using = self.sql(expression, "using")
3428            using = f" USING {using}" if using else ""
3429            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3430            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3431
3432        default = self.sql(expression, "default")
3433        if default:
3434            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3435
3436        comment = self.sql(expression, "comment")
3437        if comment:
3438            return f"ALTER COLUMN {this} COMMENT {comment}"
3439
3440        visible = expression.args.get("visible")
3441        if visible:
3442            return f"ALTER COLUMN {this} SET {visible}"
3443
3444        allow_null = expression.args.get("allow_null")
3445        drop = expression.args.get("drop")
3446
3447        if not drop and not allow_null:
3448            self.unsupported("Unsupported ALTER COLUMN syntax")
3449
3450        if allow_null is not None:
3451            keyword = "DROP" if drop else "SET"
3452            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3453
3454        return f"ALTER COLUMN {this} DROP DEFAULT"
def alterindex_sql(self, expression: sqlglot.expressions.AlterIndex) -> str:
3456    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3457        this = self.sql(expression, "this")
3458
3459        visible = expression.args.get("visible")
3460        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3461
3462        return f"ALTER INDEX {this} {visible_sql}"
def alterdiststyle_sql(self, expression: sqlglot.expressions.AlterDistStyle) -> str:
3464    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3465        this = self.sql(expression, "this")
3466        if not isinstance(expression.this, exp.Var):
3467            this = f"KEY DISTKEY {this}"
3468        return f"ALTER DISTSTYLE {this}"
def altersortkey_sql(self, expression: sqlglot.expressions.AlterSortKey) -> str:
3470    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3471        compound = " COMPOUND" if expression.args.get("compound") else ""
3472        this = self.sql(expression, "this")
3473        expressions = self.expressions(expression, flat=True)
3474        expressions = f"({expressions})" if expressions else ""
3475        return f"ALTER{compound} SORTKEY {this or expressions}"
def alterrename_sql(self, expression: sqlglot.expressions.AlterRename) -> str:
3477    def alterrename_sql(self, expression: exp.AlterRename) -> str:
3478        if not self.RENAME_TABLE_WITH_DB:
3479            # Remove db from tables
3480            expression = expression.transform(
3481                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3482            ).assert_is(exp.AlterRename)
3483        this = self.sql(expression, "this")
3484        return f"RENAME TO {this}"
def renamecolumn_sql(self, expression: sqlglot.expressions.RenameColumn) -> str:
3486    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3487        exists = " IF EXISTS" if expression.args.get("exists") else ""
3488        old_column = self.sql(expression, "this")
3489        new_column = self.sql(expression, "to")
3490        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
def alterset_sql(self, expression: sqlglot.expressions.AlterSet) -> str:
3492    def alterset_sql(self, expression: exp.AlterSet) -> str:
3493        exprs = self.expressions(expression, flat=True)
3494        if self.ALTER_SET_WRAPPED:
3495            exprs = f"({exprs})"
3496
3497        return f"SET {exprs}"
def alter_sql(self, expression: sqlglot.expressions.Alter) -> str:
3499    def alter_sql(self, expression: exp.Alter) -> str:
3500        actions = expression.args["actions"]
3501
3502        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3503            actions[0], exp.ColumnDef
3504        ):
3505            actions_sql = self.expressions(expression, key="actions", flat=True)
3506            actions_sql = f"ADD {actions_sql}"
3507        else:
3508            actions_list = []
3509            for action in actions:
3510                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3511                    action_sql = self.add_column_sql(action)
3512                else:
3513                    action_sql = self.sql(action)
3514                    if isinstance(action, exp.Query):
3515                        action_sql = f"AS {action_sql}"
3516
3517                actions_list.append(action_sql)
3518
3519            actions_sql = self.format_args(*actions_list).lstrip("\n")
3520
3521        exists = " IF EXISTS" if expression.args.get("exists") else ""
3522        on_cluster = self.sql(expression, "cluster")
3523        on_cluster = f" {on_cluster}" if on_cluster else ""
3524        only = " ONLY" if expression.args.get("only") else ""
3525        options = self.expressions(expression, key="options")
3526        options = f", {options}" if options else ""
3527        kind = self.sql(expression, "kind")
3528        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3529
3530        return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
def add_column_sql(self, expression: sqlglot.expressions.Expression) -> str:
3532    def add_column_sql(self, expression: exp.Expression) -> str:
3533        sql = self.sql(expression)
3534        if isinstance(expression, exp.Schema):
3535            column_text = " COLUMNS"
3536        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3537            column_text = " COLUMN"
3538        else:
3539            column_text = ""
3540
3541        return f"ADD{column_text} {sql}"
def droppartition_sql(self, expression: sqlglot.expressions.DropPartition) -> str:
3543    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3544        expressions = self.expressions(expression)
3545        exists = " IF EXISTS " if expression.args.get("exists") else " "
3546        return f"DROP{exists}{expressions}"
def addconstraint_sql(self, expression: sqlglot.expressions.AddConstraint) -> str:
3548    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3549        return f"ADD {self.expressions(expression, indent=False)}"
def addpartition_sql(self, expression: sqlglot.expressions.AddPartition) -> str:
3551    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3552        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3553        location = self.sql(expression, "location")
3554        location = f" {location}" if location else ""
3555        return f"ADD {exists}{self.sql(expression.this)}{location}"
def distinct_sql(self, expression: sqlglot.expressions.Distinct) -> str:
3557    def distinct_sql(self, expression: exp.Distinct) -> str:
3558        this = self.expressions(expression, flat=True)
3559
3560        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3561            case = exp.case()
3562            for arg in expression.expressions:
3563                case = case.when(arg.is_(exp.null()), exp.null())
3564            this = self.sql(case.else_(f"({this})"))
3565
3566        this = f" {this}" if this else ""
3567
3568        on = self.sql(expression, "on")
3569        on = f" ON {on}" if on else ""
3570        return f"DISTINCT{this}{on}"
def ignorenulls_sql(self, expression: sqlglot.expressions.IgnoreNulls) -> str:
3572    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3573        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
def respectnulls_sql(self, expression: sqlglot.expressions.RespectNulls) -> str:
3575    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3576        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
def havingmax_sql(self, expression: sqlglot.expressions.HavingMax) -> str:
3578    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3579        this_sql = self.sql(expression, "this")
3580        expression_sql = self.sql(expression, "expression")
3581        kind = "MAX" if expression.args.get("max") else "MIN"
3582        return f"{this_sql} HAVING {kind} {expression_sql}"
def intdiv_sql(self, expression: sqlglot.expressions.IntDiv) -> str:
3584    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3585        return self.sql(
3586            exp.Cast(
3587                this=exp.Div(this=expression.this, expression=expression.expression),
3588                to=exp.DataType(this=exp.DataType.Type.INT),
3589            )
3590        )
def dpipe_sql(self, expression: sqlglot.expressions.DPipe) -> str:
3592    def dpipe_sql(self, expression: exp.DPipe) -> str:
3593        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3594            return self.func(
3595                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3596            )
3597        return self.binary(expression, "||")
def div_sql(self, expression: sqlglot.expressions.Div) -> str:
3599    def div_sql(self, expression: exp.Div) -> str:
3600        l, r = expression.left, expression.right
3601
3602        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3603            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3604
3605        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3606            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3607                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3608
3609        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3610            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3611                return self.sql(
3612                    exp.cast(
3613                        l / r,
3614                        to=exp.DataType.Type.BIGINT,
3615                    )
3616                )
3617
3618        return self.binary(expression, "/")
def safedivide_sql(self, expression: sqlglot.expressions.SafeDivide) -> str:
3620    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3621        n = exp._wrap(expression.this, exp.Binary)
3622        d = exp._wrap(expression.expression, exp.Binary)
3623        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
def overlaps_sql(self, expression: sqlglot.expressions.Overlaps) -> str:
3625    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3626        return self.binary(expression, "OVERLAPS")
def distance_sql(self, expression: sqlglot.expressions.Distance) -> str:
3628    def distance_sql(self, expression: exp.Distance) -> str:
3629        return self.binary(expression, "<->")
def dot_sql(self, expression: sqlglot.expressions.Dot) -> str:
3631    def dot_sql(self, expression: exp.Dot) -> str:
3632        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
def eq_sql(self, expression: sqlglot.expressions.EQ) -> str:
3634    def eq_sql(self, expression: exp.EQ) -> str:
3635        return self.binary(expression, "=")
def propertyeq_sql(self, expression: sqlglot.expressions.PropertyEQ) -> str:
3637    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3638        return self.binary(expression, ":=")
def escape_sql(self, expression: sqlglot.expressions.Escape) -> str:
3640    def escape_sql(self, expression: exp.Escape) -> str:
3641        return self.binary(expression, "ESCAPE")
def glob_sql(self, expression: sqlglot.expressions.Glob) -> str:
3643    def glob_sql(self, expression: exp.Glob) -> str:
3644        return self.binary(expression, "GLOB")
def gt_sql(self, expression: sqlglot.expressions.GT) -> str:
3646    def gt_sql(self, expression: exp.GT) -> str:
3647        return self.binary(expression, ">")
def gte_sql(self, expression: sqlglot.expressions.GTE) -> str:
3649    def gte_sql(self, expression: exp.GTE) -> str:
3650        return self.binary(expression, ">=")
def ilike_sql(self, expression: sqlglot.expressions.ILike) -> str:
3652    def ilike_sql(self, expression: exp.ILike) -> str:
3653        return self.binary(expression, "ILIKE")
def ilikeany_sql(self, expression: sqlglot.expressions.ILikeAny) -> str:
3655    def ilikeany_sql(self, expression: exp.ILikeAny) -> str:
3656        return self.binary(expression, "ILIKE ANY")
def is_sql(self, expression: sqlglot.expressions.Is) -> str:
3658    def is_sql(self, expression: exp.Is) -> str:
3659        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
3660            return self.sql(
3661                expression.this if expression.expression.this else exp.not_(expression.this)
3662            )
3663        return self.binary(expression, "IS")
def like_sql(self, expression: sqlglot.expressions.Like) -> str:
3665    def like_sql(self, expression: exp.Like) -> str:
3666        return self.binary(expression, "LIKE")
def likeany_sql(self, expression: sqlglot.expressions.LikeAny) -> str:
3668    def likeany_sql(self, expression: exp.LikeAny) -> str:
3669        return self.binary(expression, "LIKE ANY")
def similarto_sql(self, expression: sqlglot.expressions.SimilarTo) -> str:
3671    def similarto_sql(self, expression: exp.SimilarTo) -> str:
3672        return self.binary(expression, "SIMILAR TO")
def lt_sql(self, expression: sqlglot.expressions.LT) -> str:
3674    def lt_sql(self, expression: exp.LT) -> str:
3675        return self.binary(expression, "<")
def lte_sql(self, expression: sqlglot.expressions.LTE) -> str:
3677    def lte_sql(self, expression: exp.LTE) -> str:
3678        return self.binary(expression, "<=")
def mod_sql(self, expression: sqlglot.expressions.Mod) -> str:
3680    def mod_sql(self, expression: exp.Mod) -> str:
3681        return self.binary(expression, "%")
def mul_sql(self, expression: sqlglot.expressions.Mul) -> str:
3683    def mul_sql(self, expression: exp.Mul) -> str:
3684        return self.binary(expression, "*")
def neq_sql(self, expression: sqlglot.expressions.NEQ) -> str:
3686    def neq_sql(self, expression: exp.NEQ) -> str:
3687        return self.binary(expression, "<>")
def nullsafeeq_sql(self, expression: sqlglot.expressions.NullSafeEQ) -> str:
3689    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
3690        return self.binary(expression, "IS NOT DISTINCT FROM")
def nullsafeneq_sql(self, expression: sqlglot.expressions.NullSafeNEQ) -> str:
3692    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3693        return self.binary(expression, "IS DISTINCT FROM")
def slice_sql(self, expression: sqlglot.expressions.Slice) -> str:
3695    def slice_sql(self, expression: exp.Slice) -> str:
3696        return self.binary(expression, ":")
def sub_sql(self, expression: sqlglot.expressions.Sub) -> str:
3698    def sub_sql(self, expression: exp.Sub) -> str:
3699        return self.binary(expression, "-")
def trycast_sql(self, expression: sqlglot.expressions.TryCast) -> str:
3701    def trycast_sql(self, expression: exp.TryCast) -> str:
3702        return self.cast_sql(expression, safe_prefix="TRY_")
def jsoncast_sql(self, expression: sqlglot.expressions.JSONCast) -> str:
3704    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
3705        return self.cast_sql(expression)
def try_sql(self, expression: sqlglot.expressions.Try) -> str:
3707    def try_sql(self, expression: exp.Try) -> str:
3708        if not self.TRY_SUPPORTED:
3709            self.unsupported("Unsupported TRY function")
3710            return self.sql(expression, "this")
3711
3712        return self.func("TRY", expression.this)
def log_sql(self, expression: sqlglot.expressions.Log) -> str:
3714    def log_sql(self, expression: exp.Log) -> str:
3715        this = expression.this
3716        expr = expression.expression
3717
3718        if self.dialect.LOG_BASE_FIRST is False:
3719            this, expr = expr, this
3720        elif self.dialect.LOG_BASE_FIRST is None and expr:
3721            if this.name in ("2", "10"):
3722                return self.func(f"LOG{this.name}", expr)
3723
3724            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
3725
3726        return self.func("LOG", this, expr)
def use_sql(self, expression: sqlglot.expressions.Use) -> str:
3728    def use_sql(self, expression: exp.Use) -> str:
3729        kind = self.sql(expression, "kind")
3730        kind = f" {kind}" if kind else ""
3731        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
3732        this = f" {this}" if this else ""
3733        return f"USE{kind}{this}"
def binary(self, expression: sqlglot.expressions.Binary, op: str) -> str:
3735    def binary(self, expression: exp.Binary, op: str) -> str:
3736        sqls: t.List[str] = []
3737        stack: t.List[t.Union[str, exp.Expression]] = [expression]
3738        binary_type = type(expression)
3739
3740        while stack:
3741            node = stack.pop()
3742
3743            if type(node) is binary_type:
3744                op_func = node.args.get("operator")
3745                if op_func:
3746                    op = f"OPERATOR({self.sql(op_func)})"
3747
3748                stack.append(node.right)
3749                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
3750                stack.append(node.left)
3751            else:
3752                sqls.append(self.sql(node))
3753
3754        return "".join(sqls)
def ceil_floor( self, expression: sqlglot.expressions.Ceil | sqlglot.expressions.Floor) -> str:
3756    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
3757        to_clause = self.sql(expression, "to")
3758        if to_clause:
3759            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
3760
3761        return self.function_fallback_sql(expression)
def function_fallback_sql(self, expression: sqlglot.expressions.Func) -> str:
3763    def function_fallback_sql(self, expression: exp.Func) -> str:
3764        args = []
3765
3766        for key in expression.arg_types:
3767            arg_value = expression.args.get(key)
3768
3769            if isinstance(arg_value, list):
3770                for value in arg_value:
3771                    args.append(value)
3772            elif arg_value is not None:
3773                args.append(arg_value)
3774
3775        if self.dialect.PRESERVE_ORIGINAL_NAMES:
3776            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
3777        else:
3778            name = expression.sql_name()
3779
3780        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:
3782    def func(
3783        self,
3784        name: str,
3785        *args: t.Optional[exp.Expression | str],
3786        prefix: str = "(",
3787        suffix: str = ")",
3788        normalize: bool = True,
3789    ) -> str:
3790        name = self.normalize_func(name) if normalize else name
3791        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3793    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
3794        arg_sqls = tuple(
3795            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
3796        )
3797        if self.pretty and self.too_wide(arg_sqls):
3798            return self.indent(
3799                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
3800            )
3801        return sep.join(arg_sqls)
def too_wide(self, args: Iterable) -> bool:
3803    def too_wide(self, args: t.Iterable) -> bool:
3804        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]:
3806    def format_time(
3807        self,
3808        expression: exp.Expression,
3809        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
3810        inverse_time_trie: t.Optional[t.Dict] = None,
3811    ) -> t.Optional[str]:
3812        return format_time(
3813            self.sql(expression, "format"),
3814            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
3815            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
3816        )
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:
3818    def expressions(
3819        self,
3820        expression: t.Optional[exp.Expression] = None,
3821        key: t.Optional[str] = None,
3822        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
3823        flat: bool = False,
3824        indent: bool = True,
3825        skip_first: bool = False,
3826        skip_last: bool = False,
3827        sep: str = ", ",
3828        prefix: str = "",
3829        dynamic: bool = False,
3830        new_line: bool = False,
3831    ) -> str:
3832        expressions = expression.args.get(key or "expressions") if expression else sqls
3833
3834        if not expressions:
3835            return ""
3836
3837        if flat:
3838            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
3839
3840        num_sqls = len(expressions)
3841        result_sqls = []
3842
3843        for i, e in enumerate(expressions):
3844            sql = self.sql(e, comment=False)
3845            if not sql:
3846                continue
3847
3848            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
3849
3850            if self.pretty:
3851                if self.leading_comma:
3852                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
3853                else:
3854                    result_sqls.append(
3855                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
3856                    )
3857            else:
3858                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
3859
3860        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
3861            if new_line:
3862                result_sqls.insert(0, "")
3863                result_sqls.append("")
3864            result_sql = "\n".join(s.rstrip() for s in result_sqls)
3865        else:
3866            result_sql = "".join(result_sqls)
3867
3868        return (
3869            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
3870            if indent
3871            else result_sql
3872        )
def op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3874    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
3875        flat = flat or isinstance(expression.parent, exp.Properties)
3876        expressions_sql = self.expressions(expression, flat=flat)
3877        if flat:
3878            return f"{op} {expressions_sql}"
3879        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
def naked_property(self, expression: sqlglot.expressions.Property) -> str:
3881    def naked_property(self, expression: exp.Property) -> str:
3882        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
3883        if not property_name:
3884            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
3885        return f"{property_name} {self.sql(expression, 'this')}"
def tag_sql(self, expression: sqlglot.expressions.Tag) -> str:
3887    def tag_sql(self, expression: exp.Tag) -> str:
3888        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
def token_sql(self, token_type: sqlglot.tokens.TokenType) -> str:
3890    def token_sql(self, token_type: TokenType) -> str:
3891        return self.TOKEN_MAPPING.get(token_type, token_type.name)
def userdefinedfunction_sql(self, expression: sqlglot.expressions.UserDefinedFunction) -> str:
3893    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
3894        this = self.sql(expression, "this")
3895        expressions = self.no_identify(self.expressions, expression)
3896        expressions = (
3897            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
3898        )
3899        return f"{this}{expressions}" if expressions.strip() != "" else this
def joinhint_sql(self, expression: sqlglot.expressions.JoinHint) -> str:
3901    def joinhint_sql(self, expression: exp.JoinHint) -> str:
3902        this = self.sql(expression, "this")
3903        expressions = self.expressions(expression, flat=True)
3904        return f"{this}({expressions})"
def kwarg_sql(self, expression: sqlglot.expressions.Kwarg) -> str:
3906    def kwarg_sql(self, expression: exp.Kwarg) -> str:
3907        return self.binary(expression, "=>")
def when_sql(self, expression: sqlglot.expressions.When) -> str:
3909    def when_sql(self, expression: exp.When) -> str:
3910        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
3911        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
3912        condition = self.sql(expression, "condition")
3913        condition = f" AND {condition}" if condition else ""
3914
3915        then_expression = expression.args.get("then")
3916        if isinstance(then_expression, exp.Insert):
3917            this = self.sql(then_expression, "this")
3918            this = f"INSERT {this}" if this else "INSERT"
3919            then = self.sql(then_expression, "expression")
3920            then = f"{this} VALUES {then}" if then else this
3921        elif isinstance(then_expression, exp.Update):
3922            if isinstance(then_expression.args.get("expressions"), exp.Star):
3923                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
3924            else:
3925                then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}"
3926        else:
3927            then = self.sql(then_expression)
3928        return f"WHEN {matched}{source}{condition} THEN {then}"
def whens_sql(self, expression: sqlglot.expressions.Whens) -> str:
3930    def whens_sql(self, expression: exp.Whens) -> str:
3931        return self.expressions(expression, sep=" ", indent=False)
def merge_sql(self, expression: sqlglot.expressions.Merge) -> str:
3933    def merge_sql(self, expression: exp.Merge) -> str:
3934        table = expression.this
3935        table_alias = ""
3936
3937        hints = table.args.get("hints")
3938        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
3939            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
3940            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
3941
3942        this = self.sql(table)
3943        using = f"USING {self.sql(expression, 'using')}"
3944        on = f"ON {self.sql(expression, 'on')}"
3945        whens = self.sql(expression, "whens")
3946
3947        returning = self.sql(expression, "returning")
3948        if returning:
3949            whens = f"{whens}{returning}"
3950
3951        sep = self.sep()
3952
3953        return self.prepend_ctes(
3954            expression,
3955            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
3956        )
@unsupported_args('format')
def tochar_sql(self, expression: sqlglot.expressions.ToChar) -> str:
3958    @unsupported_args("format")
3959    def tochar_sql(self, expression: exp.ToChar) -> str:
3960        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
def tonumber_sql(self, expression: sqlglot.expressions.ToNumber) -> str:
3962    def tonumber_sql(self, expression: exp.ToNumber) -> str:
3963        if not self.SUPPORTS_TO_NUMBER:
3964            self.unsupported("Unsupported TO_NUMBER function")
3965            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3966
3967        fmt = expression.args.get("format")
3968        if not fmt:
3969            self.unsupported("Conversion format is required for TO_NUMBER")
3970            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3971
3972        return self.func("TO_NUMBER", expression.this, fmt)
def dictproperty_sql(self, expression: sqlglot.expressions.DictProperty) -> str:
3974    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
3975        this = self.sql(expression, "this")
3976        kind = self.sql(expression, "kind")
3977        settings_sql = self.expressions(expression, key="settings", sep=" ")
3978        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
3979        return f"{this}({kind}{args})"
def dictrange_sql(self, expression: sqlglot.expressions.DictRange) -> str:
3981    def dictrange_sql(self, expression: exp.DictRange) -> str:
3982        this = self.sql(expression, "this")
3983        max = self.sql(expression, "max")
3984        min = self.sql(expression, "min")
3985        return f"{this}(MIN {min} MAX {max})"
def dictsubproperty_sql(self, expression: sqlglot.expressions.DictSubProperty) -> str:
3987    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
3988        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
def duplicatekeyproperty_sql(self, expression: sqlglot.expressions.DuplicateKeyProperty) -> str:
3990    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
3991        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
def uniquekeyproperty_sql(self, expression: sqlglot.expressions.UniqueKeyProperty) -> str:
3994    def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str:
3995        return f"UNIQUE KEY ({self.expressions(expression, flat=True)})"
def distributedbyproperty_sql(self, expression: sqlglot.expressions.DistributedByProperty) -> str:
3998    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
3999        expressions = self.expressions(expression, flat=True)
4000        expressions = f" {self.wrap(expressions)}" if expressions else ""
4001        buckets = self.sql(expression, "buckets")
4002        kind = self.sql(expression, "kind")
4003        buckets = f" BUCKETS {buckets}" if buckets else ""
4004        order = self.sql(expression, "order")
4005        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def oncluster_sql(self, expression: sqlglot.expressions.OnCluster) -> str:
4007    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4008        return ""
def clusteredbyproperty_sql(self, expression: sqlglot.expressions.ClusteredByProperty) -> str:
4010    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4011        expressions = self.expressions(expression, key="expressions", flat=True)
4012        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4013        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4014        buckets = self.sql(expression, "buckets")
4015        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
def anyvalue_sql(self, expression: sqlglot.expressions.AnyValue) -> str:
4017    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4018        this = self.sql(expression, "this")
4019        having = self.sql(expression, "having")
4020
4021        if having:
4022            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4023
4024        return self.func("ANY_VALUE", this)
def querytransform_sql(self, expression: sqlglot.expressions.QueryTransform) -> str:
4026    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4027        transform = self.func("TRANSFORM", *expression.expressions)
4028        row_format_before = self.sql(expression, "row_format_before")
4029        row_format_before = f" {row_format_before}" if row_format_before else ""
4030        record_writer = self.sql(expression, "record_writer")
4031        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4032        using = f" USING {self.sql(expression, 'command_script')}"
4033        schema = self.sql(expression, "schema")
4034        schema = f" AS {schema}" if schema else ""
4035        row_format_after = self.sql(expression, "row_format_after")
4036        row_format_after = f" {row_format_after}" if row_format_after else ""
4037        record_reader = self.sql(expression, "record_reader")
4038        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4039        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
def indexconstraintoption_sql(self, expression: sqlglot.expressions.IndexConstraintOption) -> str:
4041    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4042        key_block_size = self.sql(expression, "key_block_size")
4043        if key_block_size:
4044            return f"KEY_BLOCK_SIZE = {key_block_size}"
4045
4046        using = self.sql(expression, "using")
4047        if using:
4048            return f"USING {using}"
4049
4050        parser = self.sql(expression, "parser")
4051        if parser:
4052            return f"WITH PARSER {parser}"
4053
4054        comment = self.sql(expression, "comment")
4055        if comment:
4056            return f"COMMENT {comment}"
4057
4058        visible = expression.args.get("visible")
4059        if visible is not None:
4060            return "VISIBLE" if visible else "INVISIBLE"
4061
4062        engine_attr = self.sql(expression, "engine_attr")
4063        if engine_attr:
4064            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4065
4066        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4067        if secondary_engine_attr:
4068            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4069
4070        self.unsupported("Unsupported index constraint option.")
4071        return ""
def checkcolumnconstraint_sql(self, expression: sqlglot.expressions.CheckColumnConstraint) -> str:
4073    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4074        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4075        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
def indexcolumnconstraint_sql(self, expression: sqlglot.expressions.IndexColumnConstraint) -> str:
4077    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4078        kind = self.sql(expression, "kind")
4079        kind = f"{kind} INDEX" if kind else "INDEX"
4080        this = self.sql(expression, "this")
4081        this = f" {this}" if this else ""
4082        index_type = self.sql(expression, "index_type")
4083        index_type = f" USING {index_type}" if index_type else ""
4084        expressions = self.expressions(expression, flat=True)
4085        expressions = f" ({expressions})" if expressions else ""
4086        options = self.expressions(expression, key="options", sep=" ")
4087        options = f" {options}" if options else ""
4088        return f"{kind}{this}{index_type}{expressions}{options}"
def nvl2_sql(self, expression: sqlglot.expressions.Nvl2) -> str:
4090    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4091        if self.NVL2_SUPPORTED:
4092            return self.function_fallback_sql(expression)
4093
4094        case = exp.Case().when(
4095            expression.this.is_(exp.null()).not_(copy=False),
4096            expression.args["true"],
4097            copy=False,
4098        )
4099        else_cond = expression.args.get("false")
4100        if else_cond:
4101            case.else_(else_cond, copy=False)
4102
4103        return self.sql(case)
def comprehension_sql(self, expression: sqlglot.expressions.Comprehension) -> str:
4105    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4106        this = self.sql(expression, "this")
4107        expr = self.sql(expression, "expression")
4108        iterator = self.sql(expression, "iterator")
4109        condition = self.sql(expression, "condition")
4110        condition = f" IF {condition}" if condition else ""
4111        return f"{this} FOR {expr} IN {iterator}{condition}"
def columnprefix_sql(self, expression: sqlglot.expressions.ColumnPrefix) -> str:
4113    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4114        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
def opclass_sql(self, expression: sqlglot.expressions.Opclass) -> str:
4116    def opclass_sql(self, expression: exp.Opclass) -> str:
4117        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def predict_sql(self, expression: sqlglot.expressions.Predict) -> str:
4119    def predict_sql(self, expression: exp.Predict) -> str:
4120        model = self.sql(expression, "this")
4121        model = f"MODEL {model}"
4122        table = self.sql(expression, "expression")
4123        table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table
4124        parameters = self.sql(expression, "params_struct")
4125        return self.func("PREDICT", model, table, parameters or None)
def forin_sql(self, expression: sqlglot.expressions.ForIn) -> str:
4127    def forin_sql(self, expression: exp.ForIn) -> str:
4128        this = self.sql(expression, "this")
4129        expression_sql = self.sql(expression, "expression")
4130        return f"FOR {this} DO {expression_sql}"
def refresh_sql(self, expression: sqlglot.expressions.Refresh) -> str:
4132    def refresh_sql(self, expression: exp.Refresh) -> str:
4133        this = self.sql(expression, "this")
4134        table = "" if isinstance(expression.this, exp.Literal) else "TABLE "
4135        return f"REFRESH {table}{this}"
def toarray_sql(self, expression: sqlglot.expressions.ToArray) -> str:
4137    def toarray_sql(self, expression: exp.ToArray) -> str:
4138        arg = expression.this
4139        if not arg.type:
4140            from sqlglot.optimizer.annotate_types import annotate_types
4141
4142            arg = annotate_types(arg, dialect=self.dialect)
4143
4144        if arg.is_type(exp.DataType.Type.ARRAY):
4145            return self.sql(arg)
4146
4147        cond_for_null = arg.is_(exp.null())
4148        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:
4150    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4151        this = expression.this
4152        time_format = self.format_time(expression)
4153
4154        if time_format:
4155            return self.sql(
4156                exp.cast(
4157                    exp.StrToTime(this=this, format=expression.args["format"]),
4158                    exp.DataType.Type.TIME,
4159                )
4160            )
4161
4162        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4163            return self.sql(this)
4164
4165        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
def tsordstotimestamp_sql(self, expression: sqlglot.expressions.TsOrDsToTimestamp) -> str:
4167    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4168        this = expression.this
4169        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4170            return self.sql(this)
4171
4172        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
def tsordstodatetime_sql(self, expression: sqlglot.expressions.TsOrDsToDatetime) -> str:
4174    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4175        this = expression.this
4176        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4177            return self.sql(this)
4178
4179        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
def tsordstodate_sql(self, expression: sqlglot.expressions.TsOrDsToDate) -> str:
4181    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4182        this = expression.this
4183        time_format = self.format_time(expression)
4184
4185        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4186            return self.sql(
4187                exp.cast(
4188                    exp.StrToTime(this=this, format=expression.args["format"]),
4189                    exp.DataType.Type.DATE,
4190                )
4191            )
4192
4193        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4194            return self.sql(this)
4195
4196        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
def unixdate_sql(self, expression: sqlglot.expressions.UnixDate) -> str:
4198    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4199        return self.sql(
4200            exp.func(
4201                "DATEDIFF",
4202                expression.this,
4203                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4204                "day",
4205            )
4206        )
def lastday_sql(self, expression: sqlglot.expressions.LastDay) -> str:
4208    def lastday_sql(self, expression: exp.LastDay) -> str:
4209        if self.LAST_DAY_SUPPORTS_DATE_PART:
4210            return self.function_fallback_sql(expression)
4211
4212        unit = expression.text("unit")
4213        if unit and unit != "MONTH":
4214            self.unsupported("Date parts are not supported in LAST_DAY.")
4215
4216        return self.func("LAST_DAY", expression.this)
def dateadd_sql(self, expression: sqlglot.expressions.DateAdd) -> str:
4218    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4219        from sqlglot.dialects.dialect import unit_to_str
4220
4221        return self.func(
4222            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4223        )
def arrayany_sql(self, expression: sqlglot.expressions.ArrayAny) -> str:
4225    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4226        if self.CAN_IMPLEMENT_ARRAY_ANY:
4227            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4228            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4229            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4230            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4231
4232        from sqlglot.dialects import Dialect
4233
4234        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4235        if self.dialect.__class__ != Dialect:
4236            self.unsupported("ARRAY_ANY is unsupported")
4237
4238        return self.function_fallback_sql(expression)
def struct_sql(self, expression: sqlglot.expressions.Struct) -> str:
4240    def struct_sql(self, expression: exp.Struct) -> str:
4241        expression.set(
4242            "expressions",
4243            [
4244                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4245                if isinstance(e, exp.PropertyEQ)
4246                else e
4247                for e in expression.expressions
4248            ],
4249        )
4250
4251        return self.function_fallback_sql(expression)
def partitionrange_sql(self, expression: sqlglot.expressions.PartitionRange) -> str:
4253    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4254        low = self.sql(expression, "this")
4255        high = self.sql(expression, "expression")
4256
4257        return f"{low} TO {high}"
def truncatetable_sql(self, expression: sqlglot.expressions.TruncateTable) -> str:
4259    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4260        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4261        tables = f" {self.expressions(expression)}"
4262
4263        exists = " IF EXISTS" if expression.args.get("exists") else ""
4264
4265        on_cluster = self.sql(expression, "cluster")
4266        on_cluster = f" {on_cluster}" if on_cluster else ""
4267
4268        identity = self.sql(expression, "identity")
4269        identity = f" {identity} IDENTITY" if identity else ""
4270
4271        option = self.sql(expression, "option")
4272        option = f" {option}" if option else ""
4273
4274        partition = self.sql(expression, "partition")
4275        partition = f" {partition}" if partition else ""
4276
4277        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
def convert_sql(self, expression: sqlglot.expressions.Convert) -> str:
4281    def convert_sql(self, expression: exp.Convert) -> str:
4282        to = expression.this
4283        value = expression.expression
4284        style = expression.args.get("style")
4285        safe = expression.args.get("safe")
4286        strict = expression.args.get("strict")
4287
4288        if not to or not value:
4289            return ""
4290
4291        # Retrieve length of datatype and override to default if not specified
4292        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4293            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4294
4295        transformed: t.Optional[exp.Expression] = None
4296        cast = exp.Cast if strict else exp.TryCast
4297
4298        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4299        if isinstance(style, exp.Literal) and style.is_int:
4300            from sqlglot.dialects.tsql import TSQL
4301
4302            style_value = style.name
4303            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4304            if not converted_style:
4305                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4306
4307            fmt = exp.Literal.string(converted_style)
4308
4309            if to.this == exp.DataType.Type.DATE:
4310                transformed = exp.StrToDate(this=value, format=fmt)
4311            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4312                transformed = exp.StrToTime(this=value, format=fmt)
4313            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4314                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4315            elif to.this == exp.DataType.Type.TEXT:
4316                transformed = exp.TimeToStr(this=value, format=fmt)
4317
4318        if not transformed:
4319            transformed = cast(this=value, to=to, safe=safe)
4320
4321        return self.sql(transformed)
def copyparameter_sql(self, expression: sqlglot.expressions.CopyParameter) -> str:
4389    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4390        option = self.sql(expression, "this")
4391
4392        if expression.expressions:
4393            upper = option.upper()
4394
4395            # Snowflake FILE_FORMAT options are separated by whitespace
4396            sep = " " if upper == "FILE_FORMAT" else ", "
4397
4398            # Databricks copy/format options do not set their list of values with EQ
4399            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4400            values = self.expressions(expression, flat=True, sep=sep)
4401            return f"{option}{op}({values})"
4402
4403        value = self.sql(expression, "expression")
4404
4405        if not value:
4406            return option
4407
4408        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4409
4410        return f"{option}{op}{value}"
def credentials_sql(self, expression: sqlglot.expressions.Credentials) -> str:
4412    def credentials_sql(self, expression: exp.Credentials) -> str:
4413        cred_expr = expression.args.get("credentials")
4414        if isinstance(cred_expr, exp.Literal):
4415            # Redshift case: CREDENTIALS <string>
4416            credentials = self.sql(expression, "credentials")
4417            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4418        else:
4419            # Snowflake case: CREDENTIALS = (...)
4420            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4421            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4422
4423        storage = self.sql(expression, "storage")
4424        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4425
4426        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4427        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4428
4429        iam_role = self.sql(expression, "iam_role")
4430        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4431
4432        region = self.sql(expression, "region")
4433        region = f" REGION {region}" if region else ""
4434
4435        return f"{credentials}{storage}{encryption}{iam_role}{region}"
def copy_sql(self, expression: sqlglot.expressions.Copy) -> str:
4437    def copy_sql(self, expression: exp.Copy) -> str:
4438        this = self.sql(expression, "this")
4439        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4440
4441        credentials = self.sql(expression, "credentials")
4442        credentials = self.seg(credentials) if credentials else ""
4443        kind = self.seg("FROM" if expression.args.get("kind") else "TO")
4444        files = self.expressions(expression, key="files", flat=True)
4445
4446        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4447        params = self.expressions(
4448            expression,
4449            key="params",
4450            sep=sep,
4451            new_line=True,
4452            skip_last=True,
4453            skip_first=True,
4454            indent=self.COPY_PARAMS_ARE_WRAPPED,
4455        )
4456
4457        if params:
4458            if self.COPY_PARAMS_ARE_WRAPPED:
4459                params = f" WITH ({params})"
4460            elif not self.pretty:
4461                params = f" {params}"
4462
4463        return f"COPY{this}{kind} {files}{credentials}{params}"
def semicolon_sql(self, expression: sqlglot.expressions.Semicolon) -> str:
4465    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4466        return ""
def datadeletionproperty_sql(self, expression: sqlglot.expressions.DataDeletionProperty) -> str:
4468    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4469        on_sql = "ON" if expression.args.get("on") else "OFF"
4470        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4471        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4472        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4473        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4474
4475        if filter_col or retention_period:
4476            on_sql = self.func("ON", filter_col, retention_period)
4477
4478        return f"DATA_DELETION={on_sql}"
def maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4480    def maskingpolicycolumnconstraint_sql(
4481        self, expression: exp.MaskingPolicyColumnConstraint
4482    ) -> str:
4483        this = self.sql(expression, "this")
4484        expressions = self.expressions(expression, flat=True)
4485        expressions = f" USING ({expressions})" if expressions else ""
4486        return f"MASKING POLICY {this}{expressions}"
def gapfill_sql(self, expression: sqlglot.expressions.GapFill) -> str:
4488    def gapfill_sql(self, expression: exp.GapFill) -> str:
4489        this = self.sql(expression, "this")
4490        this = f"TABLE {this}"
4491        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:
4493    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4494        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
def scoperesolution_sql(self, expression: sqlglot.expressions.ScopeResolution) -> str:
4496    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4497        this = self.sql(expression, "this")
4498        expr = expression.expression
4499
4500        if isinstance(expr, exp.Func):
4501            # T-SQL's CLR functions are case sensitive
4502            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4503        else:
4504            expr = self.sql(expression, "expression")
4505
4506        return self.scope_resolution(expr, this)
def parsejson_sql(self, expression: sqlglot.expressions.ParseJSON) -> str:
4508    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4509        if self.PARSE_JSON_NAME is None:
4510            return self.sql(expression.this)
4511
4512        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
def rand_sql(self, expression: sqlglot.expressions.Rand) -> str:
4514    def rand_sql(self, expression: exp.Rand) -> str:
4515        lower = self.sql(expression, "lower")
4516        upper = self.sql(expression, "upper")
4517
4518        if lower and upper:
4519            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4520        return self.func("RAND", expression.this)
def changes_sql(self, expression: sqlglot.expressions.Changes) -> str:
4522    def changes_sql(self, expression: exp.Changes) -> str:
4523        information = self.sql(expression, "information")
4524        information = f"INFORMATION => {information}"
4525        at_before = self.sql(expression, "at_before")
4526        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4527        end = self.sql(expression, "end")
4528        end = f"{self.seg('')}{end}" if end else ""
4529
4530        return f"CHANGES ({information}){at_before}{end}"
def pad_sql(self, expression: sqlglot.expressions.Pad) -> str:
4532    def pad_sql(self, expression: exp.Pad) -> str:
4533        prefix = "L" if expression.args.get("is_left") else "R"
4534
4535        fill_pattern = self.sql(expression, "fill_pattern") or None
4536        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4537            fill_pattern = "' '"
4538
4539        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def summarize_sql(self, expression: sqlglot.expressions.Summarize) -> str:
4541    def summarize_sql(self, expression: exp.Summarize) -> str:
4542        table = " TABLE" if expression.args.get("table") else ""
4543        return f"SUMMARIZE{table} {self.sql(expression.this)}"
def explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4545    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4546        generate_series = exp.GenerateSeries(**expression.args)
4547
4548        parent = expression.parent
4549        if isinstance(parent, (exp.Alias, exp.TableAlias)):
4550            parent = parent.parent
4551
4552        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
4553            return self.sql(exp.Unnest(expressions=[generate_series]))
4554
4555        if isinstance(parent, exp.Select):
4556            self.unsupported("GenerateSeries projection unnesting is not supported.")
4557
4558        return self.sql(generate_series)
def arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4560    def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str:
4561        exprs = expression.expressions
4562        if not self.ARRAY_CONCAT_IS_VAR_LEN:
4563            rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs)
4564        else:
4565            rhs = self.expressions(expression)
4566
4567        return self.func(name, expression.this, rhs or None)
def converttimezone_sql(self, expression: sqlglot.expressions.ConvertTimezone) -> str:
4569    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
4570        if self.SUPPORTS_CONVERT_TIMEZONE:
4571            return self.function_fallback_sql(expression)
4572
4573        source_tz = expression.args.get("source_tz")
4574        target_tz = expression.args.get("target_tz")
4575        timestamp = expression.args.get("timestamp")
4576
4577        if source_tz and timestamp:
4578            timestamp = exp.AtTimeZone(
4579                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
4580            )
4581
4582        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
4583
4584        return self.sql(expr)
def json_sql(self, expression: sqlglot.expressions.JSON) -> str:
4586    def json_sql(self, expression: exp.JSON) -> str:
4587        this = self.sql(expression, "this")
4588        this = f" {this}" if this else ""
4589
4590        _with = expression.args.get("with")
4591
4592        if _with is None:
4593            with_sql = ""
4594        elif not _with:
4595            with_sql = " WITHOUT"
4596        else:
4597            with_sql = " WITH"
4598
4599        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
4600
4601        return f"JSON{this}{with_sql}{unique_sql}"
def jsonvalue_sql(self, expression: sqlglot.expressions.JSONValue) -> str:
4603    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
4604        def _generate_on_options(arg: t.Any) -> str:
4605            return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}"
4606
4607        path = self.sql(expression, "path")
4608        returning = self.sql(expression, "returning")
4609        returning = f" RETURNING {returning}" if returning else ""
4610
4611        on_condition = self.sql(expression, "on_condition")
4612        on_condition = f" {on_condition}" if on_condition else ""
4613
4614        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
def conditionalinsert_sql(self, expression: sqlglot.expressions.ConditionalInsert) -> str:
4616    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
4617        else_ = "ELSE " if expression.args.get("else_") else ""
4618        condition = self.sql(expression, "expression")
4619        condition = f"WHEN {condition} THEN " if condition else else_
4620        insert = self.sql(expression, "this")[len("INSERT") :].strip()
4621        return f"{condition}{insert}"
def multitableinserts_sql(self, expression: sqlglot.expressions.MultitableInserts) -> str:
4623    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
4624        kind = self.sql(expression, "kind")
4625        expressions = self.seg(self.expressions(expression, sep=" "))
4626        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
4627        return res
def oncondition_sql(self, expression: sqlglot.expressions.OnCondition) -> str:
4629    def oncondition_sql(self, expression: exp.OnCondition) -> str:
4630        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
4631        empty = expression.args.get("empty")
4632        empty = (
4633            f"DEFAULT {empty} ON EMPTY"
4634            if isinstance(empty, exp.Expression)
4635            else self.sql(expression, "empty")
4636        )
4637
4638        error = expression.args.get("error")
4639        error = (
4640            f"DEFAULT {error} ON ERROR"
4641            if isinstance(error, exp.Expression)
4642            else self.sql(expression, "error")
4643        )
4644
4645        if error and empty:
4646            error = (
4647                f"{empty} {error}"
4648                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
4649                else f"{error} {empty}"
4650            )
4651            empty = ""
4652
4653        null = self.sql(expression, "null")
4654
4655        return f"{empty}{error}{null}"
def jsonextractquote_sql(self, expression: sqlglot.expressions.JSONExtractQuote) -> str:
4657    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
4658        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
4659        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
def jsonexists_sql(self, expression: sqlglot.expressions.JSONExists) -> str:
4661    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
4662        this = self.sql(expression, "this")
4663        path = self.sql(expression, "path")
4664
4665        passing = self.expressions(expression, "passing")
4666        passing = f" PASSING {passing}" if passing else ""
4667
4668        on_condition = self.sql(expression, "on_condition")
4669        on_condition = f" {on_condition}" if on_condition else ""
4670
4671        path = f"{path}{passing}{on_condition}"
4672
4673        return self.func("JSON_EXISTS", this, path)
def arrayagg_sql(self, expression: sqlglot.expressions.ArrayAgg) -> str:
4675    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
4676        array_agg = self.function_fallback_sql(expression)
4677
4678        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
4679        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
4680        if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"):
4681            parent = expression.parent
4682            if isinstance(parent, exp.Filter):
4683                parent_cond = parent.expression.this
4684                parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_()))
4685            else:
4686                this = expression.this
4687                # Do not add the filter if the input is not a column (e.g. literal, struct etc)
4688                if this.find(exp.Column):
4689                    # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
4690                    this_sql = (
4691                        self.expressions(this)
4692                        if isinstance(this, exp.Distinct)
4693                        else self.sql(expression, "this")
4694                    )
4695
4696                    array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)"
4697
4698        return array_agg
def apply_sql(self, expression: sqlglot.expressions.Apply) -> str:
4700    def apply_sql(self, expression: exp.Apply) -> str:
4701        this = self.sql(expression, "this")
4702        expr = self.sql(expression, "expression")
4703
4704        return f"{this} APPLY({expr})"
def grant_sql(self, expression: sqlglot.expressions.Grant) -> str:
4706    def grant_sql(self, expression: exp.Grant) -> str:
4707        privileges_sql = self.expressions(expression, key="privileges", flat=True)
4708
4709        kind = self.sql(expression, "kind")
4710        kind = f" {kind}" if kind else ""
4711
4712        securable = self.sql(expression, "securable")
4713        securable = f" {securable}" if securable else ""
4714
4715        principals = self.expressions(expression, key="principals", flat=True)
4716
4717        grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else ""
4718
4719        return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
def grantprivilege_sql(self, expression: sqlglot.expressions.GrantPrivilege):
4721    def grantprivilege_sql(self, expression: exp.GrantPrivilege):
4722        this = self.sql(expression, "this")
4723        columns = self.expressions(expression, flat=True)
4724        columns = f"({columns})" if columns else ""
4725
4726        return f"{this}{columns}"
def grantprincipal_sql(self, expression: sqlglot.expressions.GrantPrincipal):
4728    def grantprincipal_sql(self, expression: exp.GrantPrincipal):
4729        this = self.sql(expression, "this")
4730
4731        kind = self.sql(expression, "kind")
4732        kind = f"{kind} " if kind else ""
4733
4734        return f"{kind}{this}"
def columns_sql(self, expression: sqlglot.expressions.Columns):
4736    def columns_sql(self, expression: exp.Columns):
4737        func = self.function_fallback_sql(expression)
4738        if expression.args.get("unpack"):
4739            func = f"*{func}"
4740
4741        return func
def overlay_sql(self, expression: sqlglot.expressions.Overlay):
4743    def overlay_sql(self, expression: exp.Overlay):
4744        this = self.sql(expression, "this")
4745        expr = self.sql(expression, "expression")
4746        from_sql = self.sql(expression, "from")
4747        for_sql = self.sql(expression, "for")
4748        for_sql = f" FOR {for_sql}" if for_sql else ""
4749
4750        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4752    @unsupported_args("format")
4753    def todouble_sql(self, expression: exp.ToDouble) -> str:
4754        return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
def string_sql(self, expression: sqlglot.expressions.String) -> str:
4756    def string_sql(self, expression: exp.String) -> str:
4757        this = expression.this
4758        zone = expression.args.get("zone")
4759
4760        if zone:
4761            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
4762            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
4763            # set for source_tz to transpile the time conversion before the STRING cast
4764            this = exp.ConvertTimezone(
4765                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
4766            )
4767
4768        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def median_sql(self, expression: sqlglot.expressions.Median):
4770    def median_sql(self, expression: exp.Median):
4771        if not self.SUPPORTS_MEDIAN:
4772            return self.sql(
4773                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
4774            )
4775
4776        return self.function_fallback_sql(expression)
def overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4778    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
4779        filler = self.sql(expression, "this")
4780        filler = f" {filler}" if filler else ""
4781        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
4782        return f"TRUNCATE{filler} {with_count}"
def unixseconds_sql(self, expression: sqlglot.expressions.UnixSeconds) -> str:
4784    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
4785        if self.SUPPORTS_UNIX_SECONDS:
4786            return self.function_fallback_sql(expression)
4787
4788        start_ts = exp.cast(
4789            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
4790        )
4791
4792        return self.sql(
4793            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
4794        )
def arraysize_sql(self, expression: sqlglot.expressions.ArraySize) -> str:
4796    def arraysize_sql(self, expression: exp.ArraySize) -> str:
4797        dim = expression.expression
4798
4799        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
4800        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
4801            if not (dim.is_int and dim.name == "1"):
4802                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
4803            dim = None
4804
4805        # If dimension is required but not specified, default initialize it
4806        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
4807            dim = exp.Literal.number(1)
4808
4809        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
def attach_sql(self, expression: sqlglot.expressions.Attach) -> str:
4811    def attach_sql(self, expression: exp.Attach) -> str:
4812        this = self.sql(expression, "this")
4813        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
4814        expressions = self.expressions(expression)
4815        expressions = f" ({expressions})" if expressions else ""
4816
4817        return f"ATTACH{exists_sql} {this}{expressions}"
def detach_sql(self, expression: sqlglot.expressions.Detach) -> str:
4819    def detach_sql(self, expression: exp.Detach) -> str:
4820        this = self.sql(expression, "this")
4821        # the DATABASE keyword is required if IF EXISTS is set
4822        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
4823        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
4824        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
4825
4826        return f"DETACH{exists_sql} {this}"
def attachoption_sql(self, expression: sqlglot.expressions.AttachOption) -> str:
4828    def attachoption_sql(self, expression: exp.AttachOption) -> str:
4829        this = self.sql(expression, "this")
4830        value = self.sql(expression, "expression")
4831        value = f" {value}" if value else ""
4832        return f"{this}{value}"
def featuresattime_sql(self, expression: sqlglot.expressions.FeaturesAtTime) -> str:
4834    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4835        this_sql = self.sql(expression, "this")
4836        if isinstance(expression.this, exp.Table):
4837            this_sql = f"TABLE {this_sql}"
4838
4839        return self.func(
4840            "FEATURES_AT_TIME",
4841            this_sql,
4842            expression.args.get("time"),
4843            expression.args.get("num_rows"),
4844            expression.args.get("ignore_feature_nulls"),
4845        )
def watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
4847    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
4848        return (
4849            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
4850        )
def encodeproperty_sql(self, expression: sqlglot.expressions.EncodeProperty) -> str:
4852    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
4853        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
4854        encode = f"{encode} {self.sql(expression, 'this')}"
4855
4856        properties = expression.args.get("properties")
4857        if properties:
4858            encode = f"{encode} {self.properties(properties)}"
4859
4860        return encode
def includeproperty_sql(self, expression: sqlglot.expressions.IncludeProperty) -> str:
4862    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
4863        this = self.sql(expression, "this")
4864        include = f"INCLUDE {this}"
4865
4866        column_def = self.sql(expression, "column_def")
4867        if column_def:
4868            include = f"{include} {column_def}"
4869
4870        alias = self.sql(expression, "alias")
4871        if alias:
4872            include = f"{include} AS {alias}"
4873
4874        return include
def xmlelement_sql(self, expression: sqlglot.expressions.XMLElement) -> str:
4876    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
4877        name = f"NAME {self.sql(expression, 'this')}"
4878        return self.func("XMLELEMENT", name, *expression.expressions)
def xmlkeyvalueoption_sql(self, expression: sqlglot.expressions.XMLKeyValueOption) -> str:
4880    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
4881        this = self.sql(expression, "this")
4882        expr = self.sql(expression, "expression")
4883        expr = f"({expr})" if expr else ""
4884        return f"{this}{expr}"
def partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
4886    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
4887        partitions = self.expressions(expression, "partition_expressions")
4888        create = self.expressions(expression, "create_expressions")
4889        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
4891    def partitionbyrangepropertydynamic_sql(
4892        self, expression: exp.PartitionByRangePropertyDynamic
4893    ) -> str:
4894        start = self.sql(expression, "start")
4895        end = self.sql(expression, "end")
4896
4897        every = expression.args["every"]
4898        if isinstance(every, exp.Interval) and every.this.is_string:
4899            every.this.replace(exp.Literal.number(every.name))
4900
4901        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:
4903    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
4904        name = self.sql(expression, "this")
4905        values = self.expressions(expression, flat=True)
4906
4907        return f"NAME {name} VALUE {values}"
def analyzesample_sql(self, expression: sqlglot.expressions.AnalyzeSample) -> str:
4909    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
4910        kind = self.sql(expression, "kind")
4911        sample = self.sql(expression, "sample")
4912        return f"SAMPLE {sample} {kind}"
def analyzestatistics_sql(self, expression: sqlglot.expressions.AnalyzeStatistics) -> str:
4914    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
4915        kind = self.sql(expression, "kind")
4916        option = self.sql(expression, "option")
4917        option = f" {option}" if option else ""
4918        this = self.sql(expression, "this")
4919        this = f" {this}" if this else ""
4920        columns = self.expressions(expression)
4921        columns = f" {columns}" if columns else ""
4922        return f"{kind}{option} STATISTICS{this}{columns}"
def analyzehistogram_sql(self, expression: sqlglot.expressions.AnalyzeHistogram) -> str:
4924    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
4925        this = self.sql(expression, "this")
4926        columns = self.expressions(expression)
4927        inner_expression = self.sql(expression, "expression")
4928        inner_expression = f" {inner_expression}" if inner_expression else ""
4929        update_options = self.sql(expression, "update_options")
4930        update_options = f" {update_options} UPDATE" if update_options else ""
4931        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def analyzedelete_sql(self, expression: sqlglot.expressions.AnalyzeDelete) -> str:
4933    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
4934        kind = self.sql(expression, "kind")
4935        kind = f" {kind}" if kind else ""
4936        return f"DELETE{kind} STATISTICS"
def analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
4938    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
4939        inner_expression = self.sql(expression, "expression")
4940        return f"LIST CHAINED ROWS{inner_expression}"
def analyzevalidate_sql(self, expression: sqlglot.expressions.AnalyzeValidate) -> str:
4942    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
4943        kind = self.sql(expression, "kind")
4944        this = self.sql(expression, "this")
4945        this = f" {this}" if this else ""
4946        inner_expression = self.sql(expression, "expression")
4947        return f"VALIDATE {kind}{this}{inner_expression}"
def analyze_sql(self, expression: sqlglot.expressions.Analyze) -> str:
4949    def analyze_sql(self, expression: exp.Analyze) -> str:
4950        options = self.expressions(expression, key="options", sep=" ")
4951        options = f" {options}" if options else ""
4952        kind = self.sql(expression, "kind")
4953        kind = f" {kind}" if kind else ""
4954        this = self.sql(expression, "this")
4955        this = f" {this}" if this else ""
4956        mode = self.sql(expression, "mode")
4957        mode = f" {mode}" if mode else ""
4958        properties = self.sql(expression, "properties")
4959        properties = f" {properties}" if properties else ""
4960        partition = self.sql(expression, "partition")
4961        partition = f" {partition}" if partition else ""
4962        inner_expression = self.sql(expression, "expression")
4963        inner_expression = f" {inner_expression}" if inner_expression else ""
4964        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
def xmltable_sql(self, expression: sqlglot.expressions.XMLTable) -> str:
4966    def xmltable_sql(self, expression: exp.XMLTable) -> str:
4967        this = self.sql(expression, "this")
4968        namespaces = self.expressions(expression, key="namespaces")
4969        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
4970        passing = self.expressions(expression, key="passing")
4971        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
4972        columns = self.expressions(expression, key="columns")
4973        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
4974        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
4975        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:
4977    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
4978        this = self.sql(expression, "this")
4979        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
def export_sql(self, expression: sqlglot.expressions.Export) -> str:
4981    def export_sql(self, expression: exp.Export) -> str:
4982        this = self.sql(expression, "this")
4983        connection = self.sql(expression, "connection")
4984        connection = f"WITH CONNECTION {connection} " if connection else ""
4985        options = self.sql(expression, "options")
4986        return f"EXPORT DATA {connection}{options} AS {this}"
def declare_sql(self, expression: sqlglot.expressions.Declare) -> str:
4988    def declare_sql(self, expression: exp.Declare) -> str:
4989        return f"DECLARE {self.expressions(expression, flat=True)}"
def declareitem_sql(self, expression: sqlglot.expressions.DeclareItem) -> str:
4991    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
4992        variable = self.sql(expression, "this")
4993        default = self.sql(expression, "default")
4994        default = f" = {default}" if default else ""
4995
4996        kind = self.sql(expression, "kind")
4997        if isinstance(expression.args.get("kind"), exp.Schema):
4998            kind = f"TABLE {kind}"
4999
5000        return f"{variable} AS {kind}{default}"
def recursivewithsearch_sql(self, expression: sqlglot.expressions.RecursiveWithSearch) -> str:
5002    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5003        kind = self.sql(expression, "kind")
5004        this = self.sql(expression, "this")
5005        set = self.sql(expression, "expression")
5006        using = self.sql(expression, "using")
5007        using = f" USING {using}" if using else ""
5008
5009        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5010
5011        return f"{kind_sql} {this} SET {set}{using}"
def parameterizedagg_sql(self, expression: sqlglot.expressions.ParameterizedAgg) -> str:
5013    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5014        params = self.expressions(expression, key="params", flat=True)
5015        return self.func(expression.name, *expression.expressions) + f"({params})"
def anonymousaggfunc_sql(self, expression: sqlglot.expressions.AnonymousAggFunc) -> str:
5017    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5018        return self.func(expression.name, *expression.expressions)
def combinedaggfunc_sql(self, expression: sqlglot.expressions.CombinedAggFunc) -> str:
5020    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5021        return self.anonymousaggfunc_sql(expression)
def combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5023    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5024        return self.parameterizedagg_sql(expression)
def show_sql(self, expression: sqlglot.expressions.Show) -> str:
5026    def show_sql(self, expression: exp.Show) -> str:
5027        self.unsupported("Unsupported SHOW statement")
5028        return ""
def get_put_sql( self, expression: sqlglot.expressions.Put | sqlglot.expressions.Get) -> str:
5030    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5031        # Snowflake GET/PUT statements:
5032        #   PUT <file> <internalStage> <properties>
5033        #   GET <internalStage> <file> <properties>
5034        props = expression.args.get("properties")
5035        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5036        this = self.sql(expression, "this")
5037        target = self.sql(expression, "target")
5038
5039        if isinstance(expression, exp.Put):
5040            return f"PUT {this} {target}{props_sql}"
5041        else:
5042            return f"GET {target} {this}{props_sql}"
def translatecharacters_sql(self, expression: sqlglot.expressions.TranslateCharacters):
5044    def translatecharacters_sql(self, expression: exp.TranslateCharacters):
5045        this = self.sql(expression, "this")
5046        expr = self.sql(expression, "expression")
5047        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5048        return f"TRANSLATE({this} USING {expr}{with_error})"
def decodecase_sql(self, expression: sqlglot.expressions.DecodeCase) -> str:
5050    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5051        if self.SUPPORTS_DECODE_CASE:
5052            return self.func("DECODE", *expression.expressions)
5053
5054        expression, *expressions = expression.expressions
5055
5056        ifs = []
5057        for search, result in zip(expressions[::2], expressions[1::2]):
5058            if isinstance(search, exp.Literal):
5059                ifs.append(exp.If(this=expression.eq(search), true=result))
5060            elif isinstance(search, exp.Null):
5061                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5062            else:
5063                if isinstance(search, exp.Binary):
5064                    search = exp.paren(search)
5065
5066                cond = exp.or_(
5067                    expression.eq(search),
5068                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5069                    copy=False,
5070                )
5071                ifs.append(exp.If(this=cond, true=result))
5072
5073        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5074        return self.sql(case)
def semanticview_sql(self, expression: sqlglot.expressions.SemanticView) -> str:
5076    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5077        this = self.sql(expression, "this")
5078        this = self.seg(this, sep="")
5079        dimensions = self.expressions(
5080            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5081        )
5082        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5083        metrics = self.expressions(
5084            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5085        )
5086        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5087        where = self.sql(expression, "where")
5088        where = self.seg(f"WHERE {where}") if where else ""
5089        return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"