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.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 164        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 165        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 166        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 167        exp.LocationProperty: lambda self, e: self.naked_property(e),
 168        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 169        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 170        exp.NonClusteredColumnConstraint: lambda self,
 171        e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})",
 172        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 173        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 174        exp.OnCommitProperty: lambda _,
 175        e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS",
 176        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 177        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 178        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 179        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 180        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 181        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 182        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 183        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 184        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 185        exp.ProjectionPolicyColumnConstraint: lambda self,
 186        e: f"PROJECTION POLICY {self.sql(e, 'this')}",
 187        exp.Put: lambda self, e: self.get_put_sql(e),
 188        exp.RemoteWithConnectionModelProperty: lambda self,
 189        e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
 190        exp.ReturnsProperty: lambda self, e: (
 191            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 192        ),
 193        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 194        exp.SecureProperty: lambda *_: "SECURE",
 195        exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}",
 196        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 197        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 198        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 199        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 200        exp.SqlReadWriteProperty: lambda _, e: e.name,
 201        exp.SqlSecurityProperty: lambda _,
 202        e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}",
 203        exp.StabilityProperty: lambda _, e: e.name,
 204        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 205        exp.StreamingTableProperty: lambda *_: "STREAMING",
 206        exp.StrictProperty: lambda *_: "STRICT",
 207        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 208        exp.TableColumn: lambda self, e: self.sql(e.this),
 209        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 210        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 211        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 212        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 213        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 214        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 215        exp.TransientProperty: lambda *_: "TRANSIENT",
 216        exp.Union: lambda self, e: self.set_operations(e),
 217        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 218        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 219        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 220        exp.Uuid: lambda *_: "UUID()",
 221        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 222        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 223        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 224        exp.UtcTimestamp: lambda self, e: self.sql(
 225            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 226        ),
 227        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 228        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 229        exp.VolatileProperty: lambda *_: "VOLATILE",
 230        exp.WeekStart: lambda self, e: f"WEEK({self.sql(e, 'this')})",
 231        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 232        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 233        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 234        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 235        exp.ForceProperty: lambda *_: "FORCE",
 236    }
 237
 238    # Whether null ordering is supported in order by
 239    # True: Full Support, None: No support, False: No support for certain cases
 240    # such as window specifications, aggregate functions etc
 241    NULL_ORDERING_SUPPORTED: t.Optional[bool] = True
 242
 243    # Whether ignore nulls is inside the agg or outside.
 244    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 245    IGNORE_NULLS_IN_FUNC = False
 246
 247    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 248    LOCKING_READS_SUPPORTED = False
 249
 250    # Whether the EXCEPT and INTERSECT operations can return duplicates
 251    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 252
 253    # Wrap derived values in parens, usually standard but spark doesn't support it
 254    WRAP_DERIVED_VALUES = True
 255
 256    # Whether create function uses an AS before the RETURN
 257    CREATE_FUNCTION_RETURN_AS = True
 258
 259    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 260    MATCHED_BY_SOURCE = True
 261
 262    # Whether the INTERVAL expression works only with values like '1 day'
 263    SINGLE_STRING_INTERVAL = False
 264
 265    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 266    INTERVAL_ALLOWS_PLURAL_FORM = True
 267
 268    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 269    LIMIT_FETCH = "ALL"
 270
 271    # Whether limit and fetch allows expresions or just limits
 272    LIMIT_ONLY_LITERALS = False
 273
 274    # Whether a table is allowed to be renamed with a db
 275    RENAME_TABLE_WITH_DB = True
 276
 277    # The separator for grouping sets and rollups
 278    GROUPINGS_SEP = ","
 279
 280    # The string used for creating an index on a table
 281    INDEX_ON = "ON"
 282
 283    # Whether join hints should be generated
 284    JOIN_HINTS = True
 285
 286    # Whether table hints should be generated
 287    TABLE_HINTS = True
 288
 289    # Whether query hints should be generated
 290    QUERY_HINTS = True
 291
 292    # What kind of separator to use for query hints
 293    QUERY_HINT_SEP = ", "
 294
 295    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 296    IS_BOOL_ALLOWED = True
 297
 298    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 299    DUPLICATE_KEY_UPDATE_WITH_SET = True
 300
 301    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 302    LIMIT_IS_TOP = False
 303
 304    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 305    RETURNING_END = True
 306
 307    # Whether to generate an unquoted value for EXTRACT's date part argument
 308    EXTRACT_ALLOWS_QUOTES = True
 309
 310    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 311    TZ_TO_WITH_TIME_ZONE = False
 312
 313    # Whether the NVL2 function is supported
 314    NVL2_SUPPORTED = True
 315
 316    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 317    SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE")
 318
 319    # Whether VALUES statements can be used as derived tables.
 320    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 321    # SELECT * VALUES into SELECT UNION
 322    VALUES_AS_TABLE = True
 323
 324    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 325    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 326
 327    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 328    UNNEST_WITH_ORDINALITY = True
 329
 330    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 331    AGGREGATE_FILTER_SUPPORTED = True
 332
 333    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 334    SEMI_ANTI_JOIN_WITH_SIDE = True
 335
 336    # Whether to include the type of a computed column in the CREATE DDL
 337    COMPUTED_COLUMN_WITH_TYPE = True
 338
 339    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 340    SUPPORTS_TABLE_COPY = True
 341
 342    # Whether parentheses are required around the table sample's expression
 343    TABLESAMPLE_REQUIRES_PARENS = True
 344
 345    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 346    TABLESAMPLE_SIZE_IS_ROWS = True
 347
 348    # The keyword(s) to use when generating a sample clause
 349    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 350
 351    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 352    TABLESAMPLE_WITH_METHOD = True
 353
 354    # The keyword to use when specifying the seed of a sample clause
 355    TABLESAMPLE_SEED_KEYWORD = "SEED"
 356
 357    # Whether COLLATE is a function instead of a binary operator
 358    COLLATE_IS_FUNC = False
 359
 360    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 361    DATA_TYPE_SPECIFIERS_ALLOWED = False
 362
 363    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 364    ENSURE_BOOLS = False
 365
 366    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 367    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 368
 369    # Whether CONCAT requires >1 arguments
 370    SUPPORTS_SINGLE_ARG_CONCAT = True
 371
 372    # Whether LAST_DAY function supports a date part argument
 373    LAST_DAY_SUPPORTS_DATE_PART = True
 374
 375    # Whether named columns are allowed in table aliases
 376    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 377
 378    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 379    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 380
 381    # What delimiter to use for separating JSON key/value pairs
 382    JSON_KEY_VALUE_PAIR_SEP = ":"
 383
 384    # INSERT OVERWRITE TABLE x override
 385    INSERT_OVERWRITE = " OVERWRITE TABLE"
 386
 387    # Whether the SELECT .. INTO syntax is used instead of CTAS
 388    SUPPORTS_SELECT_INTO = False
 389
 390    # Whether UNLOGGED tables can be created
 391    SUPPORTS_UNLOGGED_TABLES = False
 392
 393    # Whether the CREATE TABLE LIKE statement is supported
 394    SUPPORTS_CREATE_TABLE_LIKE = True
 395
 396    # Whether the LikeProperty needs to be specified inside of the schema clause
 397    LIKE_PROPERTY_INSIDE_SCHEMA = False
 398
 399    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 400    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 401    MULTI_ARG_DISTINCT = True
 402
 403    # Whether the JSON extraction operators expect a value of type JSON
 404    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 405
 406    # Whether bracketed keys like ["foo"] are supported in JSON paths
 407    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 408
 409    # Whether to escape keys using single quotes in JSON paths
 410    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 411
 412    # The JSONPathPart expressions supported by this dialect
 413    SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy()
 414
 415    # Whether any(f(x) for x in array) can be implemented by this dialect
 416    CAN_IMPLEMENT_ARRAY_ANY = False
 417
 418    # Whether the function TO_NUMBER is supported
 419    SUPPORTS_TO_NUMBER = True
 420
 421    # Whether EXCLUDE in window specification is supported
 422    SUPPORTS_WINDOW_EXCLUDE = False
 423
 424    # Whether or not set op modifiers apply to the outer set op or select.
 425    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 426    # True means limit 1 happens after the set op, False means it it happens on y.
 427    SET_OP_MODIFIERS = True
 428
 429    # Whether parameters from COPY statement are wrapped in parentheses
 430    COPY_PARAMS_ARE_WRAPPED = True
 431
 432    # Whether values of params are set with "=" token or empty space
 433    COPY_PARAMS_EQ_REQUIRED = False
 434
 435    # Whether COPY statement has INTO keyword
 436    COPY_HAS_INTO_KEYWORD = True
 437
 438    # Whether the conditional TRY(expression) function is supported
 439    TRY_SUPPORTED = True
 440
 441    # Whether the UESCAPE syntax in unicode strings is supported
 442    SUPPORTS_UESCAPE = True
 443
 444    # Function used to replace escaped unicode codes in unicode strings
 445    UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None
 446
 447    # The keyword to use when generating a star projection with excluded columns
 448    STAR_EXCEPT = "EXCEPT"
 449
 450    # The HEX function name
 451    HEX_FUNC = "HEX"
 452
 453    # The keywords to use when prefixing & separating WITH based properties
 454    WITH_PROPERTIES_PREFIX = "WITH"
 455
 456    # Whether to quote the generated expression of exp.JsonPath
 457    QUOTE_JSON_PATH = True
 458
 459    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 460    PAD_FILL_PATTERN_IS_REQUIRED = False
 461
 462    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 463    SUPPORTS_EXPLODING_PROJECTIONS = True
 464
 465    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 466    ARRAY_CONCAT_IS_VAR_LEN = True
 467
 468    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 469    SUPPORTS_CONVERT_TIMEZONE = False
 470
 471    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 472    SUPPORTS_MEDIAN = True
 473
 474    # Whether UNIX_SECONDS(timestamp) is supported
 475    SUPPORTS_UNIX_SECONDS = False
 476
 477    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 478    ALTER_SET_WRAPPED = False
 479
 480    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 481    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 482    # TODO: The normalization should be done by default once we've tested it across all dialects.
 483    NORMALIZE_EXTRACT_DATE_PARTS = False
 484
 485    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 486    PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
 487
 488    # The function name of the exp.ArraySize expression
 489    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 490
 491    # The syntax to use when altering the type of a column
 492    ALTER_SET_TYPE = "SET DATA TYPE"
 493
 494    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 495    # None -> Doesn't support it at all
 496    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 497    # True (Postgres) -> Explicitly requires it
 498    ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None
 499
 500    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 501    SUPPORTS_DECODE_CASE = True
 502
 503    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 504    SUPPORTS_BETWEEN_FLAGS = False
 505
 506    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 507    SUPPORTS_LIKE_QUANTIFIERS = True
 508
 509    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 510    MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None
 511
 512    TYPE_MAPPING = {
 513        exp.DataType.Type.DATETIME2: "TIMESTAMP",
 514        exp.DataType.Type.NCHAR: "CHAR",
 515        exp.DataType.Type.NVARCHAR: "VARCHAR",
 516        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 517        exp.DataType.Type.LONGTEXT: "TEXT",
 518        exp.DataType.Type.TINYTEXT: "TEXT",
 519        exp.DataType.Type.BLOB: "VARBINARY",
 520        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 521        exp.DataType.Type.LONGBLOB: "BLOB",
 522        exp.DataType.Type.TINYBLOB: "BLOB",
 523        exp.DataType.Type.INET: "INET",
 524        exp.DataType.Type.ROWVERSION: "VARBINARY",
 525        exp.DataType.Type.SMALLDATETIME: "TIMESTAMP",
 526    }
 527
 528    UNSUPPORTED_TYPES: set[exp.DataType.Type] = set()
 529
 530    TIME_PART_SINGULARS = {
 531        "MICROSECONDS": "MICROSECOND",
 532        "SECONDS": "SECOND",
 533        "MINUTES": "MINUTE",
 534        "HOURS": "HOUR",
 535        "DAYS": "DAY",
 536        "WEEKS": "WEEK",
 537        "MONTHS": "MONTH",
 538        "QUARTERS": "QUARTER",
 539        "YEARS": "YEAR",
 540    }
 541
 542    AFTER_HAVING_MODIFIER_TRANSFORMS = {
 543        "cluster": lambda self, e: self.sql(e, "cluster"),
 544        "distribute": lambda self, e: self.sql(e, "distribute"),
 545        "sort": lambda self, e: self.sql(e, "sort"),
 546        "windows": lambda self, e: (
 547            self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
 548            if e.args.get("windows")
 549            else ""
 550        ),
 551        "qualify": lambda self, e: self.sql(e, "qualify"),
 552    }
 553
 554    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 555
 556    STRUCT_DELIMITER = ("<", ">")
 557
 558    PARAMETER_TOKEN = "@"
 559    NAMED_PLACEHOLDER_TOKEN = ":"
 560
 561    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set()
 562
 563    PROPERTIES_LOCATION = {
 564        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 565        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 566        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 567        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 568        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 569        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 570        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 571        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 572        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 573        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 574        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 575        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 576        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 577        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 578        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 579        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 580        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 581        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 582        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 583        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 584        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 585        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 586        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 587        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 588        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 589        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 590        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 591        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 592        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 593        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 594        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 595        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 596        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 597        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 598        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 599        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 600        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 601        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 602        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 603        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 604        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 605        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 606        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 607        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 608        exp.LogProperty: exp.Properties.Location.POST_NAME,
 609        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 610        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 611        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 612        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 613        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 614        exp.Order: exp.Properties.Location.POST_SCHEMA,
 615        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 616        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 617        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 618        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 619        exp.Property: exp.Properties.Location.POST_WITH,
 620        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 621        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 622        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 623        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 624        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 625        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 626        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 627        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 628        exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA,
 629        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 630        exp.Set: exp.Properties.Location.POST_SCHEMA,
 631        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 632        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 633        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 634        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 635        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 636        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 637        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 638        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 639        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 640        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 641        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 642        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 643        exp.Tags: exp.Properties.Location.POST_WITH,
 644        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 645        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 646        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 647        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 648        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 649        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 650        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 651        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 652        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 653        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 654        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 655        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 656        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 657        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 658        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 659    }
 660
 661    # Keywords that can't be used as unquoted identifier names
 662    RESERVED_KEYWORDS: t.Set[str] = set()
 663
 664    # Expressions whose comments are separated from them for better formatting
 665    WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 666        exp.Command,
 667        exp.Create,
 668        exp.Describe,
 669        exp.Delete,
 670        exp.Drop,
 671        exp.From,
 672        exp.Insert,
 673        exp.Join,
 674        exp.MultitableInserts,
 675        exp.Order,
 676        exp.Group,
 677        exp.Having,
 678        exp.Select,
 679        exp.SetOperation,
 680        exp.Update,
 681        exp.Where,
 682        exp.With,
 683    )
 684
 685    # Expressions that should not have their comments generated in maybe_comment
 686    EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 687        exp.Binary,
 688        exp.SetOperation,
 689    )
 690
 691    # Expressions that can remain unwrapped when appearing in the context of an INTERVAL
 692    UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = (
 693        exp.Column,
 694        exp.Literal,
 695        exp.Neg,
 696        exp.Paren,
 697    )
 698
 699    PARAMETERIZABLE_TEXT_TYPES = {
 700        exp.DataType.Type.NVARCHAR,
 701        exp.DataType.Type.VARCHAR,
 702        exp.DataType.Type.CHAR,
 703        exp.DataType.Type.NCHAR,
 704    }
 705
 706    # Expressions that need to have all CTEs under them bubbled up to them
 707    EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set()
 708
 709    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = ()
 710
 711    SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE
 712
 713    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 714
 715    __slots__ = (
 716        "pretty",
 717        "identify",
 718        "normalize",
 719        "pad",
 720        "_indent",
 721        "normalize_functions",
 722        "unsupported_level",
 723        "max_unsupported",
 724        "leading_comma",
 725        "max_text_width",
 726        "comments",
 727        "dialect",
 728        "unsupported_messages",
 729        "_escaped_quote_end",
 730        "_escaped_byte_quote_end",
 731        "_escaped_identifier_end",
 732        "_next_name",
 733        "_identifier_start",
 734        "_identifier_end",
 735        "_quote_json_path_key_using_brackets",
 736    )
 737
 738    def __init__(
 739        self,
 740        pretty: t.Optional[bool] = None,
 741        identify: str | bool = False,
 742        normalize: bool = False,
 743        pad: int = 2,
 744        indent: int = 2,
 745        normalize_functions: t.Optional[str | bool] = None,
 746        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 747        max_unsupported: int = 3,
 748        leading_comma: bool = False,
 749        max_text_width: int = 80,
 750        comments: bool = True,
 751        dialect: DialectType = None,
 752    ):
 753        import sqlglot
 754        from sqlglot.dialects import Dialect
 755
 756        self.pretty = pretty if pretty is not None else sqlglot.pretty
 757        self.identify = identify
 758        self.normalize = normalize
 759        self.pad = pad
 760        self._indent = indent
 761        self.unsupported_level = unsupported_level
 762        self.max_unsupported = max_unsupported
 763        self.leading_comma = leading_comma
 764        self.max_text_width = max_text_width
 765        self.comments = comments
 766        self.dialect = Dialect.get_or_raise(dialect)
 767
 768        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 769        self.normalize_functions = (
 770            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 771        )
 772
 773        self.unsupported_messages: t.List[str] = []
 774        self._escaped_quote_end: str = (
 775            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 776        )
 777        self._escaped_byte_quote_end: str = (
 778            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 779            if self.dialect.BYTE_END
 780            else ""
 781        )
 782        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 783
 784        self._next_name = name_sequence("_t")
 785
 786        self._identifier_start = self.dialect.IDENTIFIER_START
 787        self._identifier_end = self.dialect.IDENTIFIER_END
 788
 789        self._quote_json_path_key_using_brackets = True
 790
 791    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
 792        """
 793        Generates the SQL string corresponding to the given syntax tree.
 794
 795        Args:
 796            expression: The syntax tree.
 797            copy: Whether to copy the expression. The generator performs mutations so
 798                it is safer to copy.
 799
 800        Returns:
 801            The SQL string corresponding to `expression`.
 802        """
 803        if copy:
 804            expression = expression.copy()
 805
 806        expression = self.preprocess(expression)
 807
 808        self.unsupported_messages = []
 809        sql = self.sql(expression).strip()
 810
 811        if self.pretty:
 812            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 813
 814        if self.unsupported_level == ErrorLevel.IGNORE:
 815            return sql
 816
 817        if self.unsupported_level == ErrorLevel.WARN:
 818            for msg in self.unsupported_messages:
 819                logger.warning(msg)
 820        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 821            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 822
 823        return sql
 824
 825    def preprocess(self, expression: exp.Expression) -> exp.Expression:
 826        """Apply generic preprocessing transformations to a given expression."""
 827        expression = self._move_ctes_to_top_level(expression)
 828
 829        if self.ENSURE_BOOLS:
 830            from sqlglot.transforms import ensure_bools
 831
 832            expression = ensure_bools(expression)
 833
 834        return expression
 835
 836    def _move_ctes_to_top_level(self, expression: E) -> E:
 837        if (
 838            not expression.parent
 839            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 840            and any(node.parent is not expression for node in expression.find_all(exp.With))
 841        ):
 842            from sqlglot.transforms import move_ctes_to_top_level
 843
 844            expression = move_ctes_to_top_level(expression)
 845        return expression
 846
 847    def unsupported(self, message: str) -> None:
 848        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 849            raise UnsupportedError(message)
 850        self.unsupported_messages.append(message)
 851
 852    def sep(self, sep: str = " ") -> str:
 853        return f"{sep.strip()}\n" if self.pretty else sep
 854
 855    def seg(self, sql: str, sep: str = " ") -> str:
 856        return f"{self.sep(sep)}{sql}"
 857
 858    def sanitize_comment(self, comment: str) -> str:
 859        comment = " " + comment if comment[0].strip() else comment
 860        comment = comment + " " if comment[-1].strip() else comment
 861
 862        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
 863            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
 864            comment = comment.replace("*/", "* /")
 865
 866        return comment
 867
 868    def maybe_comment(
 869        self,
 870        sql: str,
 871        expression: t.Optional[exp.Expression] = None,
 872        comments: t.Optional[t.List[str]] = None,
 873        separated: bool = False,
 874    ) -> str:
 875        comments = (
 876            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 877            if self.comments
 878            else None
 879        )
 880
 881        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 882            return sql
 883
 884        comments_sql = " ".join(
 885            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
 886        )
 887
 888        if not comments_sql:
 889            return sql
 890
 891        comments_sql = self._replace_line_breaks(comments_sql)
 892
 893        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 894            return (
 895                f"{self.sep()}{comments_sql}{sql}"
 896                if not sql or sql[0].isspace()
 897                else f"{comments_sql}{self.sep()}{sql}"
 898            )
 899
 900        return f"{sql} {comments_sql}"
 901
 902    def wrap(self, expression: exp.Expression | str) -> str:
 903        this_sql = (
 904            self.sql(expression)
 905            if isinstance(expression, exp.UNWRAPPED_QUERIES)
 906            else self.sql(expression, "this")
 907        )
 908        if not this_sql:
 909            return "()"
 910
 911        this_sql = self.indent(this_sql, level=1, pad=0)
 912        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 913
 914    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 915        original = self.identify
 916        self.identify = False
 917        result = func(*args, **kwargs)
 918        self.identify = original
 919        return result
 920
 921    def normalize_func(self, name: str) -> str:
 922        if self.normalize_functions == "upper" or self.normalize_functions is True:
 923            return name.upper()
 924        if self.normalize_functions == "lower":
 925            return name.lower()
 926        return name
 927
 928    def indent(
 929        self,
 930        sql: str,
 931        level: int = 0,
 932        pad: t.Optional[int] = None,
 933        skip_first: bool = False,
 934        skip_last: bool = False,
 935    ) -> str:
 936        if not self.pretty or not sql:
 937            return sql
 938
 939        pad = self.pad if pad is None else pad
 940        lines = sql.split("\n")
 941
 942        return "\n".join(
 943            (
 944                line
 945                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 946                else f"{' ' * (level * self._indent + pad)}{line}"
 947            )
 948            for i, line in enumerate(lines)
 949        )
 950
 951    def sql(
 952        self,
 953        expression: t.Optional[str | exp.Expression],
 954        key: t.Optional[str] = None,
 955        comment: bool = True,
 956    ) -> str:
 957        if not expression:
 958            return ""
 959
 960        if isinstance(expression, str):
 961            return expression
 962
 963        if key:
 964            value = expression.args.get(key)
 965            if value:
 966                return self.sql(value)
 967            return ""
 968
 969        transform = self.TRANSFORMS.get(expression.__class__)
 970
 971        if callable(transform):
 972            sql = transform(self, expression)
 973        elif isinstance(expression, exp.Expression):
 974            exp_handler_name = f"{expression.key}_sql"
 975
 976            if hasattr(self, exp_handler_name):
 977                sql = getattr(self, exp_handler_name)(expression)
 978            elif isinstance(expression, exp.Func):
 979                sql = self.function_fallback_sql(expression)
 980            elif isinstance(expression, exp.Property):
 981                sql = self.property_sql(expression)
 982            else:
 983                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
 984        else:
 985            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
 986
 987        return self.maybe_comment(sql, expression) if self.comments and comment else sql
 988
 989    def uncache_sql(self, expression: exp.Uncache) -> str:
 990        table = self.sql(expression, "this")
 991        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
 992        return f"UNCACHE TABLE{exists_sql} {table}"
 993
 994    def cache_sql(self, expression: exp.Cache) -> str:
 995        lazy = " LAZY" if expression.args.get("lazy") else ""
 996        table = self.sql(expression, "this")
 997        options = expression.args.get("options")
 998        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
 999        sql = self.sql(expression, "expression")
1000        sql = f" AS{self.sep()}{sql}" if sql else ""
1001        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1002        return self.prepend_ctes(expression, sql)
1003
1004    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1005        if isinstance(expression.parent, exp.Cast):
1006            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
1007        default = "DEFAULT " if expression.args.get("default") else ""
1008        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1009
1010    def column_parts(self, expression: exp.Column) -> str:
1011        return ".".join(
1012            self.sql(part)
1013            for part in (
1014                expression.args.get("catalog"),
1015                expression.args.get("db"),
1016                expression.args.get("table"),
1017                expression.args.get("this"),
1018            )
1019            if part
1020        )
1021
1022    def column_sql(self, expression: exp.Column) -> str:
1023        join_mark = " (+)" if expression.args.get("join_mark") else ""
1024
1025        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1026            join_mark = ""
1027            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1028
1029        return f"{self.column_parts(expression)}{join_mark}"
1030
1031    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1032        this = self.sql(expression, "this")
1033        this = f" {this}" if this else ""
1034        position = self.sql(expression, "position")
1035        return f"{position}{this}"
1036
1037    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1038        column = self.sql(expression, "this")
1039        kind = self.sql(expression, "kind")
1040        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1041        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1042        kind = f"{sep}{kind}" if kind else ""
1043        constraints = f" {constraints}" if constraints else ""
1044        position = self.sql(expression, "position")
1045        position = f" {position}" if position else ""
1046
1047        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1048            kind = ""
1049
1050        return f"{exists}{column}{kind}{constraints}{position}"
1051
1052    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1053        this = self.sql(expression, "this")
1054        kind_sql = self.sql(expression, "kind").strip()
1055        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1056
1057    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1058        this = self.sql(expression, "this")
1059        if expression.args.get("not_null"):
1060            persisted = " PERSISTED NOT NULL"
1061        elif expression.args.get("persisted"):
1062            persisted = " PERSISTED"
1063        else:
1064            persisted = ""
1065
1066        return f"AS {this}{persisted}"
1067
1068    def autoincrementcolumnconstraint_sql(self, _) -> str:
1069        return self.token_sql(TokenType.AUTO_INCREMENT)
1070
1071    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1072        if isinstance(expression.this, list):
1073            this = self.wrap(self.expressions(expression, key="this", flat=True))
1074        else:
1075            this = self.sql(expression, "this")
1076
1077        return f"COMPRESS {this}"
1078
1079    def generatedasidentitycolumnconstraint_sql(
1080        self, expression: exp.GeneratedAsIdentityColumnConstraint
1081    ) -> str:
1082        this = ""
1083        if expression.this is not None:
1084            on_null = " ON NULL" if expression.args.get("on_null") else ""
1085            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1086
1087        start = expression.args.get("start")
1088        start = f"START WITH {start}" if start else ""
1089        increment = expression.args.get("increment")
1090        increment = f" INCREMENT BY {increment}" if increment else ""
1091        minvalue = expression.args.get("minvalue")
1092        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1093        maxvalue = expression.args.get("maxvalue")
1094        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1095        cycle = expression.args.get("cycle")
1096        cycle_sql = ""
1097
1098        if cycle is not None:
1099            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1100            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1101
1102        sequence_opts = ""
1103        if start or increment or cycle_sql:
1104            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1105            sequence_opts = f" ({sequence_opts.strip()})"
1106
1107        expr = self.sql(expression, "expression")
1108        expr = f"({expr})" if expr else "IDENTITY"
1109
1110        return f"GENERATED{this} AS {expr}{sequence_opts}"
1111
1112    def generatedasrowcolumnconstraint_sql(
1113        self, expression: exp.GeneratedAsRowColumnConstraint
1114    ) -> str:
1115        start = "START" if expression.args.get("start") else "END"
1116        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1117        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1118
1119    def periodforsystemtimeconstraint_sql(
1120        self, expression: exp.PeriodForSystemTimeConstraint
1121    ) -> str:
1122        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1123
1124    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1125        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1126
1127    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1128        desc = expression.args.get("desc")
1129        if desc is not None:
1130            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1131        options = self.expressions(expression, key="options", flat=True, sep=" ")
1132        options = f" {options}" if options else ""
1133        return f"PRIMARY KEY{options}"
1134
1135    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1136        this = self.sql(expression, "this")
1137        this = f" {this}" if this else ""
1138        index_type = expression.args.get("index_type")
1139        index_type = f" USING {index_type}" if index_type else ""
1140        on_conflict = self.sql(expression, "on_conflict")
1141        on_conflict = f" {on_conflict}" if on_conflict else ""
1142        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1143        options = self.expressions(expression, key="options", flat=True, sep=" ")
1144        options = f" {options}" if options else ""
1145        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1146
1147    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1148        return self.sql(expression, "this")
1149
1150    def create_sql(self, expression: exp.Create) -> str:
1151        kind = self.sql(expression, "kind")
1152        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1153        properties = expression.args.get("properties")
1154        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1155
1156        this = self.createable_sql(expression, properties_locs)
1157
1158        properties_sql = ""
1159        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1160            exp.Properties.Location.POST_WITH
1161        ):
1162            props_ast = exp.Properties(
1163                expressions=[
1164                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1165                    *properties_locs[exp.Properties.Location.POST_WITH],
1166                ]
1167            )
1168            props_ast.parent = expression
1169            properties_sql = self.sql(props_ast)
1170
1171            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1172                properties_sql = self.sep() + properties_sql
1173            elif not self.pretty:
1174                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1175                properties_sql = f" {properties_sql}"
1176
1177        begin = " BEGIN" if expression.args.get("begin") else ""
1178        end = " END" if expression.args.get("end") else ""
1179
1180        expression_sql = self.sql(expression, "expression")
1181        if expression_sql:
1182            expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
1183
1184            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1185                postalias_props_sql = ""
1186                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1187                    postalias_props_sql = self.properties(
1188                        exp.Properties(
1189                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1190                        ),
1191                        wrapped=False,
1192                    )
1193                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1194                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1195
1196        postindex_props_sql = ""
1197        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1198            postindex_props_sql = self.properties(
1199                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1200                wrapped=False,
1201                prefix=" ",
1202            )
1203
1204        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1205        indexes = f" {indexes}" if indexes else ""
1206        index_sql = indexes + postindex_props_sql
1207
1208        replace = " OR REPLACE" if expression.args.get("replace") else ""
1209        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1210        unique = " UNIQUE" if expression.args.get("unique") else ""
1211
1212        clustered = expression.args.get("clustered")
1213        if clustered is None:
1214            clustered_sql = ""
1215        elif clustered:
1216            clustered_sql = " CLUSTERED COLUMNSTORE"
1217        else:
1218            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1219
1220        postcreate_props_sql = ""
1221        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1222            postcreate_props_sql = self.properties(
1223                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1224                sep=" ",
1225                prefix=" ",
1226                wrapped=False,
1227            )
1228
1229        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1230
1231        postexpression_props_sql = ""
1232        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1233            postexpression_props_sql = self.properties(
1234                exp.Properties(
1235                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1236                ),
1237                sep=" ",
1238                prefix=" ",
1239                wrapped=False,
1240            )
1241
1242        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1243        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1244        no_schema_binding = (
1245            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1246        )
1247
1248        clone = self.sql(expression, "clone")
1249        clone = f" {clone}" if clone else ""
1250
1251        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1252            properties_expression = f"{expression_sql}{properties_sql}"
1253        else:
1254            properties_expression = f"{properties_sql}{expression_sql}"
1255
1256        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1257        return self.prepend_ctes(expression, expression_sql)
1258
1259    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1260        start = self.sql(expression, "start")
1261        start = f"START WITH {start}" if start else ""
1262        increment = self.sql(expression, "increment")
1263        increment = f" INCREMENT BY {increment}" if increment else ""
1264        minvalue = self.sql(expression, "minvalue")
1265        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1266        maxvalue = self.sql(expression, "maxvalue")
1267        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1268        owned = self.sql(expression, "owned")
1269        owned = f" OWNED BY {owned}" if owned else ""
1270
1271        cache = expression.args.get("cache")
1272        if cache is None:
1273            cache_str = ""
1274        elif cache is True:
1275            cache_str = " CACHE"
1276        else:
1277            cache_str = f" CACHE {cache}"
1278
1279        options = self.expressions(expression, key="options", flat=True, sep=" ")
1280        options = f" {options}" if options else ""
1281
1282        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1283
1284    def clone_sql(self, expression: exp.Clone) -> str:
1285        this = self.sql(expression, "this")
1286        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1287        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1288        return f"{shallow}{keyword} {this}"
1289
1290    def describe_sql(self, expression: exp.Describe) -> str:
1291        style = expression.args.get("style")
1292        style = f" {style}" if style else ""
1293        partition = self.sql(expression, "partition")
1294        partition = f" {partition}" if partition else ""
1295        format = self.sql(expression, "format")
1296        format = f" {format}" if format else ""
1297
1298        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1299
1300    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1301        tag = self.sql(expression, "tag")
1302        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1303
1304    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1305        with_ = self.sql(expression, "with")
1306        if with_:
1307            sql = f"{with_}{self.sep()}{sql}"
1308        return sql
1309
1310    def with_sql(self, expression: exp.With) -> str:
1311        sql = self.expressions(expression, flat=True)
1312        recursive = (
1313            "RECURSIVE "
1314            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1315            else ""
1316        )
1317        search = self.sql(expression, "search")
1318        search = f" {search}" if search else ""
1319
1320        return f"WITH {recursive}{sql}{search}"
1321
1322    def cte_sql(self, expression: exp.CTE) -> str:
1323        alias = expression.args.get("alias")
1324        if alias:
1325            alias.add_comments(expression.pop_comments())
1326
1327        alias_sql = self.sql(expression, "alias")
1328
1329        materialized = expression.args.get("materialized")
1330        if materialized is False:
1331            materialized = "NOT MATERIALIZED "
1332        elif materialized:
1333            materialized = "MATERIALIZED "
1334
1335        return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1336
1337    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1338        alias = self.sql(expression, "this")
1339        columns = self.expressions(expression, key="columns", flat=True)
1340        columns = f"({columns})" if columns else ""
1341
1342        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1343            columns = ""
1344            self.unsupported("Named columns are not supported in table alias.")
1345
1346        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1347            alias = self._next_name()
1348
1349        return f"{alias}{columns}"
1350
1351    def bitstring_sql(self, expression: exp.BitString) -> str:
1352        this = self.sql(expression, "this")
1353        if self.dialect.BIT_START:
1354            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1355        return f"{int(this, 2)}"
1356
1357    def hexstring_sql(
1358        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1359    ) -> str:
1360        this = self.sql(expression, "this")
1361        is_integer_type = expression.args.get("is_integer")
1362
1363        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1364            not self.dialect.HEX_START and not binary_function_repr
1365        ):
1366            # Integer representation will be returned if:
1367            # - The read dialect treats the hex value as integer literal but not the write
1368            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1369            return f"{int(this, 16)}"
1370
1371        if not is_integer_type:
1372            # Read dialect treats the hex value as BINARY/BLOB
1373            if binary_function_repr:
1374                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1375                return self.func(binary_function_repr, exp.Literal.string(this))
1376            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1377                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1378                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1379
1380        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1381
1382    def bytestring_sql(self, expression: exp.ByteString) -> str:
1383        this = self.sql(expression, "this")
1384        if self.dialect.BYTE_START:
1385            escaped_byte_string = self.escape_str(
1386                this,
1387                escape_backslash=False,
1388                delimiter=self.dialect.BYTE_END,
1389                escaped_delimiter=self._escaped_byte_quote_end,
1390            )
1391            return f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1392        return this
1393
1394    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1395        this = self.sql(expression, "this")
1396        escape = expression.args.get("escape")
1397
1398        if self.dialect.UNICODE_START:
1399            escape_substitute = r"\\\1"
1400            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1401        else:
1402            escape_substitute = r"\\u\1"
1403            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1404
1405        if escape:
1406            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1407            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1408        else:
1409            escape_pattern = ESCAPED_UNICODE_RE
1410            escape_sql = ""
1411
1412        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1413            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1414
1415        return f"{left_quote}{this}{right_quote}{escape_sql}"
1416
1417    def rawstring_sql(self, expression: exp.RawString) -> str:
1418        string = expression.this
1419        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1420            string = string.replace("\\", "\\\\")
1421
1422        string = self.escape_str(string, escape_backslash=False)
1423        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1424
1425    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1426        this = self.sql(expression, "this")
1427        specifier = self.sql(expression, "expression")
1428        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1429        return f"{this}{specifier}"
1430
1431    def datatype_sql(self, expression: exp.DataType) -> str:
1432        nested = ""
1433        values = ""
1434        interior = self.expressions(expression, flat=True)
1435
1436        type_value = expression.this
1437        if type_value in self.UNSUPPORTED_TYPES:
1438            self.unsupported(
1439                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1440            )
1441
1442        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1443            type_sql = self.sql(expression, "kind")
1444        else:
1445            type_sql = (
1446                self.TYPE_MAPPING.get(type_value, type_value.value)
1447                if isinstance(type_value, exp.DataType.Type)
1448                else type_value
1449            )
1450
1451        if interior:
1452            if expression.args.get("nested"):
1453                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1454                if expression.args.get("values") is not None:
1455                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1456                    values = self.expressions(expression, key="values", flat=True)
1457                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1458            elif type_value == exp.DataType.Type.INTERVAL:
1459                nested = f" {interior}"
1460            else:
1461                nested = f"({interior})"
1462
1463        type_sql = f"{type_sql}{nested}{values}"
1464        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1465            exp.DataType.Type.TIMETZ,
1466            exp.DataType.Type.TIMESTAMPTZ,
1467        ):
1468            type_sql = f"{type_sql} WITH TIME ZONE"
1469
1470        return type_sql
1471
1472    def directory_sql(self, expression: exp.Directory) -> str:
1473        local = "LOCAL " if expression.args.get("local") else ""
1474        row_format = self.sql(expression, "row_format")
1475        row_format = f" {row_format}" if row_format else ""
1476        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1477
1478    def delete_sql(self, expression: exp.Delete) -> str:
1479        this = self.sql(expression, "this")
1480        this = f" FROM {this}" if this else ""
1481        using = self.sql(expression, "using")
1482        using = f" USING {using}" if using else ""
1483        cluster = self.sql(expression, "cluster")
1484        cluster = f" {cluster}" if cluster else ""
1485        where = self.sql(expression, "where")
1486        returning = self.sql(expression, "returning")
1487        limit = self.sql(expression, "limit")
1488        tables = self.expressions(expression, key="tables")
1489        tables = f" {tables}" if tables else ""
1490        if self.RETURNING_END:
1491            expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}"
1492        else:
1493            expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}"
1494        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1495
1496    def drop_sql(self, expression: exp.Drop) -> str:
1497        this = self.sql(expression, "this")
1498        expressions = self.expressions(expression, flat=True)
1499        expressions = f" ({expressions})" if expressions else ""
1500        kind = expression.args["kind"]
1501        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1502        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1503        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1504        on_cluster = self.sql(expression, "cluster")
1505        on_cluster = f" {on_cluster}" if on_cluster else ""
1506        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1507        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1508        cascade = " CASCADE" if expression.args.get("cascade") else ""
1509        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1510        purge = " PURGE" if expression.args.get("purge") else ""
1511        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1512
1513    def set_operation(self, expression: exp.SetOperation) -> str:
1514        op_type = type(expression)
1515        op_name = op_type.key.upper()
1516
1517        distinct = expression.args.get("distinct")
1518        if (
1519            distinct is False
1520            and op_type in (exp.Except, exp.Intersect)
1521            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1522        ):
1523            self.unsupported(f"{op_name} ALL is not supported")
1524
1525        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1526
1527        if distinct is None:
1528            distinct = default_distinct
1529            if distinct is None:
1530                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1531
1532        if distinct is default_distinct:
1533            distinct_or_all = ""
1534        else:
1535            distinct_or_all = " DISTINCT" if distinct else " ALL"
1536
1537        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1538        side_kind = f"{side_kind} " if side_kind else ""
1539
1540        by_name = " BY NAME" if expression.args.get("by_name") else ""
1541        on = self.expressions(expression, key="on", flat=True)
1542        on = f" ON ({on})" if on else ""
1543
1544        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1545
1546    def set_operations(self, expression: exp.SetOperation) -> str:
1547        if not self.SET_OP_MODIFIERS:
1548            limit = expression.args.get("limit")
1549            order = expression.args.get("order")
1550
1551            if limit or order:
1552                select = self._move_ctes_to_top_level(
1553                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1554                )
1555
1556                if limit:
1557                    select = select.limit(limit.pop(), copy=False)
1558                if order:
1559                    select = select.order_by(order.pop(), copy=False)
1560                return self.sql(select)
1561
1562        sqls: t.List[str] = []
1563        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1564
1565        while stack:
1566            node = stack.pop()
1567
1568            if isinstance(node, exp.SetOperation):
1569                stack.append(node.expression)
1570                stack.append(
1571                    self.maybe_comment(
1572                        self.set_operation(node), comments=node.comments, separated=True
1573                    )
1574                )
1575                stack.append(node.this)
1576            else:
1577                sqls.append(self.sql(node))
1578
1579        this = self.sep().join(sqls)
1580        this = self.query_modifiers(expression, this)
1581        return self.prepend_ctes(expression, this)
1582
1583    def fetch_sql(self, expression: exp.Fetch) -> str:
1584        direction = expression.args.get("direction")
1585        direction = f" {direction}" if direction else ""
1586        count = self.sql(expression, "count")
1587        count = f" {count}" if count else ""
1588        limit_options = self.sql(expression, "limit_options")
1589        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1590        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1591
1592    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1593        percent = " PERCENT" if expression.args.get("percent") else ""
1594        rows = " ROWS" if expression.args.get("rows") else ""
1595        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1596        if not with_ties and rows:
1597            with_ties = " ONLY"
1598        return f"{percent}{rows}{with_ties}"
1599
1600    def filter_sql(self, expression: exp.Filter) -> str:
1601        if self.AGGREGATE_FILTER_SUPPORTED:
1602            this = self.sql(expression, "this")
1603            where = self.sql(expression, "expression").strip()
1604            return f"{this} FILTER({where})"
1605
1606        agg = expression.this
1607        agg_arg = agg.this
1608        cond = expression.expression.this
1609        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1610        return self.sql(agg)
1611
1612    def hint_sql(self, expression: exp.Hint) -> str:
1613        if not self.QUERY_HINTS:
1614            self.unsupported("Hints are not supported")
1615            return ""
1616
1617        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1618
1619    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1620        using = self.sql(expression, "using")
1621        using = f" USING {using}" if using else ""
1622        columns = self.expressions(expression, key="columns", flat=True)
1623        columns = f"({columns})" if columns else ""
1624        partition_by = self.expressions(expression, key="partition_by", flat=True)
1625        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1626        where = self.sql(expression, "where")
1627        include = self.expressions(expression, key="include", flat=True)
1628        if include:
1629            include = f" INCLUDE ({include})"
1630        with_storage = self.expressions(expression, key="with_storage", flat=True)
1631        with_storage = f" WITH ({with_storage})" if with_storage else ""
1632        tablespace = self.sql(expression, "tablespace")
1633        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1634        on = self.sql(expression, "on")
1635        on = f" ON {on}" if on else ""
1636
1637        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1638
1639    def index_sql(self, expression: exp.Index) -> str:
1640        unique = "UNIQUE " if expression.args.get("unique") else ""
1641        primary = "PRIMARY " if expression.args.get("primary") else ""
1642        amp = "AMP " if expression.args.get("amp") else ""
1643        name = self.sql(expression, "this")
1644        name = f"{name} " if name else ""
1645        table = self.sql(expression, "table")
1646        table = f"{self.INDEX_ON} {table}" if table else ""
1647
1648        index = "INDEX " if not table else ""
1649
1650        params = self.sql(expression, "params")
1651        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1652
1653    def identifier_sql(self, expression: exp.Identifier) -> str:
1654        text = expression.name
1655        lower = text.lower()
1656        text = lower if self.normalize and not expression.quoted else text
1657        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1658        if (
1659            expression.quoted
1660            or self.dialect.can_identify(text, self.identify)
1661            or lower in self.RESERVED_KEYWORDS
1662            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1663        ):
1664            text = f"{self._identifier_start}{text}{self._identifier_end}"
1665        return text
1666
1667    def hex_sql(self, expression: exp.Hex) -> str:
1668        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1669        if self.dialect.HEX_LOWERCASE:
1670            text = self.func("LOWER", text)
1671
1672        return text
1673
1674    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1675        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1676        if not self.dialect.HEX_LOWERCASE:
1677            text = self.func("LOWER", text)
1678        return text
1679
1680    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1681        input_format = self.sql(expression, "input_format")
1682        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1683        output_format = self.sql(expression, "output_format")
1684        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1685        return self.sep().join((input_format, output_format))
1686
1687    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1688        string = self.sql(exp.Literal.string(expression.name))
1689        return f"{prefix}{string}"
1690
1691    def partition_sql(self, expression: exp.Partition) -> str:
1692        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1693        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1694
1695    def properties_sql(self, expression: exp.Properties) -> str:
1696        root_properties = []
1697        with_properties = []
1698
1699        for p in expression.expressions:
1700            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1701            if p_loc == exp.Properties.Location.POST_WITH:
1702                with_properties.append(p)
1703            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1704                root_properties.append(p)
1705
1706        root_props_ast = exp.Properties(expressions=root_properties)
1707        root_props_ast.parent = expression.parent
1708
1709        with_props_ast = exp.Properties(expressions=with_properties)
1710        with_props_ast.parent = expression.parent
1711
1712        root_props = self.root_properties(root_props_ast)
1713        with_props = self.with_properties(with_props_ast)
1714
1715        if root_props and with_props and not self.pretty:
1716            with_props = " " + with_props
1717
1718        return root_props + with_props
1719
1720    def root_properties(self, properties: exp.Properties) -> str:
1721        if properties.expressions:
1722            return self.expressions(properties, indent=False, sep=" ")
1723        return ""
1724
1725    def properties(
1726        self,
1727        properties: exp.Properties,
1728        prefix: str = "",
1729        sep: str = ", ",
1730        suffix: str = "",
1731        wrapped: bool = True,
1732    ) -> str:
1733        if properties.expressions:
1734            expressions = self.expressions(properties, sep=sep, indent=False)
1735            if expressions:
1736                expressions = self.wrap(expressions) if wrapped else expressions
1737                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1738        return ""
1739
1740    def with_properties(self, properties: exp.Properties) -> str:
1741        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1742
1743    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1744        properties_locs = defaultdict(list)
1745        for p in properties.expressions:
1746            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1747            if p_loc != exp.Properties.Location.UNSUPPORTED:
1748                properties_locs[p_loc].append(p)
1749            else:
1750                self.unsupported(f"Unsupported property {p.key}")
1751
1752        return properties_locs
1753
1754    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1755        if isinstance(expression.this, exp.Dot):
1756            return self.sql(expression, "this")
1757        return f"'{expression.name}'" if string_key else expression.name
1758
1759    def property_sql(self, expression: exp.Property) -> str:
1760        property_cls = expression.__class__
1761        if property_cls == exp.Property:
1762            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1763
1764        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1765        if not property_name:
1766            self.unsupported(f"Unsupported property {expression.key}")
1767
1768        return f"{property_name}={self.sql(expression, 'this')}"
1769
1770    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1771        if self.SUPPORTS_CREATE_TABLE_LIKE:
1772            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1773            options = f" {options}" if options else ""
1774
1775            like = f"LIKE {self.sql(expression, 'this')}{options}"
1776            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1777                like = f"({like})"
1778
1779            return like
1780
1781        if expression.expressions:
1782            self.unsupported("Transpilation of LIKE property options is unsupported")
1783
1784        select = exp.select("*").from_(expression.this).limit(0)
1785        return f"AS {self.sql(select)}"
1786
1787    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1788        no = "NO " if expression.args.get("no") else ""
1789        protection = " PROTECTION" if expression.args.get("protection") else ""
1790        return f"{no}FALLBACK{protection}"
1791
1792    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1793        no = "NO " if expression.args.get("no") else ""
1794        local = expression.args.get("local")
1795        local = f"{local} " if local else ""
1796        dual = "DUAL " if expression.args.get("dual") else ""
1797        before = "BEFORE " if expression.args.get("before") else ""
1798        after = "AFTER " if expression.args.get("after") else ""
1799        return f"{no}{local}{dual}{before}{after}JOURNAL"
1800
1801    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1802        freespace = self.sql(expression, "this")
1803        percent = " PERCENT" if expression.args.get("percent") else ""
1804        return f"FREESPACE={freespace}{percent}"
1805
1806    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1807        if expression.args.get("default"):
1808            property = "DEFAULT"
1809        elif expression.args.get("on"):
1810            property = "ON"
1811        else:
1812            property = "OFF"
1813        return f"CHECKSUM={property}"
1814
1815    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1816        if expression.args.get("no"):
1817            return "NO MERGEBLOCKRATIO"
1818        if expression.args.get("default"):
1819            return "DEFAULT MERGEBLOCKRATIO"
1820
1821        percent = " PERCENT" if expression.args.get("percent") else ""
1822        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1823
1824    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1825        default = expression.args.get("default")
1826        minimum = expression.args.get("minimum")
1827        maximum = expression.args.get("maximum")
1828        if default or minimum or maximum:
1829            if default:
1830                prop = "DEFAULT"
1831            elif minimum:
1832                prop = "MINIMUM"
1833            else:
1834                prop = "MAXIMUM"
1835            return f"{prop} DATABLOCKSIZE"
1836        units = expression.args.get("units")
1837        units = f" {units}" if units else ""
1838        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
1839
1840    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1841        autotemp = expression.args.get("autotemp")
1842        always = expression.args.get("always")
1843        default = expression.args.get("default")
1844        manual = expression.args.get("manual")
1845        never = expression.args.get("never")
1846
1847        if autotemp is not None:
1848            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1849        elif always:
1850            prop = "ALWAYS"
1851        elif default:
1852            prop = "DEFAULT"
1853        elif manual:
1854            prop = "MANUAL"
1855        elif never:
1856            prop = "NEVER"
1857        return f"BLOCKCOMPRESSION={prop}"
1858
1859    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1860        no = expression.args.get("no")
1861        no = " NO" if no else ""
1862        concurrent = expression.args.get("concurrent")
1863        concurrent = " CONCURRENT" if concurrent else ""
1864        target = self.sql(expression, "target")
1865        target = f" {target}" if target else ""
1866        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1867
1868    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1869        if isinstance(expression.this, list):
1870            return f"IN ({self.expressions(expression, key='this', flat=True)})"
1871        if expression.this:
1872            modulus = self.sql(expression, "this")
1873            remainder = self.sql(expression, "expression")
1874            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1875
1876        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1877        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1878        return f"FROM ({from_expressions}) TO ({to_expressions})"
1879
1880    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1881        this = self.sql(expression, "this")
1882
1883        for_values_or_default = expression.expression
1884        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1885            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1886        else:
1887            for_values_or_default = " DEFAULT"
1888
1889        return f"PARTITION OF {this}{for_values_or_default}"
1890
1891    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1892        kind = expression.args.get("kind")
1893        this = f" {self.sql(expression, 'this')}" if expression.this else ""
1894        for_or_in = expression.args.get("for_or_in")
1895        for_or_in = f" {for_or_in}" if for_or_in else ""
1896        lock_type = expression.args.get("lock_type")
1897        override = " OVERRIDE" if expression.args.get("override") else ""
1898        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1899
1900    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1901        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1902        statistics = expression.args.get("statistics")
1903        statistics_sql = ""
1904        if statistics is not None:
1905            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1906        return f"{data_sql}{statistics_sql}"
1907
1908    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1909        this = self.sql(expression, "this")
1910        this = f"HISTORY_TABLE={this}" if this else ""
1911        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
1912        data_consistency = (
1913            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
1914        )
1915        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
1916        retention_period = (
1917            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
1918        )
1919
1920        if this:
1921            on_sql = self.func("ON", this, data_consistency, retention_period)
1922        else:
1923            on_sql = "ON" if expression.args.get("on") else "OFF"
1924
1925        sql = f"SYSTEM_VERSIONING={on_sql}"
1926
1927        return f"WITH({sql})" if expression.args.get("with") else sql
1928
1929    def insert_sql(self, expression: exp.Insert) -> str:
1930        hint = self.sql(expression, "hint")
1931        overwrite = expression.args.get("overwrite")
1932
1933        if isinstance(expression.this, exp.Directory):
1934            this = " OVERWRITE" if overwrite else " INTO"
1935        else:
1936            this = self.INSERT_OVERWRITE if overwrite else " INTO"
1937
1938        stored = self.sql(expression, "stored")
1939        stored = f" {stored}" if stored else ""
1940        alternative = expression.args.get("alternative")
1941        alternative = f" OR {alternative}" if alternative else ""
1942        ignore = " IGNORE" if expression.args.get("ignore") else ""
1943        is_function = expression.args.get("is_function")
1944        if is_function:
1945            this = f"{this} FUNCTION"
1946        this = f"{this} {self.sql(expression, 'this')}"
1947
1948        exists = " IF EXISTS" if expression.args.get("exists") else ""
1949        where = self.sql(expression, "where")
1950        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
1951        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
1952        on_conflict = self.sql(expression, "conflict")
1953        on_conflict = f" {on_conflict}" if on_conflict else ""
1954        by_name = " BY NAME" if expression.args.get("by_name") else ""
1955        returning = self.sql(expression, "returning")
1956
1957        if self.RETURNING_END:
1958            expression_sql = f"{expression_sql}{on_conflict}{returning}"
1959        else:
1960            expression_sql = f"{returning}{expression_sql}{on_conflict}"
1961
1962        partition_by = self.sql(expression, "partition")
1963        partition_by = f" {partition_by}" if partition_by else ""
1964        settings = self.sql(expression, "settings")
1965        settings = f" {settings}" if settings else ""
1966
1967        source = self.sql(expression, "source")
1968        source = f"TABLE {source}" if source else ""
1969
1970        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
1971        return self.prepend_ctes(expression, sql)
1972
1973    def introducer_sql(self, expression: exp.Introducer) -> str:
1974        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
1975
1976    def kill_sql(self, expression: exp.Kill) -> str:
1977        kind = self.sql(expression, "kind")
1978        kind = f" {kind}" if kind else ""
1979        this = self.sql(expression, "this")
1980        this = f" {this}" if this else ""
1981        return f"KILL{kind}{this}"
1982
1983    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
1984        return expression.name
1985
1986    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
1987        return expression.name
1988
1989    def onconflict_sql(self, expression: exp.OnConflict) -> str:
1990        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
1991
1992        constraint = self.sql(expression, "constraint")
1993        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
1994
1995        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
1996        conflict_keys = f"({conflict_keys}) " if conflict_keys else " "
1997        action = self.sql(expression, "action")
1998
1999        expressions = self.expressions(expression, flat=True)
2000        if expressions:
2001            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2002            expressions = f" {set_keyword}{expressions}"
2003
2004        where = self.sql(expression, "where")
2005        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2006
2007    def returning_sql(self, expression: exp.Returning) -> str:
2008        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2009
2010    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2011        fields = self.sql(expression, "fields")
2012        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2013        escaped = self.sql(expression, "escaped")
2014        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2015        items = self.sql(expression, "collection_items")
2016        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2017        keys = self.sql(expression, "map_keys")
2018        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2019        lines = self.sql(expression, "lines")
2020        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2021        null = self.sql(expression, "null")
2022        null = f" NULL DEFINED AS {null}" if null else ""
2023        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2024
2025    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2026        return f"WITH ({self.expressions(expression, flat=True)})"
2027
2028    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2029        this = f"{self.sql(expression, 'this')} INDEX"
2030        target = self.sql(expression, "target")
2031        target = f" FOR {target}" if target else ""
2032        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2033
2034    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2035        this = self.sql(expression, "this")
2036        kind = self.sql(expression, "kind")
2037        expr = self.sql(expression, "expression")
2038        return f"{this} ({kind} => {expr})"
2039
2040    def table_parts(self, expression: exp.Table) -> str:
2041        return ".".join(
2042            self.sql(part)
2043            for part in (
2044                expression.args.get("catalog"),
2045                expression.args.get("db"),
2046                expression.args.get("this"),
2047            )
2048            if part is not None
2049        )
2050
2051    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2052        table = self.table_parts(expression)
2053        only = "ONLY " if expression.args.get("only") else ""
2054        partition = self.sql(expression, "partition")
2055        partition = f" {partition}" if partition else ""
2056        version = self.sql(expression, "version")
2057        version = f" {version}" if version else ""
2058        alias = self.sql(expression, "alias")
2059        alias = f"{sep}{alias}" if alias else ""
2060
2061        sample = self.sql(expression, "sample")
2062        if self.dialect.ALIAS_POST_TABLESAMPLE:
2063            sample_pre_alias = sample
2064            sample_post_alias = ""
2065        else:
2066            sample_pre_alias = ""
2067            sample_post_alias = sample
2068
2069        hints = self.expressions(expression, key="hints", sep=" ")
2070        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2071        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2072        joins = self.indent(
2073            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2074        )
2075        laterals = self.expressions(expression, key="laterals", sep="")
2076
2077        file_format = self.sql(expression, "format")
2078        if file_format:
2079            pattern = self.sql(expression, "pattern")
2080            pattern = f", PATTERN => {pattern}" if pattern else ""
2081            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2082
2083        ordinality = expression.args.get("ordinality") or ""
2084        if ordinality:
2085            ordinality = f" WITH ORDINALITY{alias}"
2086            alias = ""
2087
2088        when = self.sql(expression, "when")
2089        if when:
2090            table = f"{table} {when}"
2091
2092        changes = self.sql(expression, "changes")
2093        changes = f" {changes}" if changes else ""
2094
2095        rows_from = self.expressions(expression, key="rows_from")
2096        if rows_from:
2097            table = f"ROWS FROM {self.wrap(rows_from)}"
2098
2099        return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2100
2101    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2102        table = self.func("TABLE", expression.this)
2103        alias = self.sql(expression, "alias")
2104        alias = f" AS {alias}" if alias else ""
2105        sample = self.sql(expression, "sample")
2106        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2107        joins = self.indent(
2108            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2109        )
2110        return f"{table}{alias}{pivots}{sample}{joins}"
2111
2112    def tablesample_sql(
2113        self,
2114        expression: exp.TableSample,
2115        tablesample_keyword: t.Optional[str] = None,
2116    ) -> str:
2117        method = self.sql(expression, "method")
2118        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2119        numerator = self.sql(expression, "bucket_numerator")
2120        denominator = self.sql(expression, "bucket_denominator")
2121        field = self.sql(expression, "bucket_field")
2122        field = f" ON {field}" if field else ""
2123        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2124        seed = self.sql(expression, "seed")
2125        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2126
2127        size = self.sql(expression, "size")
2128        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2129            size = f"{size} ROWS"
2130
2131        percent = self.sql(expression, "percent")
2132        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2133            percent = f"{percent} PERCENT"
2134
2135        expr = f"{bucket}{percent}{size}"
2136        if self.TABLESAMPLE_REQUIRES_PARENS:
2137            expr = f"({expr})"
2138
2139        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2140
2141    def pivot_sql(self, expression: exp.Pivot) -> str:
2142        expressions = self.expressions(expression, flat=True)
2143        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2144
2145        group = self.sql(expression, "group")
2146
2147        if expression.this:
2148            this = self.sql(expression, "this")
2149            if not expressions:
2150                return f"UNPIVOT {this}"
2151
2152            on = f"{self.seg('ON')} {expressions}"
2153            into = self.sql(expression, "into")
2154            into = f"{self.seg('INTO')} {into}" if into else ""
2155            using = self.expressions(expression, key="using", flat=True)
2156            using = f"{self.seg('USING')} {using}" if using else ""
2157            return f"{direction} {this}{on}{into}{using}{group}"
2158
2159        alias = self.sql(expression, "alias")
2160        alias = f" AS {alias}" if alias else ""
2161
2162        fields = self.expressions(
2163            expression,
2164            "fields",
2165            sep=" ",
2166            dynamic=True,
2167            new_line=True,
2168            skip_first=True,
2169            skip_last=True,
2170        )
2171
2172        include_nulls = expression.args.get("include_nulls")
2173        if include_nulls is not None:
2174            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2175        else:
2176            nulls = ""
2177
2178        default_on_null = self.sql(expression, "default_on_null")
2179        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2180        return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2181
2182    def version_sql(self, expression: exp.Version) -> str:
2183        this = f"FOR {expression.name}"
2184        kind = expression.text("kind")
2185        expr = self.sql(expression, "expression")
2186        return f"{this} {kind} {expr}"
2187
2188    def tuple_sql(self, expression: exp.Tuple) -> str:
2189        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2190
2191    def update_sql(self, expression: exp.Update) -> str:
2192        this = self.sql(expression, "this")
2193        set_sql = self.expressions(expression, flat=True)
2194        from_sql = self.sql(expression, "from")
2195        where_sql = self.sql(expression, "where")
2196        returning = self.sql(expression, "returning")
2197        order = self.sql(expression, "order")
2198        limit = self.sql(expression, "limit")
2199        if self.RETURNING_END:
2200            expression_sql = f"{from_sql}{where_sql}{returning}"
2201        else:
2202            expression_sql = f"{returning}{from_sql}{where_sql}"
2203        options = self.expressions(expression, key="options")
2204        options = f" OPTION({options})" if options else ""
2205        sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}{options}"
2206        return self.prepend_ctes(expression, sql)
2207
2208    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2209        values_as_table = values_as_table and self.VALUES_AS_TABLE
2210
2211        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2212        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2213            args = self.expressions(expression)
2214            alias = self.sql(expression, "alias")
2215            values = f"VALUES{self.seg('')}{args}"
2216            values = (
2217                f"({values})"
2218                if self.WRAP_DERIVED_VALUES
2219                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2220                else values
2221            )
2222            return f"{values} AS {alias}" if alias else values
2223
2224        # Converts `VALUES...` expression into a series of select unions.
2225        alias_node = expression.args.get("alias")
2226        column_names = alias_node and alias_node.columns
2227
2228        selects: t.List[exp.Query] = []
2229
2230        for i, tup in enumerate(expression.expressions):
2231            row = tup.expressions
2232
2233            if i == 0 and column_names:
2234                row = [
2235                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2236                ]
2237
2238            selects.append(exp.Select(expressions=row))
2239
2240        if self.pretty:
2241            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2242            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2243            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2244            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2245            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2246
2247        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2248        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2249        return f"({unions}){alias}"
2250
2251    def var_sql(self, expression: exp.Var) -> str:
2252        return self.sql(expression, "this")
2253
2254    @unsupported_args("expressions")
2255    def into_sql(self, expression: exp.Into) -> str:
2256        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2257        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2258        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2259
2260    def from_sql(self, expression: exp.From) -> str:
2261        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2262
2263    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2264        grouping_sets = self.expressions(expression, indent=False)
2265        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2266
2267    def rollup_sql(self, expression: exp.Rollup) -> str:
2268        expressions = self.expressions(expression, indent=False)
2269        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2270
2271    def cube_sql(self, expression: exp.Cube) -> str:
2272        expressions = self.expressions(expression, indent=False)
2273        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2274
2275    def group_sql(self, expression: exp.Group) -> str:
2276        group_by_all = expression.args.get("all")
2277        if group_by_all is True:
2278            modifier = " ALL"
2279        elif group_by_all is False:
2280            modifier = " DISTINCT"
2281        else:
2282            modifier = ""
2283
2284        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2285
2286        grouping_sets = self.expressions(expression, key="grouping_sets")
2287        cube = self.expressions(expression, key="cube")
2288        rollup = self.expressions(expression, key="rollup")
2289
2290        groupings = csv(
2291            self.seg(grouping_sets) if grouping_sets else "",
2292            self.seg(cube) if cube else "",
2293            self.seg(rollup) if rollup else "",
2294            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2295            sep=self.GROUPINGS_SEP,
2296        )
2297
2298        if (
2299            expression.expressions
2300            and groupings
2301            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2302        ):
2303            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2304
2305        return f"{group_by}{groupings}"
2306
2307    def having_sql(self, expression: exp.Having) -> str:
2308        this = self.indent(self.sql(expression, "this"))
2309        return f"{self.seg('HAVING')}{self.sep()}{this}"
2310
2311    def connect_sql(self, expression: exp.Connect) -> str:
2312        start = self.sql(expression, "start")
2313        start = self.seg(f"START WITH {start}") if start else ""
2314        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2315        connect = self.sql(expression, "connect")
2316        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2317        return start + connect
2318
2319    def prior_sql(self, expression: exp.Prior) -> str:
2320        return f"PRIOR {self.sql(expression, 'this')}"
2321
2322    def join_sql(self, expression: exp.Join) -> str:
2323        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2324            side = None
2325        else:
2326            side = expression.side
2327
2328        op_sql = " ".join(
2329            op
2330            for op in (
2331                expression.method,
2332                "GLOBAL" if expression.args.get("global") else None,
2333                side,
2334                expression.kind,
2335                expression.hint if self.JOIN_HINTS else None,
2336            )
2337            if op
2338        )
2339        match_cond = self.sql(expression, "match_condition")
2340        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2341        on_sql = self.sql(expression, "on")
2342        using = expression.args.get("using")
2343
2344        if not on_sql and using:
2345            on_sql = csv(*(self.sql(column) for column in using))
2346
2347        this = expression.this
2348        this_sql = self.sql(this)
2349
2350        exprs = self.expressions(expression)
2351        if exprs:
2352            this_sql = f"{this_sql},{self.seg(exprs)}"
2353
2354        if on_sql:
2355            on_sql = self.indent(on_sql, skip_first=True)
2356            space = self.seg(" " * self.pad) if self.pretty else " "
2357            if using:
2358                on_sql = f"{space}USING ({on_sql})"
2359            else:
2360                on_sql = f"{space}ON {on_sql}"
2361        elif not op_sql:
2362            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2363                return f" {this_sql}"
2364
2365            return f", {this_sql}"
2366
2367        if op_sql != "STRAIGHT_JOIN":
2368            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2369
2370        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2371        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2372
2373    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2374        args = self.expressions(expression, flat=True)
2375        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2376        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2377
2378    def lateral_op(self, expression: exp.Lateral) -> str:
2379        cross_apply = expression.args.get("cross_apply")
2380
2381        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2382        if cross_apply is True:
2383            op = "INNER JOIN "
2384        elif cross_apply is False:
2385            op = "LEFT JOIN "
2386        else:
2387            op = ""
2388
2389        return f"{op}LATERAL"
2390
2391    def lateral_sql(self, expression: exp.Lateral) -> str:
2392        this = self.sql(expression, "this")
2393
2394        if expression.args.get("view"):
2395            alias = expression.args["alias"]
2396            columns = self.expressions(alias, key="columns", flat=True)
2397            table = f" {alias.name}" if alias.name else ""
2398            columns = f" AS {columns}" if columns else ""
2399            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2400            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2401
2402        alias = self.sql(expression, "alias")
2403        alias = f" AS {alias}" if alias else ""
2404
2405        ordinality = expression.args.get("ordinality") or ""
2406        if ordinality:
2407            ordinality = f" WITH ORDINALITY{alias}"
2408            alias = ""
2409
2410        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2411
2412    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2413        this = self.sql(expression, "this")
2414
2415        args = [
2416            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2417            for e in (expression.args.get(k) for k in ("offset", "expression"))
2418            if e
2419        ]
2420
2421        args_sql = ", ".join(self.sql(e) for e in args)
2422        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2423        expressions = self.expressions(expression, flat=True)
2424        limit_options = self.sql(expression, "limit_options")
2425        expressions = f" BY {expressions}" if expressions else ""
2426
2427        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2428
2429    def offset_sql(self, expression: exp.Offset) -> str:
2430        this = self.sql(expression, "this")
2431        value = expression.expression
2432        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2433        expressions = self.expressions(expression, flat=True)
2434        expressions = f" BY {expressions}" if expressions else ""
2435        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2436
2437    def setitem_sql(self, expression: exp.SetItem) -> str:
2438        kind = self.sql(expression, "kind")
2439        kind = f"{kind} " if kind else ""
2440        this = self.sql(expression, "this")
2441        expressions = self.expressions(expression)
2442        collate = self.sql(expression, "collate")
2443        collate = f" COLLATE {collate}" if collate else ""
2444        global_ = "GLOBAL " if expression.args.get("global") else ""
2445        return f"{global_}{kind}{this}{expressions}{collate}"
2446
2447    def set_sql(self, expression: exp.Set) -> str:
2448        expressions = f" {self.expressions(expression, flat=True)}"
2449        tag = " TAG" if expression.args.get("tag") else ""
2450        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2451
2452    def queryband_sql(self, expression: exp.QueryBand) -> str:
2453        this = self.sql(expression, "this")
2454        update = " UPDATE" if expression.args.get("update") else ""
2455        scope = self.sql(expression, "scope")
2456        scope = f" FOR {scope}" if scope else ""
2457
2458        return f"QUERY_BAND = {this}{update}{scope}"
2459
2460    def pragma_sql(self, expression: exp.Pragma) -> str:
2461        return f"PRAGMA {self.sql(expression, 'this')}"
2462
2463    def lock_sql(self, expression: exp.Lock) -> str:
2464        if not self.LOCKING_READS_SUPPORTED:
2465            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2466            return ""
2467
2468        update = expression.args["update"]
2469        key = expression.args.get("key")
2470        if update:
2471            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2472        else:
2473            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2474        expressions = self.expressions(expression, flat=True)
2475        expressions = f" OF {expressions}" if expressions else ""
2476        wait = expression.args.get("wait")
2477
2478        if wait is not None:
2479            if isinstance(wait, exp.Literal):
2480                wait = f" WAIT {self.sql(wait)}"
2481            else:
2482                wait = " NOWAIT" if wait else " SKIP LOCKED"
2483
2484        return f"{lock_type}{expressions}{wait or ''}"
2485
2486    def literal_sql(self, expression: exp.Literal) -> str:
2487        text = expression.this or ""
2488        if expression.is_string:
2489            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2490        return text
2491
2492    def escape_str(
2493        self,
2494        text: str,
2495        escape_backslash: bool = True,
2496        delimiter: t.Optional[str] = None,
2497        escaped_delimiter: t.Optional[str] = None,
2498    ) -> str:
2499        if self.dialect.ESCAPED_SEQUENCES:
2500            to_escaped = self.dialect.ESCAPED_SEQUENCES
2501            text = "".join(
2502                to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text
2503            )
2504
2505        delimiter = delimiter or self.dialect.QUOTE_END
2506        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2507
2508        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2509
2510    def loaddata_sql(self, expression: exp.LoadData) -> str:
2511        local = " LOCAL" if expression.args.get("local") else ""
2512        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2513        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2514        this = f" INTO TABLE {self.sql(expression, 'this')}"
2515        partition = self.sql(expression, "partition")
2516        partition = f" {partition}" if partition else ""
2517        input_format = self.sql(expression, "input_format")
2518        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2519        serde = self.sql(expression, "serde")
2520        serde = f" SERDE {serde}" if serde else ""
2521        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2522
2523    def null_sql(self, *_) -> str:
2524        return "NULL"
2525
2526    def boolean_sql(self, expression: exp.Boolean) -> str:
2527        return "TRUE" if expression.this else "FALSE"
2528
2529    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2530        this = self.sql(expression, "this")
2531        this = f"{this} " if this else this
2532        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2533        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
2534
2535    def withfill_sql(self, expression: exp.WithFill) -> str:
2536        from_sql = self.sql(expression, "from")
2537        from_sql = f" FROM {from_sql}" if from_sql else ""
2538        to_sql = self.sql(expression, "to")
2539        to_sql = f" TO {to_sql}" if to_sql else ""
2540        step_sql = self.sql(expression, "step")
2541        step_sql = f" STEP {step_sql}" if step_sql else ""
2542        interpolated_values = [
2543            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2544            if isinstance(e, exp.Alias)
2545            else self.sql(e, "this")
2546            for e in expression.args.get("interpolate") or []
2547        ]
2548        interpolate = (
2549            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2550        )
2551        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2552
2553    def cluster_sql(self, expression: exp.Cluster) -> str:
2554        return self.op_expressions("CLUSTER BY", expression)
2555
2556    def distribute_sql(self, expression: exp.Distribute) -> str:
2557        return self.op_expressions("DISTRIBUTE BY", expression)
2558
2559    def sort_sql(self, expression: exp.Sort) -> str:
2560        return self.op_expressions("SORT BY", expression)
2561
2562    def ordered_sql(self, expression: exp.Ordered) -> str:
2563        desc = expression.args.get("desc")
2564        asc = not desc
2565
2566        nulls_first = expression.args.get("nulls_first")
2567        nulls_last = not nulls_first
2568        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2569        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2570        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2571
2572        this = self.sql(expression, "this")
2573
2574        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2575        nulls_sort_change = ""
2576        if nulls_first and (
2577            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2578        ):
2579            nulls_sort_change = " NULLS FIRST"
2580        elif (
2581            nulls_last
2582            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2583            and not nulls_are_last
2584        ):
2585            nulls_sort_change = " NULLS LAST"
2586
2587        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2588        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2589            window = expression.find_ancestor(exp.Window, exp.Select)
2590            if isinstance(window, exp.Window) and window.args.get("spec"):
2591                self.unsupported(
2592                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2593                )
2594                nulls_sort_change = ""
2595            elif self.NULL_ORDERING_SUPPORTED is False and (
2596                (asc and nulls_sort_change == " NULLS LAST")
2597                or (desc and nulls_sort_change == " NULLS FIRST")
2598            ):
2599                # BigQuery does not allow these ordering/nulls combinations when used under
2600                # an aggregation func or under a window containing one
2601                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2602
2603                if isinstance(ancestor, exp.Window):
2604                    ancestor = ancestor.this
2605                if isinstance(ancestor, exp.AggFunc):
2606                    self.unsupported(
2607                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2608                    )
2609                    nulls_sort_change = ""
2610            elif self.NULL_ORDERING_SUPPORTED is None:
2611                if expression.this.is_int:
2612                    self.unsupported(
2613                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2614                    )
2615                elif not isinstance(expression.this, exp.Rand):
2616                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2617                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2618                nulls_sort_change = ""
2619
2620        with_fill = self.sql(expression, "with_fill")
2621        with_fill = f" {with_fill}" if with_fill else ""
2622
2623        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2624
2625    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2626        window_frame = self.sql(expression, "window_frame")
2627        window_frame = f"{window_frame} " if window_frame else ""
2628
2629        this = self.sql(expression, "this")
2630
2631        return f"{window_frame}{this}"
2632
2633    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2634        partition = self.partition_by_sql(expression)
2635        order = self.sql(expression, "order")
2636        measures = self.expressions(expression, key="measures")
2637        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2638        rows = self.sql(expression, "rows")
2639        rows = self.seg(rows) if rows else ""
2640        after = self.sql(expression, "after")
2641        after = self.seg(after) if after else ""
2642        pattern = self.sql(expression, "pattern")
2643        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2644        definition_sqls = [
2645            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2646            for definition in expression.args.get("define", [])
2647        ]
2648        definitions = self.expressions(sqls=definition_sqls)
2649        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2650        body = "".join(
2651            (
2652                partition,
2653                order,
2654                measures,
2655                rows,
2656                after,
2657                pattern,
2658                define,
2659            )
2660        )
2661        alias = self.sql(expression, "alias")
2662        alias = f" {alias}" if alias else ""
2663        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2664
2665    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2666        limit = expression.args.get("limit")
2667
2668        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2669            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2670        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2671            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2672
2673        return csv(
2674            *sqls,
2675            *[self.sql(join) for join in expression.args.get("joins") or []],
2676            self.sql(expression, "match"),
2677            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2678            self.sql(expression, "prewhere"),
2679            self.sql(expression, "where"),
2680            self.sql(expression, "connect"),
2681            self.sql(expression, "group"),
2682            self.sql(expression, "having"),
2683            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2684            self.sql(expression, "order"),
2685            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2686            *self.after_limit_modifiers(expression),
2687            self.options_modifier(expression),
2688            self.for_modifiers(expression),
2689            sep="",
2690        )
2691
2692    def options_modifier(self, expression: exp.Expression) -> str:
2693        options = self.expressions(expression, key="options")
2694        return f" {options}" if options else ""
2695
2696    def for_modifiers(self, expression: exp.Expression) -> str:
2697        for_modifiers = self.expressions(expression, key="for")
2698        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
2699
2700    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2701        self.unsupported("Unsupported query option.")
2702        return ""
2703
2704    def offset_limit_modifiers(
2705        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2706    ) -> t.List[str]:
2707        return [
2708            self.sql(expression, "offset") if fetch else self.sql(limit),
2709            self.sql(limit) if fetch else self.sql(expression, "offset"),
2710        ]
2711
2712    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2713        locks = self.expressions(expression, key="locks", sep=" ")
2714        locks = f" {locks}" if locks else ""
2715        return [locks, self.sql(expression, "sample")]
2716
2717    def select_sql(self, expression: exp.Select) -> str:
2718        into = expression.args.get("into")
2719        if not self.SUPPORTS_SELECT_INTO and into:
2720            into.pop()
2721
2722        hint = self.sql(expression, "hint")
2723        distinct = self.sql(expression, "distinct")
2724        distinct = f" {distinct}" if distinct else ""
2725        kind = self.sql(expression, "kind")
2726
2727        limit = expression.args.get("limit")
2728        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2729            top = self.limit_sql(limit, top=True)
2730            limit.pop()
2731        else:
2732            top = ""
2733
2734        expressions = self.expressions(expression)
2735
2736        if kind:
2737            if kind in self.SELECT_KINDS:
2738                kind = f" AS {kind}"
2739            else:
2740                if kind == "STRUCT":
2741                    expressions = self.expressions(
2742                        sqls=[
2743                            self.sql(
2744                                exp.Struct(
2745                                    expressions=[
2746                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2747                                        if isinstance(e, exp.Alias)
2748                                        else e
2749                                        for e in expression.expressions
2750                                    ]
2751                                )
2752                            )
2753                        ]
2754                    )
2755                kind = ""
2756
2757        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2758        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2759
2760        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2761        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2762        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2763        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2764        sql = self.query_modifiers(
2765            expression,
2766            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2767            self.sql(expression, "into", comment=False),
2768            self.sql(expression, "from", comment=False),
2769        )
2770
2771        # If both the CTE and SELECT clauses have comments, generate the latter earlier
2772        if expression.args.get("with"):
2773            sql = self.maybe_comment(sql, expression)
2774            expression.pop_comments()
2775
2776        sql = self.prepend_ctes(expression, sql)
2777
2778        if not self.SUPPORTS_SELECT_INTO and into:
2779            if into.args.get("temporary"):
2780                table_kind = " TEMPORARY"
2781            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
2782                table_kind = " UNLOGGED"
2783            else:
2784                table_kind = ""
2785            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
2786
2787        return sql
2788
2789    def schema_sql(self, expression: exp.Schema) -> str:
2790        this = self.sql(expression, "this")
2791        sql = self.schema_columns_sql(expression)
2792        return f"{this} {sql}" if this and sql else this or sql
2793
2794    def schema_columns_sql(self, expression: exp.Schema) -> str:
2795        if expression.expressions:
2796            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2797        return ""
2798
2799    def star_sql(self, expression: exp.Star) -> str:
2800        except_ = self.expressions(expression, key="except", flat=True)
2801        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2802        replace = self.expressions(expression, key="replace", flat=True)
2803        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
2804        rename = self.expressions(expression, key="rename", flat=True)
2805        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
2806        return f"*{except_}{replace}{rename}"
2807
2808    def parameter_sql(self, expression: exp.Parameter) -> str:
2809        this = self.sql(expression, "this")
2810        return f"{self.PARAMETER_TOKEN}{this}"
2811
2812    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2813        this = self.sql(expression, "this")
2814        kind = expression.text("kind")
2815        if kind:
2816            kind = f"{kind}."
2817        return f"@@{kind}{this}"
2818
2819    def placeholder_sql(self, expression: exp.Placeholder) -> str:
2820        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
2821
2822    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2823        alias = self.sql(expression, "alias")
2824        alias = f"{sep}{alias}" if alias else ""
2825        sample = self.sql(expression, "sample")
2826        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
2827            alias = f"{sample}{alias}"
2828
2829            # Set to None so it's not generated again by self.query_modifiers()
2830            expression.set("sample", None)
2831
2832        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2833        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2834        return self.prepend_ctes(expression, sql)
2835
2836    def qualify_sql(self, expression: exp.Qualify) -> str:
2837        this = self.indent(self.sql(expression, "this"))
2838        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
2839
2840    def unnest_sql(self, expression: exp.Unnest) -> str:
2841        args = self.expressions(expression, flat=True)
2842
2843        alias = expression.args.get("alias")
2844        offset = expression.args.get("offset")
2845
2846        if self.UNNEST_WITH_ORDINALITY:
2847            if alias and isinstance(offset, exp.Expression):
2848                alias.append("columns", offset)
2849
2850        if alias and self.dialect.UNNEST_COLUMN_ONLY:
2851            columns = alias.columns
2852            alias = self.sql(columns[0]) if columns else ""
2853        else:
2854            alias = self.sql(alias)
2855
2856        alias = f" AS {alias}" if alias else alias
2857        if self.UNNEST_WITH_ORDINALITY:
2858            suffix = f" WITH ORDINALITY{alias}" if offset else alias
2859        else:
2860            if isinstance(offset, exp.Expression):
2861                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
2862            elif offset:
2863                suffix = f"{alias} WITH OFFSET"
2864            else:
2865                suffix = alias
2866
2867        return f"UNNEST({args}){suffix}"
2868
2869    def prewhere_sql(self, expression: exp.PreWhere) -> str:
2870        return ""
2871
2872    def where_sql(self, expression: exp.Where) -> str:
2873        this = self.indent(self.sql(expression, "this"))
2874        return f"{self.seg('WHERE')}{self.sep()}{this}"
2875
2876    def window_sql(self, expression: exp.Window) -> str:
2877        this = self.sql(expression, "this")
2878        partition = self.partition_by_sql(expression)
2879        order = expression.args.get("order")
2880        order = self.order_sql(order, flat=True) if order else ""
2881        spec = self.sql(expression, "spec")
2882        alias = self.sql(expression, "alias")
2883        over = self.sql(expression, "over") or "OVER"
2884
2885        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
2886
2887        first = expression.args.get("first")
2888        if first is None:
2889            first = ""
2890        else:
2891            first = "FIRST" if first else "LAST"
2892
2893        if not partition and not order and not spec and alias:
2894            return f"{this} {alias}"
2895
2896        args = self.format_args(
2897            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
2898        )
2899        return f"{this} ({args})"
2900
2901    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
2902        partition = self.expressions(expression, key="partition_by", flat=True)
2903        return f"PARTITION BY {partition}" if partition else ""
2904
2905    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
2906        kind = self.sql(expression, "kind")
2907        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
2908        end = (
2909            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
2910            or "CURRENT ROW"
2911        )
2912
2913        window_spec = f"{kind} BETWEEN {start} AND {end}"
2914
2915        exclude = self.sql(expression, "exclude")
2916        if exclude:
2917            if self.SUPPORTS_WINDOW_EXCLUDE:
2918                window_spec += f" EXCLUDE {exclude}"
2919            else:
2920                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
2921
2922        return window_spec
2923
2924    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
2925        this = self.sql(expression, "this")
2926        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
2927        return f"{this} WITHIN GROUP ({expression_sql})"
2928
2929    def between_sql(self, expression: exp.Between) -> str:
2930        this = self.sql(expression, "this")
2931        low = self.sql(expression, "low")
2932        high = self.sql(expression, "high")
2933        symmetric = expression.args.get("symmetric")
2934
2935        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
2936            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
2937
2938        flag = (
2939            " SYMMETRIC"
2940            if symmetric
2941            else " ASYMMETRIC"
2942            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
2943            else ""  # silently drop ASYMMETRIC – semantics identical
2944        )
2945        return f"{this} BETWEEN{flag} {low} AND {high}"
2946
2947    def bracket_offset_expressions(
2948        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
2949    ) -> t.List[exp.Expression]:
2950        return apply_index_offset(
2951            expression.this,
2952            expression.expressions,
2953            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
2954            dialect=self.dialect,
2955        )
2956
2957    def bracket_sql(self, expression: exp.Bracket) -> str:
2958        expressions = self.bracket_offset_expressions(expression)
2959        expressions_sql = ", ".join(self.sql(e) for e in expressions)
2960        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
2961
2962    def all_sql(self, expression: exp.All) -> str:
2963        this = self.sql(expression, "this")
2964        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
2965            this = self.wrap(this)
2966        return f"ALL {this}"
2967
2968    def any_sql(self, expression: exp.Any) -> str:
2969        this = self.sql(expression, "this")
2970        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
2971            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
2972                this = self.wrap(this)
2973            return f"ANY{this}"
2974        return f"ANY {this}"
2975
2976    def exists_sql(self, expression: exp.Exists) -> str:
2977        return f"EXISTS{self.wrap(expression)}"
2978
2979    def case_sql(self, expression: exp.Case) -> str:
2980        this = self.sql(expression, "this")
2981        statements = [f"CASE {this}" if this else "CASE"]
2982
2983        for e in expression.args["ifs"]:
2984            statements.append(f"WHEN {self.sql(e, 'this')}")
2985            statements.append(f"THEN {self.sql(e, 'true')}")
2986
2987        default = self.sql(expression, "default")
2988
2989        if default:
2990            statements.append(f"ELSE {default}")
2991
2992        statements.append("END")
2993
2994        if self.pretty and self.too_wide(statements):
2995            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
2996
2997        return " ".join(statements)
2998
2999    def constraint_sql(self, expression: exp.Constraint) -> str:
3000        this = self.sql(expression, "this")
3001        expressions = self.expressions(expression, flat=True)
3002        return f"CONSTRAINT {this} {expressions}"
3003
3004    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3005        order = expression.args.get("order")
3006        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3007        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3008
3009    def extract_sql(self, expression: exp.Extract) -> str:
3010        from sqlglot.dialects.dialect import map_date_part
3011
3012        this = (
3013            map_date_part(expression.this, self.dialect)
3014            if self.NORMALIZE_EXTRACT_DATE_PARTS
3015            else expression.this
3016        )
3017        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3018        expression_sql = self.sql(expression, "expression")
3019
3020        return f"EXTRACT({this_sql} FROM {expression_sql})"
3021
3022    def trim_sql(self, expression: exp.Trim) -> str:
3023        trim_type = self.sql(expression, "position")
3024
3025        if trim_type == "LEADING":
3026            func_name = "LTRIM"
3027        elif trim_type == "TRAILING":
3028            func_name = "RTRIM"
3029        else:
3030            func_name = "TRIM"
3031
3032        return self.func(func_name, expression.this, expression.expression)
3033
3034    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
3035        args = expression.expressions
3036        if isinstance(expression, exp.ConcatWs):
3037            args = args[1:]  # Skip the delimiter
3038
3039        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3040            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
3041
3042        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
3043
3044            def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression:
3045                if not e.type:
3046                    from sqlglot.optimizer.annotate_types import annotate_types
3047
3048                    e = annotate_types(e, dialect=self.dialect)
3049
3050                if e.is_string or e.is_type(exp.DataType.Type.ARRAY):
3051                    return e
3052
3053                return exp.func("coalesce", e, exp.Literal.string(""))
3054
3055            args = [_wrap_with_coalesce(e) for e in args]
3056
3057        return args
3058
3059    def concat_sql(self, expression: exp.Concat) -> str:
3060        expressions = self.convert_concat_args(expression)
3061
3062        # Some dialects don't allow a single-argument CONCAT call
3063        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3064            return self.sql(expressions[0])
3065
3066        return self.func("CONCAT", *expressions)
3067
3068    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3069        return self.func(
3070            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3071        )
3072
3073    def check_sql(self, expression: exp.Check) -> str:
3074        this = self.sql(expression, key="this")
3075        return f"CHECK ({this})"
3076
3077    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3078        expressions = self.expressions(expression, flat=True)
3079        expressions = f" ({expressions})" if expressions else ""
3080        reference = self.sql(expression, "reference")
3081        reference = f" {reference}" if reference else ""
3082        delete = self.sql(expression, "delete")
3083        delete = f" ON DELETE {delete}" if delete else ""
3084        update = self.sql(expression, "update")
3085        update = f" ON UPDATE {update}" if update else ""
3086        options = self.expressions(expression, key="options", flat=True, sep=" ")
3087        options = f" {options}" if options else ""
3088        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3089
3090    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3091        expressions = self.expressions(expression, flat=True)
3092        include = self.sql(expression, "include")
3093        options = self.expressions(expression, key="options", flat=True, sep=" ")
3094        options = f" {options}" if options else ""
3095        return f"PRIMARY KEY ({expressions}){include}{options}"
3096
3097    def if_sql(self, expression: exp.If) -> str:
3098        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3099
3100    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3101        if self.MATCH_AGAINST_TABLE_PREFIX:
3102            expressions = []
3103            for expr in expression.expressions:
3104                if isinstance(expr, exp.Table):
3105                    expressions.append(f"TABLE {self.sql(expr)}")
3106                else:
3107                    expressions.append(expr)
3108        else:
3109            expressions = expression.expressions
3110
3111        modifier = expression.args.get("modifier")
3112        modifier = f" {modifier}" if modifier else ""
3113        return (
3114            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3115        )
3116
3117    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3118        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3119
3120    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3121        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3122
3123        if expression.args.get("escape"):
3124            path = self.escape_str(path)
3125
3126        if self.QUOTE_JSON_PATH:
3127            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3128
3129        return path
3130
3131    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3132        if isinstance(expression, exp.JSONPathPart):
3133            transform = self.TRANSFORMS.get(expression.__class__)
3134            if not callable(transform):
3135                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3136                return ""
3137
3138            return transform(self, expression)
3139
3140        if isinstance(expression, int):
3141            return str(expression)
3142
3143        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3144            escaped = expression.replace("'", "\\'")
3145            escaped = f"\\'{expression}\\'"
3146        else:
3147            escaped = expression.replace('"', '\\"')
3148            escaped = f'"{escaped}"'
3149
3150        return escaped
3151
3152    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3153        return f"{self.sql(expression, 'this')} FORMAT JSON"
3154
3155    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3156        # Output the Teradata column FORMAT override.
3157        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3158        this = self.sql(expression, "this")
3159        fmt = self.sql(expression, "format")
3160        return f"{this} (FORMAT {fmt})"
3161
3162    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3163        null_handling = expression.args.get("null_handling")
3164        null_handling = f" {null_handling}" if null_handling else ""
3165
3166        unique_keys = expression.args.get("unique_keys")
3167        if unique_keys is not None:
3168            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3169        else:
3170            unique_keys = ""
3171
3172        return_type = self.sql(expression, "return_type")
3173        return_type = f" RETURNING {return_type}" if return_type else ""
3174        encoding = self.sql(expression, "encoding")
3175        encoding = f" ENCODING {encoding}" if encoding else ""
3176
3177        return self.func(
3178            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3179            *expression.expressions,
3180            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3181        )
3182
3183    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3184        return self.jsonobject_sql(expression)
3185
3186    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3187        null_handling = expression.args.get("null_handling")
3188        null_handling = f" {null_handling}" if null_handling else ""
3189        return_type = self.sql(expression, "return_type")
3190        return_type = f" RETURNING {return_type}" if return_type else ""
3191        strict = " STRICT" if expression.args.get("strict") else ""
3192        return self.func(
3193            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3194        )
3195
3196    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3197        this = self.sql(expression, "this")
3198        order = self.sql(expression, "order")
3199        null_handling = expression.args.get("null_handling")
3200        null_handling = f" {null_handling}" if null_handling else ""
3201        return_type = self.sql(expression, "return_type")
3202        return_type = f" RETURNING {return_type}" if return_type else ""
3203        strict = " STRICT" if expression.args.get("strict") else ""
3204        return self.func(
3205            "JSON_ARRAYAGG",
3206            this,
3207            suffix=f"{order}{null_handling}{return_type}{strict})",
3208        )
3209
3210    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3211        path = self.sql(expression, "path")
3212        path = f" PATH {path}" if path else ""
3213        nested_schema = self.sql(expression, "nested_schema")
3214
3215        if nested_schema:
3216            return f"NESTED{path} {nested_schema}"
3217
3218        this = self.sql(expression, "this")
3219        kind = self.sql(expression, "kind")
3220        kind = f" {kind}" if kind else ""
3221
3222        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3223        return f"{this}{kind}{path}{ordinality}"
3224
3225    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3226        return self.func("COLUMNS", *expression.expressions)
3227
3228    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3229        this = self.sql(expression, "this")
3230        path = self.sql(expression, "path")
3231        path = f", {path}" if path else ""
3232        error_handling = expression.args.get("error_handling")
3233        error_handling = f" {error_handling}" if error_handling else ""
3234        empty_handling = expression.args.get("empty_handling")
3235        empty_handling = f" {empty_handling}" if empty_handling else ""
3236        schema = self.sql(expression, "schema")
3237        return self.func(
3238            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3239        )
3240
3241    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3242        this = self.sql(expression, "this")
3243        kind = self.sql(expression, "kind")
3244        path = self.sql(expression, "path")
3245        path = f" {path}" if path else ""
3246        as_json = " AS JSON" if expression.args.get("as_json") else ""
3247        return f"{this} {kind}{path}{as_json}"
3248
3249    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3250        this = self.sql(expression, "this")
3251        path = self.sql(expression, "path")
3252        path = f", {path}" if path else ""
3253        expressions = self.expressions(expression)
3254        with_ = (
3255            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3256            if expressions
3257            else ""
3258        )
3259        return f"OPENJSON({this}{path}){with_}"
3260
3261    def in_sql(self, expression: exp.In) -> str:
3262        query = expression.args.get("query")
3263        unnest = expression.args.get("unnest")
3264        field = expression.args.get("field")
3265        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3266
3267        if query:
3268            in_sql = self.sql(query)
3269        elif unnest:
3270            in_sql = self.in_unnest_op(unnest)
3271        elif field:
3272            in_sql = self.sql(field)
3273        else:
3274            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3275
3276        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3277
3278    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3279        return f"(SELECT {self.sql(unnest)})"
3280
3281    def interval_sql(self, expression: exp.Interval) -> str:
3282        unit_expression = expression.args.get("unit")
3283        unit = self.sql(unit_expression) if unit_expression else ""
3284        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3285            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3286        unit = f" {unit}" if unit else ""
3287
3288        if self.SINGLE_STRING_INTERVAL:
3289            this = expression.this.name if expression.this else ""
3290            if this:
3291                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3292                    return f"INTERVAL '{this}'{unit}"
3293                return f"INTERVAL '{this}{unit}'"
3294            return f"INTERVAL{unit}"
3295
3296        this = self.sql(expression, "this")
3297        if this:
3298            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3299            this = f" {this}" if unwrapped else f" ({this})"
3300
3301        return f"INTERVAL{this}{unit}"
3302
3303    def return_sql(self, expression: exp.Return) -> str:
3304        return f"RETURN {self.sql(expression, 'this')}"
3305
3306    def reference_sql(self, expression: exp.Reference) -> str:
3307        this = self.sql(expression, "this")
3308        expressions = self.expressions(expression, flat=True)
3309        expressions = f"({expressions})" if expressions else ""
3310        options = self.expressions(expression, key="options", flat=True, sep=" ")
3311        options = f" {options}" if options else ""
3312        return f"REFERENCES {this}{expressions}{options}"
3313
3314    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3315        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3316        parent = expression.parent
3317        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3318        return self.func(
3319            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3320        )
3321
3322    def paren_sql(self, expression: exp.Paren) -> str:
3323        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3324        return f"({sql}{self.seg(')', sep='')}"
3325
3326    def neg_sql(self, expression: exp.Neg) -> str:
3327        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3328        this_sql = self.sql(expression, "this")
3329        sep = " " if this_sql[0] == "-" else ""
3330        return f"-{sep}{this_sql}"
3331
3332    def not_sql(self, expression: exp.Not) -> str:
3333        return f"NOT {self.sql(expression, 'this')}"
3334
3335    def alias_sql(self, expression: exp.Alias) -> str:
3336        alias = self.sql(expression, "alias")
3337        alias = f" AS {alias}" if alias else ""
3338        return f"{self.sql(expression, 'this')}{alias}"
3339
3340    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3341        alias = expression.args["alias"]
3342
3343        parent = expression.parent
3344        pivot = parent and parent.parent
3345
3346        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3347            identifier_alias = isinstance(alias, exp.Identifier)
3348            literal_alias = isinstance(alias, exp.Literal)
3349
3350            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3351                alias.replace(exp.Literal.string(alias.output_name))
3352            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3353                alias.replace(exp.to_identifier(alias.output_name))
3354
3355        return self.alias_sql(expression)
3356
3357    def aliases_sql(self, expression: exp.Aliases) -> str:
3358        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3359
3360    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3361        this = self.sql(expression, "this")
3362        index = self.sql(expression, "expression")
3363        return f"{this} AT {index}"
3364
3365    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3366        this = self.sql(expression, "this")
3367        zone = self.sql(expression, "zone")
3368        return f"{this} AT TIME ZONE {zone}"
3369
3370    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3371        this = self.sql(expression, "this")
3372        zone = self.sql(expression, "zone")
3373        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3374
3375    def add_sql(self, expression: exp.Add) -> str:
3376        return self.binary(expression, "+")
3377
3378    def and_sql(
3379        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3380    ) -> str:
3381        return self.connector_sql(expression, "AND", stack)
3382
3383    def or_sql(
3384        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3385    ) -> str:
3386        return self.connector_sql(expression, "OR", stack)
3387
3388    def xor_sql(
3389        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3390    ) -> str:
3391        return self.connector_sql(expression, "XOR", stack)
3392
3393    def connector_sql(
3394        self,
3395        expression: exp.Connector,
3396        op: str,
3397        stack: t.Optional[t.List[str | exp.Expression]] = None,
3398    ) -> str:
3399        if stack is not None:
3400            if expression.expressions:
3401                stack.append(self.expressions(expression, sep=f" {op} "))
3402            else:
3403                stack.append(expression.right)
3404                if expression.comments and self.comments:
3405                    for comment in expression.comments:
3406                        if comment:
3407                            op += f" /*{self.sanitize_comment(comment)}*/"
3408                stack.extend((op, expression.left))
3409            return op
3410
3411        stack = [expression]
3412        sqls: t.List[str] = []
3413        ops = set()
3414
3415        while stack:
3416            node = stack.pop()
3417            if isinstance(node, exp.Connector):
3418                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3419            else:
3420                sql = self.sql(node)
3421                if sqls and sqls[-1] in ops:
3422                    sqls[-1] += f" {sql}"
3423                else:
3424                    sqls.append(sql)
3425
3426        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3427        return sep.join(sqls)
3428
3429    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3430        return self.binary(expression, "&")
3431
3432    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3433        return self.binary(expression, "<<")
3434
3435    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3436        return f"~{self.sql(expression, 'this')}"
3437
3438    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3439        return self.binary(expression, "|")
3440
3441    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3442        return self.binary(expression, ">>")
3443
3444    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3445        return self.binary(expression, "^")
3446
3447    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3448        format_sql = self.sql(expression, "format")
3449        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3450        to_sql = self.sql(expression, "to")
3451        to_sql = f" {to_sql}" if to_sql else ""
3452        action = self.sql(expression, "action")
3453        action = f" {action}" if action else ""
3454        default = self.sql(expression, "default")
3455        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3456        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3457
3458    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3459        zone = self.sql(expression, "this")
3460        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3461
3462    def collate_sql(self, expression: exp.Collate) -> str:
3463        if self.COLLATE_IS_FUNC:
3464            return self.function_fallback_sql(expression)
3465        return self.binary(expression, "COLLATE")
3466
3467    def command_sql(self, expression: exp.Command) -> str:
3468        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3469
3470    def comment_sql(self, expression: exp.Comment) -> str:
3471        this = self.sql(expression, "this")
3472        kind = expression.args["kind"]
3473        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3474        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3475        expression_sql = self.sql(expression, "expression")
3476        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3477
3478    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3479        this = self.sql(expression, "this")
3480        delete = " DELETE" if expression.args.get("delete") else ""
3481        recompress = self.sql(expression, "recompress")
3482        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3483        to_disk = self.sql(expression, "to_disk")
3484        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3485        to_volume = self.sql(expression, "to_volume")
3486        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3487        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3488
3489    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3490        where = self.sql(expression, "where")
3491        group = self.sql(expression, "group")
3492        aggregates = self.expressions(expression, key="aggregates")
3493        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3494
3495        if not (where or group or aggregates) and len(expression.expressions) == 1:
3496            return f"TTL {self.expressions(expression, flat=True)}"
3497
3498        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3499
3500    def transaction_sql(self, expression: exp.Transaction) -> str:
3501        modes = self.expressions(expression, key="modes")
3502        modes = f" {modes}" if modes else ""
3503        return f"BEGIN{modes}"
3504
3505    def commit_sql(self, expression: exp.Commit) -> str:
3506        chain = expression.args.get("chain")
3507        if chain is not None:
3508            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3509
3510        return f"COMMIT{chain or ''}"
3511
3512    def rollback_sql(self, expression: exp.Rollback) -> str:
3513        savepoint = expression.args.get("savepoint")
3514        savepoint = f" TO {savepoint}" if savepoint else ""
3515        return f"ROLLBACK{savepoint}"
3516
3517    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3518        this = self.sql(expression, "this")
3519
3520        dtype = self.sql(expression, "dtype")
3521        if dtype:
3522            collate = self.sql(expression, "collate")
3523            collate = f" COLLATE {collate}" if collate else ""
3524            using = self.sql(expression, "using")
3525            using = f" USING {using}" if using else ""
3526            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3527            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3528
3529        default = self.sql(expression, "default")
3530        if default:
3531            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3532
3533        comment = self.sql(expression, "comment")
3534        if comment:
3535            return f"ALTER COLUMN {this} COMMENT {comment}"
3536
3537        visible = expression.args.get("visible")
3538        if visible:
3539            return f"ALTER COLUMN {this} SET {visible}"
3540
3541        allow_null = expression.args.get("allow_null")
3542        drop = expression.args.get("drop")
3543
3544        if not drop and not allow_null:
3545            self.unsupported("Unsupported ALTER COLUMN syntax")
3546
3547        if allow_null is not None:
3548            keyword = "DROP" if drop else "SET"
3549            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3550
3551        return f"ALTER COLUMN {this} DROP DEFAULT"
3552
3553    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3554        this = self.sql(expression, "this")
3555
3556        visible = expression.args.get("visible")
3557        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3558
3559        return f"ALTER INDEX {this} {visible_sql}"
3560
3561    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3562        this = self.sql(expression, "this")
3563        if not isinstance(expression.this, exp.Var):
3564            this = f"KEY DISTKEY {this}"
3565        return f"ALTER DISTSTYLE {this}"
3566
3567    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3568        compound = " COMPOUND" if expression.args.get("compound") else ""
3569        this = self.sql(expression, "this")
3570        expressions = self.expressions(expression, flat=True)
3571        expressions = f"({expressions})" if expressions else ""
3572        return f"ALTER{compound} SORTKEY {this or expressions}"
3573
3574    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
3575        if not self.RENAME_TABLE_WITH_DB:
3576            # Remove db from tables
3577            expression = expression.transform(
3578                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3579            ).assert_is(exp.AlterRename)
3580        this = self.sql(expression, "this")
3581        to_kw = " TO" if include_to else ""
3582        return f"RENAME{to_kw} {this}"
3583
3584    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3585        exists = " IF EXISTS" if expression.args.get("exists") else ""
3586        old_column = self.sql(expression, "this")
3587        new_column = self.sql(expression, "to")
3588        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3589
3590    def alterset_sql(self, expression: exp.AlterSet) -> str:
3591        exprs = self.expressions(expression, flat=True)
3592        if self.ALTER_SET_WRAPPED:
3593            exprs = f"({exprs})"
3594
3595        return f"SET {exprs}"
3596
3597    def alter_sql(self, expression: exp.Alter) -> str:
3598        actions = expression.args["actions"]
3599
3600        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3601            actions[0], exp.ColumnDef
3602        ):
3603            actions_sql = self.expressions(expression, key="actions", flat=True)
3604            actions_sql = f"ADD {actions_sql}"
3605        else:
3606            actions_list = []
3607            for action in actions:
3608                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3609                    action_sql = self.add_column_sql(action)
3610                else:
3611                    action_sql = self.sql(action)
3612                    if isinstance(action, exp.Query):
3613                        action_sql = f"AS {action_sql}"
3614
3615                actions_list.append(action_sql)
3616
3617            actions_sql = self.format_args(*actions_list).lstrip("\n")
3618
3619        exists = " IF EXISTS" if expression.args.get("exists") else ""
3620        on_cluster = self.sql(expression, "cluster")
3621        on_cluster = f" {on_cluster}" if on_cluster else ""
3622        only = " ONLY" if expression.args.get("only") else ""
3623        options = self.expressions(expression, key="options")
3624        options = f", {options}" if options else ""
3625        kind = self.sql(expression, "kind")
3626        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3627        check = " WITH CHECK" if expression.args.get("check") else ""
3628        cascade = (
3629            " CASCADE"
3630            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
3631            else ""
3632        )
3633        this = self.sql(expression, "this")
3634        this = f" {this}" if this else ""
3635
3636        return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
3637
3638    def altersession_sql(self, expression: exp.AlterSession) -> str:
3639        items_sql = self.expressions(expression, flat=True)
3640        keyword = "UNSET" if expression.args.get("unset") else "SET"
3641        return f"{keyword} {items_sql}"
3642
3643    def add_column_sql(self, expression: exp.Expression) -> str:
3644        sql = self.sql(expression)
3645        if isinstance(expression, exp.Schema):
3646            column_text = " COLUMNS"
3647        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3648            column_text = " COLUMN"
3649        else:
3650            column_text = ""
3651
3652        return f"ADD{column_text} {sql}"
3653
3654    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3655        expressions = self.expressions(expression)
3656        exists = " IF EXISTS " if expression.args.get("exists") else " "
3657        return f"DROP{exists}{expressions}"
3658
3659    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3660        return f"ADD {self.expressions(expression, indent=False)}"
3661
3662    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3663        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3664        location = self.sql(expression, "location")
3665        location = f" {location}" if location else ""
3666        return f"ADD {exists}{self.sql(expression.this)}{location}"
3667
3668    def distinct_sql(self, expression: exp.Distinct) -> str:
3669        this = self.expressions(expression, flat=True)
3670
3671        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3672            case = exp.case()
3673            for arg in expression.expressions:
3674                case = case.when(arg.is_(exp.null()), exp.null())
3675            this = self.sql(case.else_(f"({this})"))
3676
3677        this = f" {this}" if this else ""
3678
3679        on = self.sql(expression, "on")
3680        on = f" ON {on}" if on else ""
3681        return f"DISTINCT{this}{on}"
3682
3683    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3684        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
3685
3686    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3687        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
3688
3689    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3690        this_sql = self.sql(expression, "this")
3691        expression_sql = self.sql(expression, "expression")
3692        kind = "MAX" if expression.args.get("max") else "MIN"
3693        return f"{this_sql} HAVING {kind} {expression_sql}"
3694
3695    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3696        return self.sql(
3697            exp.Cast(
3698                this=exp.Div(this=expression.this, expression=expression.expression),
3699                to=exp.DataType(this=exp.DataType.Type.INT),
3700            )
3701        )
3702
3703    def dpipe_sql(self, expression: exp.DPipe) -> str:
3704        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3705            return self.func(
3706                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3707            )
3708        return self.binary(expression, "||")
3709
3710    def div_sql(self, expression: exp.Div) -> str:
3711        l, r = expression.left, expression.right
3712
3713        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3714            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3715
3716        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3717            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3718                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3719
3720        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3721            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3722                return self.sql(
3723                    exp.cast(
3724                        l / r,
3725                        to=exp.DataType.Type.BIGINT,
3726                    )
3727                )
3728
3729        return self.binary(expression, "/")
3730
3731    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3732        n = exp._wrap(expression.this, exp.Binary)
3733        d = exp._wrap(expression.expression, exp.Binary)
3734        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
3735
3736    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3737        return self.binary(expression, "OVERLAPS")
3738
3739    def distance_sql(self, expression: exp.Distance) -> str:
3740        return self.binary(expression, "<->")
3741
3742    def dot_sql(self, expression: exp.Dot) -> str:
3743        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
3744
3745    def eq_sql(self, expression: exp.EQ) -> str:
3746        return self.binary(expression, "=")
3747
3748    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3749        return self.binary(expression, ":=")
3750
3751    def escape_sql(self, expression: exp.Escape) -> str:
3752        return self.binary(expression, "ESCAPE")
3753
3754    def glob_sql(self, expression: exp.Glob) -> str:
3755        return self.binary(expression, "GLOB")
3756
3757    def gt_sql(self, expression: exp.GT) -> str:
3758        return self.binary(expression, ">")
3759
3760    def gte_sql(self, expression: exp.GTE) -> str:
3761        return self.binary(expression, ">=")
3762
3763    def is_sql(self, expression: exp.Is) -> str:
3764        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
3765            return self.sql(
3766                expression.this if expression.expression.this else exp.not_(expression.this)
3767            )
3768        return self.binary(expression, "IS")
3769
3770    def _like_sql(self, expression: exp.Like | exp.ILike) -> str:
3771        this = expression.this
3772        rhs = expression.expression
3773
3774        if isinstance(expression, exp.Like):
3775            exp_class: t.Type[exp.Like | exp.ILike] = exp.Like
3776            op = "LIKE"
3777        else:
3778            exp_class = exp.ILike
3779            op = "ILIKE"
3780
3781        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
3782            exprs = rhs.this.unnest()
3783
3784            if isinstance(exprs, exp.Tuple):
3785                exprs = exprs.expressions
3786
3787            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
3788
3789            like_expr: exp.Expression = exp_class(this=this, expression=exprs[0])
3790            for expr in exprs[1:]:
3791                like_expr = connective(like_expr, exp_class(this=this, expression=expr))
3792
3793            return self.sql(like_expr)
3794
3795        return self.binary(expression, op)
3796
3797    def like_sql(self, expression: exp.Like) -> str:
3798        return self._like_sql(expression)
3799
3800    def ilike_sql(self, expression: exp.ILike) -> str:
3801        return self._like_sql(expression)
3802
3803    def similarto_sql(self, expression: exp.SimilarTo) -> str:
3804        return self.binary(expression, "SIMILAR TO")
3805
3806    def lt_sql(self, expression: exp.LT) -> str:
3807        return self.binary(expression, "<")
3808
3809    def lte_sql(self, expression: exp.LTE) -> str:
3810        return self.binary(expression, "<=")
3811
3812    def mod_sql(self, expression: exp.Mod) -> str:
3813        return self.binary(expression, "%")
3814
3815    def mul_sql(self, expression: exp.Mul) -> str:
3816        return self.binary(expression, "*")
3817
3818    def neq_sql(self, expression: exp.NEQ) -> str:
3819        return self.binary(expression, "<>")
3820
3821    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
3822        return self.binary(expression, "IS NOT DISTINCT FROM")
3823
3824    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3825        return self.binary(expression, "IS DISTINCT FROM")
3826
3827    def slice_sql(self, expression: exp.Slice) -> str:
3828        return self.binary(expression, ":")
3829
3830    def sub_sql(self, expression: exp.Sub) -> str:
3831        return self.binary(expression, "-")
3832
3833    def trycast_sql(self, expression: exp.TryCast) -> str:
3834        return self.cast_sql(expression, safe_prefix="TRY_")
3835
3836    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
3837        return self.cast_sql(expression)
3838
3839    def try_sql(self, expression: exp.Try) -> str:
3840        if not self.TRY_SUPPORTED:
3841            self.unsupported("Unsupported TRY function")
3842            return self.sql(expression, "this")
3843
3844        return self.func("TRY", expression.this)
3845
3846    def log_sql(self, expression: exp.Log) -> str:
3847        this = expression.this
3848        expr = expression.expression
3849
3850        if self.dialect.LOG_BASE_FIRST is False:
3851            this, expr = expr, this
3852        elif self.dialect.LOG_BASE_FIRST is None and expr:
3853            if this.name in ("2", "10"):
3854                return self.func(f"LOG{this.name}", expr)
3855
3856            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
3857
3858        return self.func("LOG", this, expr)
3859
3860    def use_sql(self, expression: exp.Use) -> str:
3861        kind = self.sql(expression, "kind")
3862        kind = f" {kind}" if kind else ""
3863        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
3864        this = f" {this}" if this else ""
3865        return f"USE{kind}{this}"
3866
3867    def binary(self, expression: exp.Binary, op: str) -> str:
3868        sqls: t.List[str] = []
3869        stack: t.List[t.Union[str, exp.Expression]] = [expression]
3870        binary_type = type(expression)
3871
3872        while stack:
3873            node = stack.pop()
3874
3875            if type(node) is binary_type:
3876                op_func = node.args.get("operator")
3877                if op_func:
3878                    op = f"OPERATOR({self.sql(op_func)})"
3879
3880                stack.append(node.right)
3881                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
3882                stack.append(node.left)
3883            else:
3884                sqls.append(self.sql(node))
3885
3886        return "".join(sqls)
3887
3888    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
3889        to_clause = self.sql(expression, "to")
3890        if to_clause:
3891            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
3892
3893        return self.function_fallback_sql(expression)
3894
3895    def function_fallback_sql(self, expression: exp.Func) -> str:
3896        args = []
3897
3898        for key in expression.arg_types:
3899            arg_value = expression.args.get(key)
3900
3901            if isinstance(arg_value, list):
3902                for value in arg_value:
3903                    args.append(value)
3904            elif arg_value is not None:
3905                args.append(arg_value)
3906
3907        if self.dialect.PRESERVE_ORIGINAL_NAMES:
3908            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
3909        else:
3910            name = expression.sql_name()
3911
3912        return self.func(name, *args)
3913
3914    def func(
3915        self,
3916        name: str,
3917        *args: t.Optional[exp.Expression | str],
3918        prefix: str = "(",
3919        suffix: str = ")",
3920        normalize: bool = True,
3921    ) -> str:
3922        name = self.normalize_func(name) if normalize else name
3923        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
3924
3925    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
3926        arg_sqls = tuple(
3927            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
3928        )
3929        if self.pretty and self.too_wide(arg_sqls):
3930            return self.indent(
3931                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
3932            )
3933        return sep.join(arg_sqls)
3934
3935    def too_wide(self, args: t.Iterable) -> bool:
3936        return sum(len(arg) for arg in args) > self.max_text_width
3937
3938    def format_time(
3939        self,
3940        expression: exp.Expression,
3941        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
3942        inverse_time_trie: t.Optional[t.Dict] = None,
3943    ) -> t.Optional[str]:
3944        return format_time(
3945            self.sql(expression, "format"),
3946            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
3947            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
3948        )
3949
3950    def expressions(
3951        self,
3952        expression: t.Optional[exp.Expression] = None,
3953        key: t.Optional[str] = None,
3954        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
3955        flat: bool = False,
3956        indent: bool = True,
3957        skip_first: bool = False,
3958        skip_last: bool = False,
3959        sep: str = ", ",
3960        prefix: str = "",
3961        dynamic: bool = False,
3962        new_line: bool = False,
3963    ) -> str:
3964        expressions = expression.args.get(key or "expressions") if expression else sqls
3965
3966        if not expressions:
3967            return ""
3968
3969        if flat:
3970            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
3971
3972        num_sqls = len(expressions)
3973        result_sqls = []
3974
3975        for i, e in enumerate(expressions):
3976            sql = self.sql(e, comment=False)
3977            if not sql:
3978                continue
3979
3980            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
3981
3982            if self.pretty:
3983                if self.leading_comma:
3984                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
3985                else:
3986                    result_sqls.append(
3987                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
3988                    )
3989            else:
3990                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
3991
3992        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
3993            if new_line:
3994                result_sqls.insert(0, "")
3995                result_sqls.append("")
3996            result_sql = "\n".join(s.rstrip() for s in result_sqls)
3997        else:
3998            result_sql = "".join(result_sqls)
3999
4000        return (
4001            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4002            if indent
4003            else result_sql
4004        )
4005
4006    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
4007        flat = flat or isinstance(expression.parent, exp.Properties)
4008        expressions_sql = self.expressions(expression, flat=flat)
4009        if flat:
4010            return f"{op} {expressions_sql}"
4011        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4012
4013    def naked_property(self, expression: exp.Property) -> str:
4014        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4015        if not property_name:
4016            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4017        return f"{property_name} {self.sql(expression, 'this')}"
4018
4019    def tag_sql(self, expression: exp.Tag) -> str:
4020        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4021
4022    def token_sql(self, token_type: TokenType) -> str:
4023        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4024
4025    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4026        this = self.sql(expression, "this")
4027        expressions = self.no_identify(self.expressions, expression)
4028        expressions = (
4029            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4030        )
4031        return f"{this}{expressions}" if expressions.strip() != "" else this
4032
4033    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4034        this = self.sql(expression, "this")
4035        expressions = self.expressions(expression, flat=True)
4036        return f"{this}({expressions})"
4037
4038    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4039        return self.binary(expression, "=>")
4040
4041    def when_sql(self, expression: exp.When) -> str:
4042        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4043        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4044        condition = self.sql(expression, "condition")
4045        condition = f" AND {condition}" if condition else ""
4046
4047        then_expression = expression.args.get("then")
4048        if isinstance(then_expression, exp.Insert):
4049            this = self.sql(then_expression, "this")
4050            this = f"INSERT {this}" if this else "INSERT"
4051            then = self.sql(then_expression, "expression")
4052            then = f"{this} VALUES {then}" if then else this
4053        elif isinstance(then_expression, exp.Update):
4054            if isinstance(then_expression.args.get("expressions"), exp.Star):
4055                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4056            else:
4057                then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}"
4058        else:
4059            then = self.sql(then_expression)
4060        return f"WHEN {matched}{source}{condition} THEN {then}"
4061
4062    def whens_sql(self, expression: exp.Whens) -> str:
4063        return self.expressions(expression, sep=" ", indent=False)
4064
4065    def merge_sql(self, expression: exp.Merge) -> str:
4066        table = expression.this
4067        table_alias = ""
4068
4069        hints = table.args.get("hints")
4070        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4071            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4072            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4073
4074        this = self.sql(table)
4075        using = f"USING {self.sql(expression, 'using')}"
4076        on = f"ON {self.sql(expression, 'on')}"
4077        whens = self.sql(expression, "whens")
4078
4079        returning = self.sql(expression, "returning")
4080        if returning:
4081            whens = f"{whens}{returning}"
4082
4083        sep = self.sep()
4084
4085        return self.prepend_ctes(
4086            expression,
4087            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4088        )
4089
4090    @unsupported_args("format")
4091    def tochar_sql(self, expression: exp.ToChar) -> str:
4092        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
4093
4094    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4095        if not self.SUPPORTS_TO_NUMBER:
4096            self.unsupported("Unsupported TO_NUMBER function")
4097            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4098
4099        fmt = expression.args.get("format")
4100        if not fmt:
4101            self.unsupported("Conversion format is required for TO_NUMBER")
4102            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4103
4104        return self.func("TO_NUMBER", expression.this, fmt)
4105
4106    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4107        this = self.sql(expression, "this")
4108        kind = self.sql(expression, "kind")
4109        settings_sql = self.expressions(expression, key="settings", sep=" ")
4110        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4111        return f"{this}({kind}{args})"
4112
4113    def dictrange_sql(self, expression: exp.DictRange) -> str:
4114        this = self.sql(expression, "this")
4115        max = self.sql(expression, "max")
4116        min = self.sql(expression, "min")
4117        return f"{this}(MIN {min} MAX {max})"
4118
4119    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4120        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4121
4122    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4123        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4124
4125    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4126    def uniquekeyproperty_sql(
4127        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4128    ) -> str:
4129        return f"{prefix} ({self.expressions(expression, flat=True)})"
4130
4131    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4132    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4133        expressions = self.expressions(expression, flat=True)
4134        expressions = f" {self.wrap(expressions)}" if expressions else ""
4135        buckets = self.sql(expression, "buckets")
4136        kind = self.sql(expression, "kind")
4137        buckets = f" BUCKETS {buckets}" if buckets else ""
4138        order = self.sql(expression, "order")
4139        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4140
4141    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4142        return ""
4143
4144    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4145        expressions = self.expressions(expression, key="expressions", flat=True)
4146        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4147        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4148        buckets = self.sql(expression, "buckets")
4149        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4150
4151    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4152        this = self.sql(expression, "this")
4153        having = self.sql(expression, "having")
4154
4155        if having:
4156            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4157
4158        return self.func("ANY_VALUE", this)
4159
4160    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4161        transform = self.func("TRANSFORM", *expression.expressions)
4162        row_format_before = self.sql(expression, "row_format_before")
4163        row_format_before = f" {row_format_before}" if row_format_before else ""
4164        record_writer = self.sql(expression, "record_writer")
4165        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4166        using = f" USING {self.sql(expression, 'command_script')}"
4167        schema = self.sql(expression, "schema")
4168        schema = f" AS {schema}" if schema else ""
4169        row_format_after = self.sql(expression, "row_format_after")
4170        row_format_after = f" {row_format_after}" if row_format_after else ""
4171        record_reader = self.sql(expression, "record_reader")
4172        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4173        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4174
4175    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4176        key_block_size = self.sql(expression, "key_block_size")
4177        if key_block_size:
4178            return f"KEY_BLOCK_SIZE = {key_block_size}"
4179
4180        using = self.sql(expression, "using")
4181        if using:
4182            return f"USING {using}"
4183
4184        parser = self.sql(expression, "parser")
4185        if parser:
4186            return f"WITH PARSER {parser}"
4187
4188        comment = self.sql(expression, "comment")
4189        if comment:
4190            return f"COMMENT {comment}"
4191
4192        visible = expression.args.get("visible")
4193        if visible is not None:
4194            return "VISIBLE" if visible else "INVISIBLE"
4195
4196        engine_attr = self.sql(expression, "engine_attr")
4197        if engine_attr:
4198            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4199
4200        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4201        if secondary_engine_attr:
4202            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4203
4204        self.unsupported("Unsupported index constraint option.")
4205        return ""
4206
4207    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4208        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4209        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4210
4211    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4212        kind = self.sql(expression, "kind")
4213        kind = f"{kind} INDEX" if kind else "INDEX"
4214        this = self.sql(expression, "this")
4215        this = f" {this}" if this else ""
4216        index_type = self.sql(expression, "index_type")
4217        index_type = f" USING {index_type}" if index_type else ""
4218        expressions = self.expressions(expression, flat=True)
4219        expressions = f" ({expressions})" if expressions else ""
4220        options = self.expressions(expression, key="options", sep=" ")
4221        options = f" {options}" if options else ""
4222        return f"{kind}{this}{index_type}{expressions}{options}"
4223
4224    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4225        if self.NVL2_SUPPORTED:
4226            return self.function_fallback_sql(expression)
4227
4228        case = exp.Case().when(
4229            expression.this.is_(exp.null()).not_(copy=False),
4230            expression.args["true"],
4231            copy=False,
4232        )
4233        else_cond = expression.args.get("false")
4234        if else_cond:
4235            case.else_(else_cond, copy=False)
4236
4237        return self.sql(case)
4238
4239    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4240        this = self.sql(expression, "this")
4241        expr = self.sql(expression, "expression")
4242        iterator = self.sql(expression, "iterator")
4243        condition = self.sql(expression, "condition")
4244        condition = f" IF {condition}" if condition else ""
4245        return f"{this} FOR {expr} IN {iterator}{condition}"
4246
4247    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4248        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4249
4250    def opclass_sql(self, expression: exp.Opclass) -> str:
4251        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4252
4253    def _ml_sql(self, expression: exp.Func, name: str) -> str:
4254        model = self.sql(expression, "this")
4255        model = f"MODEL {model}"
4256        expr = expression.expression
4257        if expr:
4258            expr_sql = self.sql(expression, "expression")
4259            expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql
4260        else:
4261            expr_sql = None
4262
4263        parameters = self.sql(expression, "params_struct") or None
4264
4265        return self.func(name, model, expr_sql, parameters)
4266
4267    def predict_sql(self, expression: exp.Predict) -> str:
4268        return self._ml_sql(expression, "PREDICT")
4269
4270    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4271        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4272        return self._ml_sql(expression, name)
4273
4274    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
4275        return self._ml_sql(expression, "TRANSLATE")
4276
4277    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
4278        return self._ml_sql(expression, "FORECAST")
4279
4280    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4281        this_sql = self.sql(expression, "this")
4282        if isinstance(expression.this, exp.Table):
4283            this_sql = f"TABLE {this_sql}"
4284
4285        return self.func(
4286            "FEATURES_AT_TIME",
4287            this_sql,
4288            expression.args.get("time"),
4289            expression.args.get("num_rows"),
4290            expression.args.get("ignore_feature_nulls"),
4291        )
4292
4293    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
4294        this_sql = self.sql(expression, "this")
4295        if isinstance(expression.this, exp.Table):
4296            this_sql = f"TABLE {this_sql}"
4297
4298        query_table = self.sql(expression, "query_table")
4299        if isinstance(expression.args["query_table"], exp.Table):
4300            query_table = f"TABLE {query_table}"
4301
4302        return self.func(
4303            "VECTOR_SEARCH",
4304            this_sql,
4305            expression.args.get("column_to_search"),
4306            query_table,
4307            expression.args.get("query_column_to_search"),
4308            expression.args.get("top_k"),
4309            expression.args.get("distance_type"),
4310            expression.args.get("options"),
4311        )
4312
4313    def forin_sql(self, expression: exp.ForIn) -> str:
4314        this = self.sql(expression, "this")
4315        expression_sql = self.sql(expression, "expression")
4316        return f"FOR {this} DO {expression_sql}"
4317
4318    def refresh_sql(self, expression: exp.Refresh) -> str:
4319        this = self.sql(expression, "this")
4320        table = "" if isinstance(expression.this, exp.Literal) else "TABLE "
4321        return f"REFRESH {table}{this}"
4322
4323    def toarray_sql(self, expression: exp.ToArray) -> str:
4324        arg = expression.this
4325        if not arg.type:
4326            from sqlglot.optimizer.annotate_types import annotate_types
4327
4328            arg = annotate_types(arg, dialect=self.dialect)
4329
4330        if arg.is_type(exp.DataType.Type.ARRAY):
4331            return self.sql(arg)
4332
4333        cond_for_null = arg.is_(exp.null())
4334        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4335
4336    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4337        this = expression.this
4338        time_format = self.format_time(expression)
4339
4340        if time_format:
4341            return self.sql(
4342                exp.cast(
4343                    exp.StrToTime(this=this, format=expression.args["format"]),
4344                    exp.DataType.Type.TIME,
4345                )
4346            )
4347
4348        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4349            return self.sql(this)
4350
4351        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4352
4353    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4354        this = expression.this
4355        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4356            return self.sql(this)
4357
4358        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4359
4360    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4361        this = expression.this
4362        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4363            return self.sql(this)
4364
4365        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4366
4367    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4368        this = expression.this
4369        time_format = self.format_time(expression)
4370
4371        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4372            return self.sql(
4373                exp.cast(
4374                    exp.StrToTime(this=this, format=expression.args["format"]),
4375                    exp.DataType.Type.DATE,
4376                )
4377            )
4378
4379        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4380            return self.sql(this)
4381
4382        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4383
4384    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4385        return self.sql(
4386            exp.func(
4387                "DATEDIFF",
4388                expression.this,
4389                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4390                "day",
4391            )
4392        )
4393
4394    def lastday_sql(self, expression: exp.LastDay) -> str:
4395        if self.LAST_DAY_SUPPORTS_DATE_PART:
4396            return self.function_fallback_sql(expression)
4397
4398        unit = expression.text("unit")
4399        if unit and unit != "MONTH":
4400            self.unsupported("Date parts are not supported in LAST_DAY.")
4401
4402        return self.func("LAST_DAY", expression.this)
4403
4404    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4405        from sqlglot.dialects.dialect import unit_to_str
4406
4407        return self.func(
4408            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4409        )
4410
4411    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4412        if self.CAN_IMPLEMENT_ARRAY_ANY:
4413            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4414            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4415            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4416            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4417
4418        from sqlglot.dialects import Dialect
4419
4420        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4421        if self.dialect.__class__ != Dialect:
4422            self.unsupported("ARRAY_ANY is unsupported")
4423
4424        return self.function_fallback_sql(expression)
4425
4426    def struct_sql(self, expression: exp.Struct) -> str:
4427        expression.set(
4428            "expressions",
4429            [
4430                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4431                if isinstance(e, exp.PropertyEQ)
4432                else e
4433                for e in expression.expressions
4434            ],
4435        )
4436
4437        return self.function_fallback_sql(expression)
4438
4439    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4440        low = self.sql(expression, "this")
4441        high = self.sql(expression, "expression")
4442
4443        return f"{low} TO {high}"
4444
4445    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4446        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4447        tables = f" {self.expressions(expression)}"
4448
4449        exists = " IF EXISTS" if expression.args.get("exists") else ""
4450
4451        on_cluster = self.sql(expression, "cluster")
4452        on_cluster = f" {on_cluster}" if on_cluster else ""
4453
4454        identity = self.sql(expression, "identity")
4455        identity = f" {identity} IDENTITY" if identity else ""
4456
4457        option = self.sql(expression, "option")
4458        option = f" {option}" if option else ""
4459
4460        partition = self.sql(expression, "partition")
4461        partition = f" {partition}" if partition else ""
4462
4463        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4464
4465    # This transpiles T-SQL's CONVERT function
4466    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4467    def convert_sql(self, expression: exp.Convert) -> str:
4468        to = expression.this
4469        value = expression.expression
4470        style = expression.args.get("style")
4471        safe = expression.args.get("safe")
4472        strict = expression.args.get("strict")
4473
4474        if not to or not value:
4475            return ""
4476
4477        # Retrieve length of datatype and override to default if not specified
4478        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4479            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4480
4481        transformed: t.Optional[exp.Expression] = None
4482        cast = exp.Cast if strict else exp.TryCast
4483
4484        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4485        if isinstance(style, exp.Literal) and style.is_int:
4486            from sqlglot.dialects.tsql import TSQL
4487
4488            style_value = style.name
4489            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4490            if not converted_style:
4491                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4492
4493            fmt = exp.Literal.string(converted_style)
4494
4495            if to.this == exp.DataType.Type.DATE:
4496                transformed = exp.StrToDate(this=value, format=fmt)
4497            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4498                transformed = exp.StrToTime(this=value, format=fmt)
4499            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4500                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4501            elif to.this == exp.DataType.Type.TEXT:
4502                transformed = exp.TimeToStr(this=value, format=fmt)
4503
4504        if not transformed:
4505            transformed = cast(this=value, to=to, safe=safe)
4506
4507        return self.sql(transformed)
4508
4509    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4510        this = expression.this
4511        if isinstance(this, exp.JSONPathWildcard):
4512            this = self.json_path_part(this)
4513            return f".{this}" if this else ""
4514
4515        if self.SAFE_JSON_PATH_KEY_RE.match(this):
4516            return f".{this}"
4517
4518        this = self.json_path_part(this)
4519        return (
4520            f"[{this}]"
4521            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
4522            else f".{this}"
4523        )
4524
4525    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
4526        this = self.json_path_part(expression.this)
4527        return f"[{this}]" if this else ""
4528
4529    def _simplify_unless_literal(self, expression: E) -> E:
4530        if not isinstance(expression, exp.Literal):
4531            from sqlglot.optimizer.simplify import simplify
4532
4533            expression = simplify(expression, dialect=self.dialect)
4534
4535        return expression
4536
4537    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
4538        this = expression.this
4539        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
4540            self.unsupported(
4541                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
4542            )
4543            return self.sql(this)
4544
4545        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
4546            # The first modifier here will be the one closest to the AggFunc's arg
4547            mods = sorted(
4548                expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
4549                key=lambda x: 0
4550                if isinstance(x, exp.HavingMax)
4551                else (1 if isinstance(x, exp.Order) else 2),
4552            )
4553
4554            if mods:
4555                mod = mods[0]
4556                this = expression.__class__(this=mod.this.copy())
4557                this.meta["inline"] = True
4558                mod.this.replace(this)
4559                return self.sql(expression.this)
4560
4561            agg_func = expression.find(exp.AggFunc)
4562
4563            if agg_func:
4564                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
4565                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
4566
4567        return f"{self.sql(expression, 'this')} {text}"
4568
4569    def _replace_line_breaks(self, string: str) -> str:
4570        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
4571        if self.pretty:
4572            return string.replace("\n", self.SENTINEL_LINE_BREAK)
4573        return string
4574
4575    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4576        option = self.sql(expression, "this")
4577
4578        if expression.expressions:
4579            upper = option.upper()
4580
4581            # Snowflake FILE_FORMAT options are separated by whitespace
4582            sep = " " if upper == "FILE_FORMAT" else ", "
4583
4584            # Databricks copy/format options do not set their list of values with EQ
4585            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4586            values = self.expressions(expression, flat=True, sep=sep)
4587            return f"{option}{op}({values})"
4588
4589        value = self.sql(expression, "expression")
4590
4591        if not value:
4592            return option
4593
4594        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4595
4596        return f"{option}{op}{value}"
4597
4598    def credentials_sql(self, expression: exp.Credentials) -> str:
4599        cred_expr = expression.args.get("credentials")
4600        if isinstance(cred_expr, exp.Literal):
4601            # Redshift case: CREDENTIALS <string>
4602            credentials = self.sql(expression, "credentials")
4603            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4604        else:
4605            # Snowflake case: CREDENTIALS = (...)
4606            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4607            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4608
4609        storage = self.sql(expression, "storage")
4610        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4611
4612        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4613        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4614
4615        iam_role = self.sql(expression, "iam_role")
4616        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4617
4618        region = self.sql(expression, "region")
4619        region = f" REGION {region}" if region else ""
4620
4621        return f"{credentials}{storage}{encryption}{iam_role}{region}"
4622
4623    def copy_sql(self, expression: exp.Copy) -> str:
4624        this = self.sql(expression, "this")
4625        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4626
4627        credentials = self.sql(expression, "credentials")
4628        credentials = self.seg(credentials) if credentials else ""
4629        files = self.expressions(expression, key="files", flat=True)
4630        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
4631
4632        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4633        params = self.expressions(
4634            expression,
4635            key="params",
4636            sep=sep,
4637            new_line=True,
4638            skip_last=True,
4639            skip_first=True,
4640            indent=self.COPY_PARAMS_ARE_WRAPPED,
4641        )
4642
4643        if params:
4644            if self.COPY_PARAMS_ARE_WRAPPED:
4645                params = f" WITH ({params})"
4646            elif not self.pretty and (files or credentials):
4647                params = f" {params}"
4648
4649        return f"COPY{this}{kind} {files}{credentials}{params}"
4650
4651    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4652        return ""
4653
4654    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4655        on_sql = "ON" if expression.args.get("on") else "OFF"
4656        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4657        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4658        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4659        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4660
4661        if filter_col or retention_period:
4662            on_sql = self.func("ON", filter_col, retention_period)
4663
4664        return f"DATA_DELETION={on_sql}"
4665
4666    def maskingpolicycolumnconstraint_sql(
4667        self, expression: exp.MaskingPolicyColumnConstraint
4668    ) -> str:
4669        this = self.sql(expression, "this")
4670        expressions = self.expressions(expression, flat=True)
4671        expressions = f" USING ({expressions})" if expressions else ""
4672        return f"MASKING POLICY {this}{expressions}"
4673
4674    def gapfill_sql(self, expression: exp.GapFill) -> str:
4675        this = self.sql(expression, "this")
4676        this = f"TABLE {this}"
4677        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
4678
4679    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4680        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
4681
4682    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4683        this = self.sql(expression, "this")
4684        expr = expression.expression
4685
4686        if isinstance(expr, exp.Func):
4687            # T-SQL's CLR functions are case sensitive
4688            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4689        else:
4690            expr = self.sql(expression, "expression")
4691
4692        return self.scope_resolution(expr, this)
4693
4694    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4695        if self.PARSE_JSON_NAME is None:
4696            return self.sql(expression.this)
4697
4698        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
4699
4700    def rand_sql(self, expression: exp.Rand) -> str:
4701        lower = self.sql(expression, "lower")
4702        upper = self.sql(expression, "upper")
4703
4704        if lower and upper:
4705            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4706        return self.func("RAND", expression.this)
4707
4708    def changes_sql(self, expression: exp.Changes) -> str:
4709        information = self.sql(expression, "information")
4710        information = f"INFORMATION => {information}"
4711        at_before = self.sql(expression, "at_before")
4712        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4713        end = self.sql(expression, "end")
4714        end = f"{self.seg('')}{end}" if end else ""
4715
4716        return f"CHANGES ({information}){at_before}{end}"
4717
4718    def pad_sql(self, expression: exp.Pad) -> str:
4719        prefix = "L" if expression.args.get("is_left") else "R"
4720
4721        fill_pattern = self.sql(expression, "fill_pattern") or None
4722        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4723            fill_pattern = "' '"
4724
4725        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
4726
4727    def summarize_sql(self, expression: exp.Summarize) -> str:
4728        table = " TABLE" if expression.args.get("table") else ""
4729        return f"SUMMARIZE{table} {self.sql(expression.this)}"
4730
4731    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4732        generate_series = exp.GenerateSeries(**expression.args)
4733
4734        parent = expression.parent
4735        if isinstance(parent, (exp.Alias, exp.TableAlias)):
4736            parent = parent.parent
4737
4738        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
4739            return self.sql(exp.Unnest(expressions=[generate_series]))
4740
4741        if isinstance(parent, exp.Select):
4742            self.unsupported("GenerateSeries projection unnesting is not supported.")
4743
4744        return self.sql(generate_series)
4745
4746    def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str:
4747        exprs = expression.expressions
4748        if not self.ARRAY_CONCAT_IS_VAR_LEN:
4749            if len(exprs) == 0:
4750                rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[])
4751            else:
4752                rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs)
4753        else:
4754            rhs = self.expressions(expression)  # type: ignore
4755
4756        return self.func(name, expression.this, rhs or None)
4757
4758    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
4759        if self.SUPPORTS_CONVERT_TIMEZONE:
4760            return self.function_fallback_sql(expression)
4761
4762        source_tz = expression.args.get("source_tz")
4763        target_tz = expression.args.get("target_tz")
4764        timestamp = expression.args.get("timestamp")
4765
4766        if source_tz and timestamp:
4767            timestamp = exp.AtTimeZone(
4768                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
4769            )
4770
4771        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
4772
4773        return self.sql(expr)
4774
4775    def json_sql(self, expression: exp.JSON) -> str:
4776        this = self.sql(expression, "this")
4777        this = f" {this}" if this else ""
4778
4779        _with = expression.args.get("with")
4780
4781        if _with is None:
4782            with_sql = ""
4783        elif not _with:
4784            with_sql = " WITHOUT"
4785        else:
4786            with_sql = " WITH"
4787
4788        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
4789
4790        return f"JSON{this}{with_sql}{unique_sql}"
4791
4792    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
4793        def _generate_on_options(arg: t.Any) -> str:
4794            return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}"
4795
4796        path = self.sql(expression, "path")
4797        returning = self.sql(expression, "returning")
4798        returning = f" RETURNING {returning}" if returning else ""
4799
4800        on_condition = self.sql(expression, "on_condition")
4801        on_condition = f" {on_condition}" if on_condition else ""
4802
4803        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4804
4805    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
4806        else_ = "ELSE " if expression.args.get("else_") else ""
4807        condition = self.sql(expression, "expression")
4808        condition = f"WHEN {condition} THEN " if condition else else_
4809        insert = self.sql(expression, "this")[len("INSERT") :].strip()
4810        return f"{condition}{insert}"
4811
4812    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
4813        kind = self.sql(expression, "kind")
4814        expressions = self.seg(self.expressions(expression, sep=" "))
4815        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
4816        return res
4817
4818    def oncondition_sql(self, expression: exp.OnCondition) -> str:
4819        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
4820        empty = expression.args.get("empty")
4821        empty = (
4822            f"DEFAULT {empty} ON EMPTY"
4823            if isinstance(empty, exp.Expression)
4824            else self.sql(expression, "empty")
4825        )
4826
4827        error = expression.args.get("error")
4828        error = (
4829            f"DEFAULT {error} ON ERROR"
4830            if isinstance(error, exp.Expression)
4831            else self.sql(expression, "error")
4832        )
4833
4834        if error and empty:
4835            error = (
4836                f"{empty} {error}"
4837                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
4838                else f"{error} {empty}"
4839            )
4840            empty = ""
4841
4842        null = self.sql(expression, "null")
4843
4844        return f"{empty}{error}{null}"
4845
4846    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
4847        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
4848        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
4849
4850    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
4851        this = self.sql(expression, "this")
4852        path = self.sql(expression, "path")
4853
4854        passing = self.expressions(expression, "passing")
4855        passing = f" PASSING {passing}" if passing else ""
4856
4857        on_condition = self.sql(expression, "on_condition")
4858        on_condition = f" {on_condition}" if on_condition else ""
4859
4860        path = f"{path}{passing}{on_condition}"
4861
4862        return self.func("JSON_EXISTS", this, path)
4863
4864    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
4865        array_agg = self.function_fallback_sql(expression)
4866
4867        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
4868        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
4869        if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"):
4870            parent = expression.parent
4871            if isinstance(parent, exp.Filter):
4872                parent_cond = parent.expression.this
4873                parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_()))
4874            else:
4875                this = expression.this
4876                # Do not add the filter if the input is not a column (e.g. literal, struct etc)
4877                if this.find(exp.Column):
4878                    # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
4879                    this_sql = (
4880                        self.expressions(this)
4881                        if isinstance(this, exp.Distinct)
4882                        else self.sql(expression, "this")
4883                    )
4884
4885                    array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)"
4886
4887        return array_agg
4888
4889    def apply_sql(self, expression: exp.Apply) -> str:
4890        this = self.sql(expression, "this")
4891        expr = self.sql(expression, "expression")
4892
4893        return f"{this} APPLY({expr})"
4894
4895    def _grant_or_revoke_sql(
4896        self,
4897        expression: exp.Grant | exp.Revoke,
4898        keyword: str,
4899        preposition: str,
4900        grant_option_prefix: str = "",
4901        grant_option_suffix: str = "",
4902    ) -> str:
4903        privileges_sql = self.expressions(expression, key="privileges", flat=True)
4904
4905        kind = self.sql(expression, "kind")
4906        kind = f" {kind}" if kind else ""
4907
4908        securable = self.sql(expression, "securable")
4909        securable = f" {securable}" if securable else ""
4910
4911        principals = self.expressions(expression, key="principals", flat=True)
4912
4913        if not expression.args.get("grant_option"):
4914            grant_option_prefix = grant_option_suffix = ""
4915
4916        # cascade for revoke only
4917        cascade = self.sql(expression, "cascade")
4918        cascade = f" {cascade}" if cascade else ""
4919
4920        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
4921
4922    def grant_sql(self, expression: exp.Grant) -> str:
4923        return self._grant_or_revoke_sql(
4924            expression,
4925            keyword="GRANT",
4926            preposition="TO",
4927            grant_option_suffix=" WITH GRANT OPTION",
4928        )
4929
4930    def revoke_sql(self, expression: exp.Revoke) -> str:
4931        return self._grant_or_revoke_sql(
4932            expression,
4933            keyword="REVOKE",
4934            preposition="FROM",
4935            grant_option_prefix="GRANT OPTION FOR ",
4936        )
4937
4938    def grantprivilege_sql(self, expression: exp.GrantPrivilege):
4939        this = self.sql(expression, "this")
4940        columns = self.expressions(expression, flat=True)
4941        columns = f"({columns})" if columns else ""
4942
4943        return f"{this}{columns}"
4944
4945    def grantprincipal_sql(self, expression: exp.GrantPrincipal):
4946        this = self.sql(expression, "this")
4947
4948        kind = self.sql(expression, "kind")
4949        kind = f"{kind} " if kind else ""
4950
4951        return f"{kind}{this}"
4952
4953    def columns_sql(self, expression: exp.Columns):
4954        func = self.function_fallback_sql(expression)
4955        if expression.args.get("unpack"):
4956            func = f"*{func}"
4957
4958        return func
4959
4960    def overlay_sql(self, expression: exp.Overlay):
4961        this = self.sql(expression, "this")
4962        expr = self.sql(expression, "expression")
4963        from_sql = self.sql(expression, "from")
4964        for_sql = self.sql(expression, "for")
4965        for_sql = f" FOR {for_sql}" if for_sql else ""
4966
4967        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
4968
4969    @unsupported_args("format")
4970    def todouble_sql(self, expression: exp.ToDouble) -> str:
4971        return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4972
4973    def string_sql(self, expression: exp.String) -> str:
4974        this = expression.this
4975        zone = expression.args.get("zone")
4976
4977        if zone:
4978            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
4979            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
4980            # set for source_tz to transpile the time conversion before the STRING cast
4981            this = exp.ConvertTimezone(
4982                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
4983            )
4984
4985        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
4986
4987    def median_sql(self, expression: exp.Median):
4988        if not self.SUPPORTS_MEDIAN:
4989            return self.sql(
4990                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
4991            )
4992
4993        return self.function_fallback_sql(expression)
4994
4995    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
4996        filler = self.sql(expression, "this")
4997        filler = f" {filler}" if filler else ""
4998        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
4999        return f"TRUNCATE{filler} {with_count}"
5000
5001    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5002        if self.SUPPORTS_UNIX_SECONDS:
5003            return self.function_fallback_sql(expression)
5004
5005        start_ts = exp.cast(
5006            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
5007        )
5008
5009        return self.sql(
5010            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5011        )
5012
5013    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5014        dim = expression.expression
5015
5016        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5017        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5018            if not (dim.is_int and dim.name == "1"):
5019                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5020            dim = None
5021
5022        # If dimension is required but not specified, default initialize it
5023        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5024            dim = exp.Literal.number(1)
5025
5026        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5027
5028    def attach_sql(self, expression: exp.Attach) -> str:
5029        this = self.sql(expression, "this")
5030        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5031        expressions = self.expressions(expression)
5032        expressions = f" ({expressions})" if expressions else ""
5033
5034        return f"ATTACH{exists_sql} {this}{expressions}"
5035
5036    def detach_sql(self, expression: exp.Detach) -> str:
5037        this = self.sql(expression, "this")
5038        # the DATABASE keyword is required if IF EXISTS is set
5039        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
5040        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5041        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
5042
5043        return f"DETACH{exists_sql} {this}"
5044
5045    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5046        this = self.sql(expression, "this")
5047        value = self.sql(expression, "expression")
5048        value = f" {value}" if value else ""
5049        return f"{this}{value}"
5050
5051    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5052        return (
5053            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5054        )
5055
5056    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5057        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5058        encode = f"{encode} {self.sql(expression, 'this')}"
5059
5060        properties = expression.args.get("properties")
5061        if properties:
5062            encode = f"{encode} {self.properties(properties)}"
5063
5064        return encode
5065
5066    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5067        this = self.sql(expression, "this")
5068        include = f"INCLUDE {this}"
5069
5070        column_def = self.sql(expression, "column_def")
5071        if column_def:
5072            include = f"{include} {column_def}"
5073
5074        alias = self.sql(expression, "alias")
5075        if alias:
5076            include = f"{include} AS {alias}"
5077
5078        return include
5079
5080    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5081        name = f"NAME {self.sql(expression, 'this')}"
5082        return self.func("XMLELEMENT", name, *expression.expressions)
5083
5084    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5085        this = self.sql(expression, "this")
5086        expr = self.sql(expression, "expression")
5087        expr = f"({expr})" if expr else ""
5088        return f"{this}{expr}"
5089
5090    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5091        partitions = self.expressions(expression, "partition_expressions")
5092        create = self.expressions(expression, "create_expressions")
5093        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
5094
5095    def partitionbyrangepropertydynamic_sql(
5096        self, expression: exp.PartitionByRangePropertyDynamic
5097    ) -> str:
5098        start = self.sql(expression, "start")
5099        end = self.sql(expression, "end")
5100
5101        every = expression.args["every"]
5102        if isinstance(every, exp.Interval) and every.this.is_string:
5103            every.this.replace(exp.Literal.number(every.name))
5104
5105        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5106
5107    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5108        name = self.sql(expression, "this")
5109        values = self.expressions(expression, flat=True)
5110
5111        return f"NAME {name} VALUE {values}"
5112
5113    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5114        kind = self.sql(expression, "kind")
5115        sample = self.sql(expression, "sample")
5116        return f"SAMPLE {sample} {kind}"
5117
5118    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5119        kind = self.sql(expression, "kind")
5120        option = self.sql(expression, "option")
5121        option = f" {option}" if option else ""
5122        this = self.sql(expression, "this")
5123        this = f" {this}" if this else ""
5124        columns = self.expressions(expression)
5125        columns = f" {columns}" if columns else ""
5126        return f"{kind}{option} STATISTICS{this}{columns}"
5127
5128    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5129        this = self.sql(expression, "this")
5130        columns = self.expressions(expression)
5131        inner_expression = self.sql(expression, "expression")
5132        inner_expression = f" {inner_expression}" if inner_expression else ""
5133        update_options = self.sql(expression, "update_options")
5134        update_options = f" {update_options} UPDATE" if update_options else ""
5135        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
5136
5137    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5138        kind = self.sql(expression, "kind")
5139        kind = f" {kind}" if kind else ""
5140        return f"DELETE{kind} STATISTICS"
5141
5142    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5143        inner_expression = self.sql(expression, "expression")
5144        return f"LIST CHAINED ROWS{inner_expression}"
5145
5146    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5147        kind = self.sql(expression, "kind")
5148        this = self.sql(expression, "this")
5149        this = f" {this}" if this else ""
5150        inner_expression = self.sql(expression, "expression")
5151        return f"VALIDATE {kind}{this}{inner_expression}"
5152
5153    def analyze_sql(self, expression: exp.Analyze) -> str:
5154        options = self.expressions(expression, key="options", sep=" ")
5155        options = f" {options}" if options else ""
5156        kind = self.sql(expression, "kind")
5157        kind = f" {kind}" if kind else ""
5158        this = self.sql(expression, "this")
5159        this = f" {this}" if this else ""
5160        mode = self.sql(expression, "mode")
5161        mode = f" {mode}" if mode else ""
5162        properties = self.sql(expression, "properties")
5163        properties = f" {properties}" if properties else ""
5164        partition = self.sql(expression, "partition")
5165        partition = f" {partition}" if partition else ""
5166        inner_expression = self.sql(expression, "expression")
5167        inner_expression = f" {inner_expression}" if inner_expression else ""
5168        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5169
5170    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5171        this = self.sql(expression, "this")
5172        namespaces = self.expressions(expression, key="namespaces")
5173        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5174        passing = self.expressions(expression, key="passing")
5175        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5176        columns = self.expressions(expression, key="columns")
5177        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5178        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5179        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5180
5181    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5182        this = self.sql(expression, "this")
5183        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
5184
5185    def export_sql(self, expression: exp.Export) -> str:
5186        this = self.sql(expression, "this")
5187        connection = self.sql(expression, "connection")
5188        connection = f"WITH CONNECTION {connection} " if connection else ""
5189        options = self.sql(expression, "options")
5190        return f"EXPORT DATA {connection}{options} AS {this}"
5191
5192    def declare_sql(self, expression: exp.Declare) -> str:
5193        return f"DECLARE {self.expressions(expression, flat=True)}"
5194
5195    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5196        variable = self.sql(expression, "this")
5197        default = self.sql(expression, "default")
5198        default = f" = {default}" if default else ""
5199
5200        kind = self.sql(expression, "kind")
5201        if isinstance(expression.args.get("kind"), exp.Schema):
5202            kind = f"TABLE {kind}"
5203
5204        return f"{variable} AS {kind}{default}"
5205
5206    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5207        kind = self.sql(expression, "kind")
5208        this = self.sql(expression, "this")
5209        set = self.sql(expression, "expression")
5210        using = self.sql(expression, "using")
5211        using = f" USING {using}" if using else ""
5212
5213        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5214
5215        return f"{kind_sql} {this} SET {set}{using}"
5216
5217    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5218        params = self.expressions(expression, key="params", flat=True)
5219        return self.func(expression.name, *expression.expressions) + f"({params})"
5220
5221    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5222        return self.func(expression.name, *expression.expressions)
5223
5224    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5225        return self.anonymousaggfunc_sql(expression)
5226
5227    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5228        return self.parameterizedagg_sql(expression)
5229
5230    def show_sql(self, expression: exp.Show) -> str:
5231        self.unsupported("Unsupported SHOW statement")
5232        return ""
5233
5234    def install_sql(self, expression: exp.Install) -> str:
5235        self.unsupported("Unsupported INSTALL statement")
5236        return ""
5237
5238    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5239        # Snowflake GET/PUT statements:
5240        #   PUT <file> <internalStage> <properties>
5241        #   GET <internalStage> <file> <properties>
5242        props = expression.args.get("properties")
5243        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5244        this = self.sql(expression, "this")
5245        target = self.sql(expression, "target")
5246
5247        if isinstance(expression, exp.Put):
5248            return f"PUT {this} {target}{props_sql}"
5249        else:
5250            return f"GET {target} {this}{props_sql}"
5251
5252    def translatecharacters_sql(self, expression: exp.TranslateCharacters):
5253        this = self.sql(expression, "this")
5254        expr = self.sql(expression, "expression")
5255        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5256        return f"TRANSLATE({this} USING {expr}{with_error})"
5257
5258    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5259        if self.SUPPORTS_DECODE_CASE:
5260            return self.func("DECODE", *expression.expressions)
5261
5262        expression, *expressions = expression.expressions
5263
5264        ifs = []
5265        for search, result in zip(expressions[::2], expressions[1::2]):
5266            if isinstance(search, exp.Literal):
5267                ifs.append(exp.If(this=expression.eq(search), true=result))
5268            elif isinstance(search, exp.Null):
5269                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5270            else:
5271                if isinstance(search, exp.Binary):
5272                    search = exp.paren(search)
5273
5274                cond = exp.or_(
5275                    expression.eq(search),
5276                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5277                    copy=False,
5278                )
5279                ifs.append(exp.If(this=cond, true=result))
5280
5281        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5282        return self.sql(case)
5283
5284    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5285        this = self.sql(expression, "this")
5286        this = self.seg(this, sep="")
5287        dimensions = self.expressions(
5288            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5289        )
5290        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5291        metrics = self.expressions(
5292            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5293        )
5294        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5295        where = self.sql(expression, "where")
5296        where = self.seg(f"WHERE {where}") if where else ""
5297        body = self.indent(this + metrics + dimensions + where, skip_first=True)
5298        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5299
5300    def getextract_sql(self, expression: exp.GetExtract) -> str:
5301        this = expression.this
5302        expr = expression.expression
5303
5304        if not this.type or not expression.type:
5305            from sqlglot.optimizer.annotate_types import annotate_types
5306
5307            this = annotate_types(this, dialect=self.dialect)
5308
5309        if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)):
5310            return self.sql(exp.Bracket(this=this, expressions=[expr]))
5311
5312        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
5313
5314    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
5315        return self.sql(
5316            exp.DateAdd(
5317                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
5318                expression=expression.this,
5319                unit=exp.var("DAY"),
5320            )
5321        )
5322
5323    def space_sql(self: Generator, expression: exp.Space) -> str:
5324        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
5325
5326    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
5327        return f"BUILD {self.sql(expression, 'this')}"
5328
5329    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
5330        method = self.sql(expression, "method")
5331        kind = expression.args.get("kind")
5332        if not kind:
5333            return f"REFRESH {method}"
5334
5335        every = self.sql(expression, "every")
5336        unit = self.sql(expression, "unit")
5337        every = f" EVERY {every} {unit}" if every else ""
5338        starts = self.sql(expression, "starts")
5339        starts = f" STARTS {starts}" if starts else ""
5340
5341        return f"REFRESH {method} ON {kind}{every}{starts}"
5342
5343    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
5344        self.unsupported("The model!attribute syntax is not supported")
5345        return ""
5346
5347    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
5348        return self.func("DIRECTORY", expression.this)
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.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 165        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 166        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 167        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 168        exp.LocationProperty: lambda self, e: self.naked_property(e),
 169        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 170        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 171        exp.NonClusteredColumnConstraint: lambda self,
 172        e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})",
 173        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 174        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 175        exp.OnCommitProperty: lambda _,
 176        e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS",
 177        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 178        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 179        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 180        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 181        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 182        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 183        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 184        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 185        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 186        exp.ProjectionPolicyColumnConstraint: lambda self,
 187        e: f"PROJECTION POLICY {self.sql(e, 'this')}",
 188        exp.Put: lambda self, e: self.get_put_sql(e),
 189        exp.RemoteWithConnectionModelProperty: lambda self,
 190        e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
 191        exp.ReturnsProperty: lambda self, e: (
 192            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 193        ),
 194        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 195        exp.SecureProperty: lambda *_: "SECURE",
 196        exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}",
 197        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 198        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 199        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 200        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 201        exp.SqlReadWriteProperty: lambda _, e: e.name,
 202        exp.SqlSecurityProperty: lambda _,
 203        e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}",
 204        exp.StabilityProperty: lambda _, e: e.name,
 205        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 206        exp.StreamingTableProperty: lambda *_: "STREAMING",
 207        exp.StrictProperty: lambda *_: "STRICT",
 208        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 209        exp.TableColumn: lambda self, e: self.sql(e.this),
 210        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 211        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 212        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 213        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 214        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 215        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 216        exp.TransientProperty: lambda *_: "TRANSIENT",
 217        exp.Union: lambda self, e: self.set_operations(e),
 218        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 219        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 220        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 221        exp.Uuid: lambda *_: "UUID()",
 222        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 223        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 224        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 225        exp.UtcTimestamp: lambda self, e: self.sql(
 226            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 227        ),
 228        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 229        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 230        exp.VolatileProperty: lambda *_: "VOLATILE",
 231        exp.WeekStart: lambda self, e: f"WEEK({self.sql(e, 'this')})",
 232        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 233        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 234        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 235        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 236        exp.ForceProperty: lambda *_: "FORCE",
 237    }
 238
 239    # Whether null ordering is supported in order by
 240    # True: Full Support, None: No support, False: No support for certain cases
 241    # such as window specifications, aggregate functions etc
 242    NULL_ORDERING_SUPPORTED: t.Optional[bool] = True
 243
 244    # Whether ignore nulls is inside the agg or outside.
 245    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 246    IGNORE_NULLS_IN_FUNC = False
 247
 248    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 249    LOCKING_READS_SUPPORTED = False
 250
 251    # Whether the EXCEPT and INTERSECT operations can return duplicates
 252    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 253
 254    # Wrap derived values in parens, usually standard but spark doesn't support it
 255    WRAP_DERIVED_VALUES = True
 256
 257    # Whether create function uses an AS before the RETURN
 258    CREATE_FUNCTION_RETURN_AS = True
 259
 260    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 261    MATCHED_BY_SOURCE = True
 262
 263    # Whether the INTERVAL expression works only with values like '1 day'
 264    SINGLE_STRING_INTERVAL = False
 265
 266    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 267    INTERVAL_ALLOWS_PLURAL_FORM = True
 268
 269    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 270    LIMIT_FETCH = "ALL"
 271
 272    # Whether limit and fetch allows expresions or just limits
 273    LIMIT_ONLY_LITERALS = False
 274
 275    # Whether a table is allowed to be renamed with a db
 276    RENAME_TABLE_WITH_DB = True
 277
 278    # The separator for grouping sets and rollups
 279    GROUPINGS_SEP = ","
 280
 281    # The string used for creating an index on a table
 282    INDEX_ON = "ON"
 283
 284    # Whether join hints should be generated
 285    JOIN_HINTS = True
 286
 287    # Whether table hints should be generated
 288    TABLE_HINTS = True
 289
 290    # Whether query hints should be generated
 291    QUERY_HINTS = True
 292
 293    # What kind of separator to use for query hints
 294    QUERY_HINT_SEP = ", "
 295
 296    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 297    IS_BOOL_ALLOWED = True
 298
 299    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 300    DUPLICATE_KEY_UPDATE_WITH_SET = True
 301
 302    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 303    LIMIT_IS_TOP = False
 304
 305    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 306    RETURNING_END = True
 307
 308    # Whether to generate an unquoted value for EXTRACT's date part argument
 309    EXTRACT_ALLOWS_QUOTES = True
 310
 311    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 312    TZ_TO_WITH_TIME_ZONE = False
 313
 314    # Whether the NVL2 function is supported
 315    NVL2_SUPPORTED = True
 316
 317    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 318    SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE")
 319
 320    # Whether VALUES statements can be used as derived tables.
 321    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 322    # SELECT * VALUES into SELECT UNION
 323    VALUES_AS_TABLE = True
 324
 325    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 326    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 327
 328    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 329    UNNEST_WITH_ORDINALITY = True
 330
 331    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 332    AGGREGATE_FILTER_SUPPORTED = True
 333
 334    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 335    SEMI_ANTI_JOIN_WITH_SIDE = True
 336
 337    # Whether to include the type of a computed column in the CREATE DDL
 338    COMPUTED_COLUMN_WITH_TYPE = True
 339
 340    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 341    SUPPORTS_TABLE_COPY = True
 342
 343    # Whether parentheses are required around the table sample's expression
 344    TABLESAMPLE_REQUIRES_PARENS = True
 345
 346    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 347    TABLESAMPLE_SIZE_IS_ROWS = True
 348
 349    # The keyword(s) to use when generating a sample clause
 350    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 351
 352    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 353    TABLESAMPLE_WITH_METHOD = True
 354
 355    # The keyword to use when specifying the seed of a sample clause
 356    TABLESAMPLE_SEED_KEYWORD = "SEED"
 357
 358    # Whether COLLATE is a function instead of a binary operator
 359    COLLATE_IS_FUNC = False
 360
 361    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 362    DATA_TYPE_SPECIFIERS_ALLOWED = False
 363
 364    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 365    ENSURE_BOOLS = False
 366
 367    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 368    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 369
 370    # Whether CONCAT requires >1 arguments
 371    SUPPORTS_SINGLE_ARG_CONCAT = True
 372
 373    # Whether LAST_DAY function supports a date part argument
 374    LAST_DAY_SUPPORTS_DATE_PART = True
 375
 376    # Whether named columns are allowed in table aliases
 377    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 378
 379    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 380    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 381
 382    # What delimiter to use for separating JSON key/value pairs
 383    JSON_KEY_VALUE_PAIR_SEP = ":"
 384
 385    # INSERT OVERWRITE TABLE x override
 386    INSERT_OVERWRITE = " OVERWRITE TABLE"
 387
 388    # Whether the SELECT .. INTO syntax is used instead of CTAS
 389    SUPPORTS_SELECT_INTO = False
 390
 391    # Whether UNLOGGED tables can be created
 392    SUPPORTS_UNLOGGED_TABLES = False
 393
 394    # Whether the CREATE TABLE LIKE statement is supported
 395    SUPPORTS_CREATE_TABLE_LIKE = True
 396
 397    # Whether the LikeProperty needs to be specified inside of the schema clause
 398    LIKE_PROPERTY_INSIDE_SCHEMA = False
 399
 400    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 401    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 402    MULTI_ARG_DISTINCT = True
 403
 404    # Whether the JSON extraction operators expect a value of type JSON
 405    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 406
 407    # Whether bracketed keys like ["foo"] are supported in JSON paths
 408    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 409
 410    # Whether to escape keys using single quotes in JSON paths
 411    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 412
 413    # The JSONPathPart expressions supported by this dialect
 414    SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy()
 415
 416    # Whether any(f(x) for x in array) can be implemented by this dialect
 417    CAN_IMPLEMENT_ARRAY_ANY = False
 418
 419    # Whether the function TO_NUMBER is supported
 420    SUPPORTS_TO_NUMBER = True
 421
 422    # Whether EXCLUDE in window specification is supported
 423    SUPPORTS_WINDOW_EXCLUDE = False
 424
 425    # Whether or not set op modifiers apply to the outer set op or select.
 426    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 427    # True means limit 1 happens after the set op, False means it it happens on y.
 428    SET_OP_MODIFIERS = True
 429
 430    # Whether parameters from COPY statement are wrapped in parentheses
 431    COPY_PARAMS_ARE_WRAPPED = True
 432
 433    # Whether values of params are set with "=" token or empty space
 434    COPY_PARAMS_EQ_REQUIRED = False
 435
 436    # Whether COPY statement has INTO keyword
 437    COPY_HAS_INTO_KEYWORD = True
 438
 439    # Whether the conditional TRY(expression) function is supported
 440    TRY_SUPPORTED = True
 441
 442    # Whether the UESCAPE syntax in unicode strings is supported
 443    SUPPORTS_UESCAPE = True
 444
 445    # Function used to replace escaped unicode codes in unicode strings
 446    UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None
 447
 448    # The keyword to use when generating a star projection with excluded columns
 449    STAR_EXCEPT = "EXCEPT"
 450
 451    # The HEX function name
 452    HEX_FUNC = "HEX"
 453
 454    # The keywords to use when prefixing & separating WITH based properties
 455    WITH_PROPERTIES_PREFIX = "WITH"
 456
 457    # Whether to quote the generated expression of exp.JsonPath
 458    QUOTE_JSON_PATH = True
 459
 460    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 461    PAD_FILL_PATTERN_IS_REQUIRED = False
 462
 463    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 464    SUPPORTS_EXPLODING_PROJECTIONS = True
 465
 466    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 467    ARRAY_CONCAT_IS_VAR_LEN = True
 468
 469    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 470    SUPPORTS_CONVERT_TIMEZONE = False
 471
 472    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 473    SUPPORTS_MEDIAN = True
 474
 475    # Whether UNIX_SECONDS(timestamp) is supported
 476    SUPPORTS_UNIX_SECONDS = False
 477
 478    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 479    ALTER_SET_WRAPPED = False
 480
 481    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 482    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 483    # TODO: The normalization should be done by default once we've tested it across all dialects.
 484    NORMALIZE_EXTRACT_DATE_PARTS = False
 485
 486    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 487    PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
 488
 489    # The function name of the exp.ArraySize expression
 490    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 491
 492    # The syntax to use when altering the type of a column
 493    ALTER_SET_TYPE = "SET DATA TYPE"
 494
 495    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 496    # None -> Doesn't support it at all
 497    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 498    # True (Postgres) -> Explicitly requires it
 499    ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None
 500
 501    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 502    SUPPORTS_DECODE_CASE = True
 503
 504    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 505    SUPPORTS_BETWEEN_FLAGS = False
 506
 507    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 508    SUPPORTS_LIKE_QUANTIFIERS = True
 509
 510    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 511    MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None
 512
 513    TYPE_MAPPING = {
 514        exp.DataType.Type.DATETIME2: "TIMESTAMP",
 515        exp.DataType.Type.NCHAR: "CHAR",
 516        exp.DataType.Type.NVARCHAR: "VARCHAR",
 517        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 518        exp.DataType.Type.LONGTEXT: "TEXT",
 519        exp.DataType.Type.TINYTEXT: "TEXT",
 520        exp.DataType.Type.BLOB: "VARBINARY",
 521        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 522        exp.DataType.Type.LONGBLOB: "BLOB",
 523        exp.DataType.Type.TINYBLOB: "BLOB",
 524        exp.DataType.Type.INET: "INET",
 525        exp.DataType.Type.ROWVERSION: "VARBINARY",
 526        exp.DataType.Type.SMALLDATETIME: "TIMESTAMP",
 527    }
 528
 529    UNSUPPORTED_TYPES: set[exp.DataType.Type] = set()
 530
 531    TIME_PART_SINGULARS = {
 532        "MICROSECONDS": "MICROSECOND",
 533        "SECONDS": "SECOND",
 534        "MINUTES": "MINUTE",
 535        "HOURS": "HOUR",
 536        "DAYS": "DAY",
 537        "WEEKS": "WEEK",
 538        "MONTHS": "MONTH",
 539        "QUARTERS": "QUARTER",
 540        "YEARS": "YEAR",
 541    }
 542
 543    AFTER_HAVING_MODIFIER_TRANSFORMS = {
 544        "cluster": lambda self, e: self.sql(e, "cluster"),
 545        "distribute": lambda self, e: self.sql(e, "distribute"),
 546        "sort": lambda self, e: self.sql(e, "sort"),
 547        "windows": lambda self, e: (
 548            self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
 549            if e.args.get("windows")
 550            else ""
 551        ),
 552        "qualify": lambda self, e: self.sql(e, "qualify"),
 553    }
 554
 555    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 556
 557    STRUCT_DELIMITER = ("<", ">")
 558
 559    PARAMETER_TOKEN = "@"
 560    NAMED_PLACEHOLDER_TOKEN = ":"
 561
 562    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set()
 563
 564    PROPERTIES_LOCATION = {
 565        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 566        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 567        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 568        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 569        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 570        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 571        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 572        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 573        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 574        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 575        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 576        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 577        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 578        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 579        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 580        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 581        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 582        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 583        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 584        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 585        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 586        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 587        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 588        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 589        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 590        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 591        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 592        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 593        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 594        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 595        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 596        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 597        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 598        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 599        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 600        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 601        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 602        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 603        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 604        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 605        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 606        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 607        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 608        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 609        exp.LogProperty: exp.Properties.Location.POST_NAME,
 610        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 611        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 612        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 613        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 614        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 615        exp.Order: exp.Properties.Location.POST_SCHEMA,
 616        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 617        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 618        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 619        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 620        exp.Property: exp.Properties.Location.POST_WITH,
 621        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 622        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 623        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 624        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 625        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 626        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 627        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 628        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 629        exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA,
 630        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 631        exp.Set: exp.Properties.Location.POST_SCHEMA,
 632        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 633        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 634        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 635        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 636        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 637        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 638        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 639        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 640        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 641        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 642        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 643        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 644        exp.Tags: exp.Properties.Location.POST_WITH,
 645        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 646        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 647        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 648        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 649        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 650        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 651        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 652        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 653        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 654        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 655        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 656        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 657        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 658        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 659        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 660    }
 661
 662    # Keywords that can't be used as unquoted identifier names
 663    RESERVED_KEYWORDS: t.Set[str] = set()
 664
 665    # Expressions whose comments are separated from them for better formatting
 666    WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 667        exp.Command,
 668        exp.Create,
 669        exp.Describe,
 670        exp.Delete,
 671        exp.Drop,
 672        exp.From,
 673        exp.Insert,
 674        exp.Join,
 675        exp.MultitableInserts,
 676        exp.Order,
 677        exp.Group,
 678        exp.Having,
 679        exp.Select,
 680        exp.SetOperation,
 681        exp.Update,
 682        exp.Where,
 683        exp.With,
 684    )
 685
 686    # Expressions that should not have their comments generated in maybe_comment
 687    EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 688        exp.Binary,
 689        exp.SetOperation,
 690    )
 691
 692    # Expressions that can remain unwrapped when appearing in the context of an INTERVAL
 693    UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = (
 694        exp.Column,
 695        exp.Literal,
 696        exp.Neg,
 697        exp.Paren,
 698    )
 699
 700    PARAMETERIZABLE_TEXT_TYPES = {
 701        exp.DataType.Type.NVARCHAR,
 702        exp.DataType.Type.VARCHAR,
 703        exp.DataType.Type.CHAR,
 704        exp.DataType.Type.NCHAR,
 705    }
 706
 707    # Expressions that need to have all CTEs under them bubbled up to them
 708    EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set()
 709
 710    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = ()
 711
 712    SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE
 713
 714    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 715
 716    __slots__ = (
 717        "pretty",
 718        "identify",
 719        "normalize",
 720        "pad",
 721        "_indent",
 722        "normalize_functions",
 723        "unsupported_level",
 724        "max_unsupported",
 725        "leading_comma",
 726        "max_text_width",
 727        "comments",
 728        "dialect",
 729        "unsupported_messages",
 730        "_escaped_quote_end",
 731        "_escaped_byte_quote_end",
 732        "_escaped_identifier_end",
 733        "_next_name",
 734        "_identifier_start",
 735        "_identifier_end",
 736        "_quote_json_path_key_using_brackets",
 737    )
 738
 739    def __init__(
 740        self,
 741        pretty: t.Optional[bool] = None,
 742        identify: str | bool = False,
 743        normalize: bool = False,
 744        pad: int = 2,
 745        indent: int = 2,
 746        normalize_functions: t.Optional[str | bool] = None,
 747        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 748        max_unsupported: int = 3,
 749        leading_comma: bool = False,
 750        max_text_width: int = 80,
 751        comments: bool = True,
 752        dialect: DialectType = None,
 753    ):
 754        import sqlglot
 755        from sqlglot.dialects import Dialect
 756
 757        self.pretty = pretty if pretty is not None else sqlglot.pretty
 758        self.identify = identify
 759        self.normalize = normalize
 760        self.pad = pad
 761        self._indent = indent
 762        self.unsupported_level = unsupported_level
 763        self.max_unsupported = max_unsupported
 764        self.leading_comma = leading_comma
 765        self.max_text_width = max_text_width
 766        self.comments = comments
 767        self.dialect = Dialect.get_or_raise(dialect)
 768
 769        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 770        self.normalize_functions = (
 771            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 772        )
 773
 774        self.unsupported_messages: t.List[str] = []
 775        self._escaped_quote_end: str = (
 776            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 777        )
 778        self._escaped_byte_quote_end: str = (
 779            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 780            if self.dialect.BYTE_END
 781            else ""
 782        )
 783        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 784
 785        self._next_name = name_sequence("_t")
 786
 787        self._identifier_start = self.dialect.IDENTIFIER_START
 788        self._identifier_end = self.dialect.IDENTIFIER_END
 789
 790        self._quote_json_path_key_using_brackets = True
 791
 792    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
 793        """
 794        Generates the SQL string corresponding to the given syntax tree.
 795
 796        Args:
 797            expression: The syntax tree.
 798            copy: Whether to copy the expression. The generator performs mutations so
 799                it is safer to copy.
 800
 801        Returns:
 802            The SQL string corresponding to `expression`.
 803        """
 804        if copy:
 805            expression = expression.copy()
 806
 807        expression = self.preprocess(expression)
 808
 809        self.unsupported_messages = []
 810        sql = self.sql(expression).strip()
 811
 812        if self.pretty:
 813            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 814
 815        if self.unsupported_level == ErrorLevel.IGNORE:
 816            return sql
 817
 818        if self.unsupported_level == ErrorLevel.WARN:
 819            for msg in self.unsupported_messages:
 820                logger.warning(msg)
 821        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 822            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 823
 824        return sql
 825
 826    def preprocess(self, expression: exp.Expression) -> exp.Expression:
 827        """Apply generic preprocessing transformations to a given expression."""
 828        expression = self._move_ctes_to_top_level(expression)
 829
 830        if self.ENSURE_BOOLS:
 831            from sqlglot.transforms import ensure_bools
 832
 833            expression = ensure_bools(expression)
 834
 835        return expression
 836
 837    def _move_ctes_to_top_level(self, expression: E) -> E:
 838        if (
 839            not expression.parent
 840            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 841            and any(node.parent is not expression for node in expression.find_all(exp.With))
 842        ):
 843            from sqlglot.transforms import move_ctes_to_top_level
 844
 845            expression = move_ctes_to_top_level(expression)
 846        return expression
 847
 848    def unsupported(self, message: str) -> None:
 849        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 850            raise UnsupportedError(message)
 851        self.unsupported_messages.append(message)
 852
 853    def sep(self, sep: str = " ") -> str:
 854        return f"{sep.strip()}\n" if self.pretty else sep
 855
 856    def seg(self, sql: str, sep: str = " ") -> str:
 857        return f"{self.sep(sep)}{sql}"
 858
 859    def sanitize_comment(self, comment: str) -> str:
 860        comment = " " + comment if comment[0].strip() else comment
 861        comment = comment + " " if comment[-1].strip() else comment
 862
 863        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
 864            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
 865            comment = comment.replace("*/", "* /")
 866
 867        return comment
 868
 869    def maybe_comment(
 870        self,
 871        sql: str,
 872        expression: t.Optional[exp.Expression] = None,
 873        comments: t.Optional[t.List[str]] = None,
 874        separated: bool = False,
 875    ) -> str:
 876        comments = (
 877            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 878            if self.comments
 879            else None
 880        )
 881
 882        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 883            return sql
 884
 885        comments_sql = " ".join(
 886            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
 887        )
 888
 889        if not comments_sql:
 890            return sql
 891
 892        comments_sql = self._replace_line_breaks(comments_sql)
 893
 894        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 895            return (
 896                f"{self.sep()}{comments_sql}{sql}"
 897                if not sql or sql[0].isspace()
 898                else f"{comments_sql}{self.sep()}{sql}"
 899            )
 900
 901        return f"{sql} {comments_sql}"
 902
 903    def wrap(self, expression: exp.Expression | str) -> str:
 904        this_sql = (
 905            self.sql(expression)
 906            if isinstance(expression, exp.UNWRAPPED_QUERIES)
 907            else self.sql(expression, "this")
 908        )
 909        if not this_sql:
 910            return "()"
 911
 912        this_sql = self.indent(this_sql, level=1, pad=0)
 913        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 914
 915    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 916        original = self.identify
 917        self.identify = False
 918        result = func(*args, **kwargs)
 919        self.identify = original
 920        return result
 921
 922    def normalize_func(self, name: str) -> str:
 923        if self.normalize_functions == "upper" or self.normalize_functions is True:
 924            return name.upper()
 925        if self.normalize_functions == "lower":
 926            return name.lower()
 927        return name
 928
 929    def indent(
 930        self,
 931        sql: str,
 932        level: int = 0,
 933        pad: t.Optional[int] = None,
 934        skip_first: bool = False,
 935        skip_last: bool = False,
 936    ) -> str:
 937        if not self.pretty or not sql:
 938            return sql
 939
 940        pad = self.pad if pad is None else pad
 941        lines = sql.split("\n")
 942
 943        return "\n".join(
 944            (
 945                line
 946                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 947                else f"{' ' * (level * self._indent + pad)}{line}"
 948            )
 949            for i, line in enumerate(lines)
 950        )
 951
 952    def sql(
 953        self,
 954        expression: t.Optional[str | exp.Expression],
 955        key: t.Optional[str] = None,
 956        comment: bool = True,
 957    ) -> str:
 958        if not expression:
 959            return ""
 960
 961        if isinstance(expression, str):
 962            return expression
 963
 964        if key:
 965            value = expression.args.get(key)
 966            if value:
 967                return self.sql(value)
 968            return ""
 969
 970        transform = self.TRANSFORMS.get(expression.__class__)
 971
 972        if callable(transform):
 973            sql = transform(self, expression)
 974        elif isinstance(expression, exp.Expression):
 975            exp_handler_name = f"{expression.key}_sql"
 976
 977            if hasattr(self, exp_handler_name):
 978                sql = getattr(self, exp_handler_name)(expression)
 979            elif isinstance(expression, exp.Func):
 980                sql = self.function_fallback_sql(expression)
 981            elif isinstance(expression, exp.Property):
 982                sql = self.property_sql(expression)
 983            else:
 984                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
 985        else:
 986            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
 987
 988        return self.maybe_comment(sql, expression) if self.comments and comment else sql
 989
 990    def uncache_sql(self, expression: exp.Uncache) -> str:
 991        table = self.sql(expression, "this")
 992        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
 993        return f"UNCACHE TABLE{exists_sql} {table}"
 994
 995    def cache_sql(self, expression: exp.Cache) -> str:
 996        lazy = " LAZY" if expression.args.get("lazy") else ""
 997        table = self.sql(expression, "this")
 998        options = expression.args.get("options")
 999        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1000        sql = self.sql(expression, "expression")
1001        sql = f" AS{self.sep()}{sql}" if sql else ""
1002        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1003        return self.prepend_ctes(expression, sql)
1004
1005    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1006        if isinstance(expression.parent, exp.Cast):
1007            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
1008        default = "DEFAULT " if expression.args.get("default") else ""
1009        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1010
1011    def column_parts(self, expression: exp.Column) -> str:
1012        return ".".join(
1013            self.sql(part)
1014            for part in (
1015                expression.args.get("catalog"),
1016                expression.args.get("db"),
1017                expression.args.get("table"),
1018                expression.args.get("this"),
1019            )
1020            if part
1021        )
1022
1023    def column_sql(self, expression: exp.Column) -> str:
1024        join_mark = " (+)" if expression.args.get("join_mark") else ""
1025
1026        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1027            join_mark = ""
1028            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1029
1030        return f"{self.column_parts(expression)}{join_mark}"
1031
1032    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1033        this = self.sql(expression, "this")
1034        this = f" {this}" if this else ""
1035        position = self.sql(expression, "position")
1036        return f"{position}{this}"
1037
1038    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1039        column = self.sql(expression, "this")
1040        kind = self.sql(expression, "kind")
1041        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1042        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1043        kind = f"{sep}{kind}" if kind else ""
1044        constraints = f" {constraints}" if constraints else ""
1045        position = self.sql(expression, "position")
1046        position = f" {position}" if position else ""
1047
1048        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1049            kind = ""
1050
1051        return f"{exists}{column}{kind}{constraints}{position}"
1052
1053    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1054        this = self.sql(expression, "this")
1055        kind_sql = self.sql(expression, "kind").strip()
1056        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1057
1058    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1059        this = self.sql(expression, "this")
1060        if expression.args.get("not_null"):
1061            persisted = " PERSISTED NOT NULL"
1062        elif expression.args.get("persisted"):
1063            persisted = " PERSISTED"
1064        else:
1065            persisted = ""
1066
1067        return f"AS {this}{persisted}"
1068
1069    def autoincrementcolumnconstraint_sql(self, _) -> str:
1070        return self.token_sql(TokenType.AUTO_INCREMENT)
1071
1072    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1073        if isinstance(expression.this, list):
1074            this = self.wrap(self.expressions(expression, key="this", flat=True))
1075        else:
1076            this = self.sql(expression, "this")
1077
1078        return f"COMPRESS {this}"
1079
1080    def generatedasidentitycolumnconstraint_sql(
1081        self, expression: exp.GeneratedAsIdentityColumnConstraint
1082    ) -> str:
1083        this = ""
1084        if expression.this is not None:
1085            on_null = " ON NULL" if expression.args.get("on_null") else ""
1086            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1087
1088        start = expression.args.get("start")
1089        start = f"START WITH {start}" if start else ""
1090        increment = expression.args.get("increment")
1091        increment = f" INCREMENT BY {increment}" if increment else ""
1092        minvalue = expression.args.get("minvalue")
1093        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1094        maxvalue = expression.args.get("maxvalue")
1095        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1096        cycle = expression.args.get("cycle")
1097        cycle_sql = ""
1098
1099        if cycle is not None:
1100            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1101            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1102
1103        sequence_opts = ""
1104        if start or increment or cycle_sql:
1105            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1106            sequence_opts = f" ({sequence_opts.strip()})"
1107
1108        expr = self.sql(expression, "expression")
1109        expr = f"({expr})" if expr else "IDENTITY"
1110
1111        return f"GENERATED{this} AS {expr}{sequence_opts}"
1112
1113    def generatedasrowcolumnconstraint_sql(
1114        self, expression: exp.GeneratedAsRowColumnConstraint
1115    ) -> str:
1116        start = "START" if expression.args.get("start") else "END"
1117        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1118        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1119
1120    def periodforsystemtimeconstraint_sql(
1121        self, expression: exp.PeriodForSystemTimeConstraint
1122    ) -> str:
1123        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1124
1125    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1126        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1127
1128    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1129        desc = expression.args.get("desc")
1130        if desc is not None:
1131            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1132        options = self.expressions(expression, key="options", flat=True, sep=" ")
1133        options = f" {options}" if options else ""
1134        return f"PRIMARY KEY{options}"
1135
1136    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1137        this = self.sql(expression, "this")
1138        this = f" {this}" if this else ""
1139        index_type = expression.args.get("index_type")
1140        index_type = f" USING {index_type}" if index_type else ""
1141        on_conflict = self.sql(expression, "on_conflict")
1142        on_conflict = f" {on_conflict}" if on_conflict else ""
1143        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1144        options = self.expressions(expression, key="options", flat=True, sep=" ")
1145        options = f" {options}" if options else ""
1146        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1147
1148    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1149        return self.sql(expression, "this")
1150
1151    def create_sql(self, expression: exp.Create) -> str:
1152        kind = self.sql(expression, "kind")
1153        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1154        properties = expression.args.get("properties")
1155        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1156
1157        this = self.createable_sql(expression, properties_locs)
1158
1159        properties_sql = ""
1160        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1161            exp.Properties.Location.POST_WITH
1162        ):
1163            props_ast = exp.Properties(
1164                expressions=[
1165                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1166                    *properties_locs[exp.Properties.Location.POST_WITH],
1167                ]
1168            )
1169            props_ast.parent = expression
1170            properties_sql = self.sql(props_ast)
1171
1172            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1173                properties_sql = self.sep() + properties_sql
1174            elif not self.pretty:
1175                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1176                properties_sql = f" {properties_sql}"
1177
1178        begin = " BEGIN" if expression.args.get("begin") else ""
1179        end = " END" if expression.args.get("end") else ""
1180
1181        expression_sql = self.sql(expression, "expression")
1182        if expression_sql:
1183            expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
1184
1185            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1186                postalias_props_sql = ""
1187                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1188                    postalias_props_sql = self.properties(
1189                        exp.Properties(
1190                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1191                        ),
1192                        wrapped=False,
1193                    )
1194                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1195                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1196
1197        postindex_props_sql = ""
1198        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1199            postindex_props_sql = self.properties(
1200                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1201                wrapped=False,
1202                prefix=" ",
1203            )
1204
1205        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1206        indexes = f" {indexes}" if indexes else ""
1207        index_sql = indexes + postindex_props_sql
1208
1209        replace = " OR REPLACE" if expression.args.get("replace") else ""
1210        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1211        unique = " UNIQUE" if expression.args.get("unique") else ""
1212
1213        clustered = expression.args.get("clustered")
1214        if clustered is None:
1215            clustered_sql = ""
1216        elif clustered:
1217            clustered_sql = " CLUSTERED COLUMNSTORE"
1218        else:
1219            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1220
1221        postcreate_props_sql = ""
1222        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1223            postcreate_props_sql = self.properties(
1224                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1225                sep=" ",
1226                prefix=" ",
1227                wrapped=False,
1228            )
1229
1230        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1231
1232        postexpression_props_sql = ""
1233        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1234            postexpression_props_sql = self.properties(
1235                exp.Properties(
1236                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1237                ),
1238                sep=" ",
1239                prefix=" ",
1240                wrapped=False,
1241            )
1242
1243        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1244        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1245        no_schema_binding = (
1246            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1247        )
1248
1249        clone = self.sql(expression, "clone")
1250        clone = f" {clone}" if clone else ""
1251
1252        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1253            properties_expression = f"{expression_sql}{properties_sql}"
1254        else:
1255            properties_expression = f"{properties_sql}{expression_sql}"
1256
1257        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1258        return self.prepend_ctes(expression, expression_sql)
1259
1260    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1261        start = self.sql(expression, "start")
1262        start = f"START WITH {start}" if start else ""
1263        increment = self.sql(expression, "increment")
1264        increment = f" INCREMENT BY {increment}" if increment else ""
1265        minvalue = self.sql(expression, "minvalue")
1266        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1267        maxvalue = self.sql(expression, "maxvalue")
1268        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1269        owned = self.sql(expression, "owned")
1270        owned = f" OWNED BY {owned}" if owned else ""
1271
1272        cache = expression.args.get("cache")
1273        if cache is None:
1274            cache_str = ""
1275        elif cache is True:
1276            cache_str = " CACHE"
1277        else:
1278            cache_str = f" CACHE {cache}"
1279
1280        options = self.expressions(expression, key="options", flat=True, sep=" ")
1281        options = f" {options}" if options else ""
1282
1283        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1284
1285    def clone_sql(self, expression: exp.Clone) -> str:
1286        this = self.sql(expression, "this")
1287        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1288        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1289        return f"{shallow}{keyword} {this}"
1290
1291    def describe_sql(self, expression: exp.Describe) -> str:
1292        style = expression.args.get("style")
1293        style = f" {style}" if style else ""
1294        partition = self.sql(expression, "partition")
1295        partition = f" {partition}" if partition else ""
1296        format = self.sql(expression, "format")
1297        format = f" {format}" if format else ""
1298
1299        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1300
1301    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1302        tag = self.sql(expression, "tag")
1303        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1304
1305    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1306        with_ = self.sql(expression, "with")
1307        if with_:
1308            sql = f"{with_}{self.sep()}{sql}"
1309        return sql
1310
1311    def with_sql(self, expression: exp.With) -> str:
1312        sql = self.expressions(expression, flat=True)
1313        recursive = (
1314            "RECURSIVE "
1315            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1316            else ""
1317        )
1318        search = self.sql(expression, "search")
1319        search = f" {search}" if search else ""
1320
1321        return f"WITH {recursive}{sql}{search}"
1322
1323    def cte_sql(self, expression: exp.CTE) -> str:
1324        alias = expression.args.get("alias")
1325        if alias:
1326            alias.add_comments(expression.pop_comments())
1327
1328        alias_sql = self.sql(expression, "alias")
1329
1330        materialized = expression.args.get("materialized")
1331        if materialized is False:
1332            materialized = "NOT MATERIALIZED "
1333        elif materialized:
1334            materialized = "MATERIALIZED "
1335
1336        return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1337
1338    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1339        alias = self.sql(expression, "this")
1340        columns = self.expressions(expression, key="columns", flat=True)
1341        columns = f"({columns})" if columns else ""
1342
1343        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1344            columns = ""
1345            self.unsupported("Named columns are not supported in table alias.")
1346
1347        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1348            alias = self._next_name()
1349
1350        return f"{alias}{columns}"
1351
1352    def bitstring_sql(self, expression: exp.BitString) -> str:
1353        this = self.sql(expression, "this")
1354        if self.dialect.BIT_START:
1355            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1356        return f"{int(this, 2)}"
1357
1358    def hexstring_sql(
1359        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1360    ) -> str:
1361        this = self.sql(expression, "this")
1362        is_integer_type = expression.args.get("is_integer")
1363
1364        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1365            not self.dialect.HEX_START and not binary_function_repr
1366        ):
1367            # Integer representation will be returned if:
1368            # - The read dialect treats the hex value as integer literal but not the write
1369            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1370            return f"{int(this, 16)}"
1371
1372        if not is_integer_type:
1373            # Read dialect treats the hex value as BINARY/BLOB
1374            if binary_function_repr:
1375                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1376                return self.func(binary_function_repr, exp.Literal.string(this))
1377            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1378                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1379                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1380
1381        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1382
1383    def bytestring_sql(self, expression: exp.ByteString) -> str:
1384        this = self.sql(expression, "this")
1385        if self.dialect.BYTE_START:
1386            escaped_byte_string = self.escape_str(
1387                this,
1388                escape_backslash=False,
1389                delimiter=self.dialect.BYTE_END,
1390                escaped_delimiter=self._escaped_byte_quote_end,
1391            )
1392            return f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1393        return this
1394
1395    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1396        this = self.sql(expression, "this")
1397        escape = expression.args.get("escape")
1398
1399        if self.dialect.UNICODE_START:
1400            escape_substitute = r"\\\1"
1401            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1402        else:
1403            escape_substitute = r"\\u\1"
1404            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1405
1406        if escape:
1407            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1408            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1409        else:
1410            escape_pattern = ESCAPED_UNICODE_RE
1411            escape_sql = ""
1412
1413        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1414            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1415
1416        return f"{left_quote}{this}{right_quote}{escape_sql}"
1417
1418    def rawstring_sql(self, expression: exp.RawString) -> str:
1419        string = expression.this
1420        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1421            string = string.replace("\\", "\\\\")
1422
1423        string = self.escape_str(string, escape_backslash=False)
1424        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1425
1426    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1427        this = self.sql(expression, "this")
1428        specifier = self.sql(expression, "expression")
1429        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1430        return f"{this}{specifier}"
1431
1432    def datatype_sql(self, expression: exp.DataType) -> str:
1433        nested = ""
1434        values = ""
1435        interior = self.expressions(expression, flat=True)
1436
1437        type_value = expression.this
1438        if type_value in self.UNSUPPORTED_TYPES:
1439            self.unsupported(
1440                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1441            )
1442
1443        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1444            type_sql = self.sql(expression, "kind")
1445        else:
1446            type_sql = (
1447                self.TYPE_MAPPING.get(type_value, type_value.value)
1448                if isinstance(type_value, exp.DataType.Type)
1449                else type_value
1450            )
1451
1452        if interior:
1453            if expression.args.get("nested"):
1454                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1455                if expression.args.get("values") is not None:
1456                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1457                    values = self.expressions(expression, key="values", flat=True)
1458                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1459            elif type_value == exp.DataType.Type.INTERVAL:
1460                nested = f" {interior}"
1461            else:
1462                nested = f"({interior})"
1463
1464        type_sql = f"{type_sql}{nested}{values}"
1465        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1466            exp.DataType.Type.TIMETZ,
1467            exp.DataType.Type.TIMESTAMPTZ,
1468        ):
1469            type_sql = f"{type_sql} WITH TIME ZONE"
1470
1471        return type_sql
1472
1473    def directory_sql(self, expression: exp.Directory) -> str:
1474        local = "LOCAL " if expression.args.get("local") else ""
1475        row_format = self.sql(expression, "row_format")
1476        row_format = f" {row_format}" if row_format else ""
1477        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1478
1479    def delete_sql(self, expression: exp.Delete) -> str:
1480        this = self.sql(expression, "this")
1481        this = f" FROM {this}" if this else ""
1482        using = self.sql(expression, "using")
1483        using = f" USING {using}" if using else ""
1484        cluster = self.sql(expression, "cluster")
1485        cluster = f" {cluster}" if cluster else ""
1486        where = self.sql(expression, "where")
1487        returning = self.sql(expression, "returning")
1488        limit = self.sql(expression, "limit")
1489        tables = self.expressions(expression, key="tables")
1490        tables = f" {tables}" if tables else ""
1491        if self.RETURNING_END:
1492            expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}"
1493        else:
1494            expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}"
1495        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1496
1497    def drop_sql(self, expression: exp.Drop) -> str:
1498        this = self.sql(expression, "this")
1499        expressions = self.expressions(expression, flat=True)
1500        expressions = f" ({expressions})" if expressions else ""
1501        kind = expression.args["kind"]
1502        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1503        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1504        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1505        on_cluster = self.sql(expression, "cluster")
1506        on_cluster = f" {on_cluster}" if on_cluster else ""
1507        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1508        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1509        cascade = " CASCADE" if expression.args.get("cascade") else ""
1510        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1511        purge = " PURGE" if expression.args.get("purge") else ""
1512        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1513
1514    def set_operation(self, expression: exp.SetOperation) -> str:
1515        op_type = type(expression)
1516        op_name = op_type.key.upper()
1517
1518        distinct = expression.args.get("distinct")
1519        if (
1520            distinct is False
1521            and op_type in (exp.Except, exp.Intersect)
1522            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1523        ):
1524            self.unsupported(f"{op_name} ALL is not supported")
1525
1526        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1527
1528        if distinct is None:
1529            distinct = default_distinct
1530            if distinct is None:
1531                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1532
1533        if distinct is default_distinct:
1534            distinct_or_all = ""
1535        else:
1536            distinct_or_all = " DISTINCT" if distinct else " ALL"
1537
1538        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1539        side_kind = f"{side_kind} " if side_kind else ""
1540
1541        by_name = " BY NAME" if expression.args.get("by_name") else ""
1542        on = self.expressions(expression, key="on", flat=True)
1543        on = f" ON ({on})" if on else ""
1544
1545        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1546
1547    def set_operations(self, expression: exp.SetOperation) -> str:
1548        if not self.SET_OP_MODIFIERS:
1549            limit = expression.args.get("limit")
1550            order = expression.args.get("order")
1551
1552            if limit or order:
1553                select = self._move_ctes_to_top_level(
1554                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1555                )
1556
1557                if limit:
1558                    select = select.limit(limit.pop(), copy=False)
1559                if order:
1560                    select = select.order_by(order.pop(), copy=False)
1561                return self.sql(select)
1562
1563        sqls: t.List[str] = []
1564        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1565
1566        while stack:
1567            node = stack.pop()
1568
1569            if isinstance(node, exp.SetOperation):
1570                stack.append(node.expression)
1571                stack.append(
1572                    self.maybe_comment(
1573                        self.set_operation(node), comments=node.comments, separated=True
1574                    )
1575                )
1576                stack.append(node.this)
1577            else:
1578                sqls.append(self.sql(node))
1579
1580        this = self.sep().join(sqls)
1581        this = self.query_modifiers(expression, this)
1582        return self.prepend_ctes(expression, this)
1583
1584    def fetch_sql(self, expression: exp.Fetch) -> str:
1585        direction = expression.args.get("direction")
1586        direction = f" {direction}" if direction else ""
1587        count = self.sql(expression, "count")
1588        count = f" {count}" if count else ""
1589        limit_options = self.sql(expression, "limit_options")
1590        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1591        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1592
1593    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1594        percent = " PERCENT" if expression.args.get("percent") else ""
1595        rows = " ROWS" if expression.args.get("rows") else ""
1596        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1597        if not with_ties and rows:
1598            with_ties = " ONLY"
1599        return f"{percent}{rows}{with_ties}"
1600
1601    def filter_sql(self, expression: exp.Filter) -> str:
1602        if self.AGGREGATE_FILTER_SUPPORTED:
1603            this = self.sql(expression, "this")
1604            where = self.sql(expression, "expression").strip()
1605            return f"{this} FILTER({where})"
1606
1607        agg = expression.this
1608        agg_arg = agg.this
1609        cond = expression.expression.this
1610        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1611        return self.sql(agg)
1612
1613    def hint_sql(self, expression: exp.Hint) -> str:
1614        if not self.QUERY_HINTS:
1615            self.unsupported("Hints are not supported")
1616            return ""
1617
1618        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1619
1620    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1621        using = self.sql(expression, "using")
1622        using = f" USING {using}" if using else ""
1623        columns = self.expressions(expression, key="columns", flat=True)
1624        columns = f"({columns})" if columns else ""
1625        partition_by = self.expressions(expression, key="partition_by", flat=True)
1626        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1627        where = self.sql(expression, "where")
1628        include = self.expressions(expression, key="include", flat=True)
1629        if include:
1630            include = f" INCLUDE ({include})"
1631        with_storage = self.expressions(expression, key="with_storage", flat=True)
1632        with_storage = f" WITH ({with_storage})" if with_storage else ""
1633        tablespace = self.sql(expression, "tablespace")
1634        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1635        on = self.sql(expression, "on")
1636        on = f" ON {on}" if on else ""
1637
1638        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1639
1640    def index_sql(self, expression: exp.Index) -> str:
1641        unique = "UNIQUE " if expression.args.get("unique") else ""
1642        primary = "PRIMARY " if expression.args.get("primary") else ""
1643        amp = "AMP " if expression.args.get("amp") else ""
1644        name = self.sql(expression, "this")
1645        name = f"{name} " if name else ""
1646        table = self.sql(expression, "table")
1647        table = f"{self.INDEX_ON} {table}" if table else ""
1648
1649        index = "INDEX " if not table else ""
1650
1651        params = self.sql(expression, "params")
1652        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1653
1654    def identifier_sql(self, expression: exp.Identifier) -> str:
1655        text = expression.name
1656        lower = text.lower()
1657        text = lower if self.normalize and not expression.quoted else text
1658        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1659        if (
1660            expression.quoted
1661            or self.dialect.can_identify(text, self.identify)
1662            or lower in self.RESERVED_KEYWORDS
1663            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1664        ):
1665            text = f"{self._identifier_start}{text}{self._identifier_end}"
1666        return text
1667
1668    def hex_sql(self, expression: exp.Hex) -> str:
1669        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1670        if self.dialect.HEX_LOWERCASE:
1671            text = self.func("LOWER", text)
1672
1673        return text
1674
1675    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1676        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1677        if not self.dialect.HEX_LOWERCASE:
1678            text = self.func("LOWER", text)
1679        return text
1680
1681    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1682        input_format = self.sql(expression, "input_format")
1683        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1684        output_format = self.sql(expression, "output_format")
1685        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1686        return self.sep().join((input_format, output_format))
1687
1688    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1689        string = self.sql(exp.Literal.string(expression.name))
1690        return f"{prefix}{string}"
1691
1692    def partition_sql(self, expression: exp.Partition) -> str:
1693        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1694        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1695
1696    def properties_sql(self, expression: exp.Properties) -> str:
1697        root_properties = []
1698        with_properties = []
1699
1700        for p in expression.expressions:
1701            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1702            if p_loc == exp.Properties.Location.POST_WITH:
1703                with_properties.append(p)
1704            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1705                root_properties.append(p)
1706
1707        root_props_ast = exp.Properties(expressions=root_properties)
1708        root_props_ast.parent = expression.parent
1709
1710        with_props_ast = exp.Properties(expressions=with_properties)
1711        with_props_ast.parent = expression.parent
1712
1713        root_props = self.root_properties(root_props_ast)
1714        with_props = self.with_properties(with_props_ast)
1715
1716        if root_props and with_props and not self.pretty:
1717            with_props = " " + with_props
1718
1719        return root_props + with_props
1720
1721    def root_properties(self, properties: exp.Properties) -> str:
1722        if properties.expressions:
1723            return self.expressions(properties, indent=False, sep=" ")
1724        return ""
1725
1726    def properties(
1727        self,
1728        properties: exp.Properties,
1729        prefix: str = "",
1730        sep: str = ", ",
1731        suffix: str = "",
1732        wrapped: bool = True,
1733    ) -> str:
1734        if properties.expressions:
1735            expressions = self.expressions(properties, sep=sep, indent=False)
1736            if expressions:
1737                expressions = self.wrap(expressions) if wrapped else expressions
1738                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1739        return ""
1740
1741    def with_properties(self, properties: exp.Properties) -> str:
1742        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1743
1744    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1745        properties_locs = defaultdict(list)
1746        for p in properties.expressions:
1747            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1748            if p_loc != exp.Properties.Location.UNSUPPORTED:
1749                properties_locs[p_loc].append(p)
1750            else:
1751                self.unsupported(f"Unsupported property {p.key}")
1752
1753        return properties_locs
1754
1755    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1756        if isinstance(expression.this, exp.Dot):
1757            return self.sql(expression, "this")
1758        return f"'{expression.name}'" if string_key else expression.name
1759
1760    def property_sql(self, expression: exp.Property) -> str:
1761        property_cls = expression.__class__
1762        if property_cls == exp.Property:
1763            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1764
1765        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1766        if not property_name:
1767            self.unsupported(f"Unsupported property {expression.key}")
1768
1769        return f"{property_name}={self.sql(expression, 'this')}"
1770
1771    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1772        if self.SUPPORTS_CREATE_TABLE_LIKE:
1773            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1774            options = f" {options}" if options else ""
1775
1776            like = f"LIKE {self.sql(expression, 'this')}{options}"
1777            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1778                like = f"({like})"
1779
1780            return like
1781
1782        if expression.expressions:
1783            self.unsupported("Transpilation of LIKE property options is unsupported")
1784
1785        select = exp.select("*").from_(expression.this).limit(0)
1786        return f"AS {self.sql(select)}"
1787
1788    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1789        no = "NO " if expression.args.get("no") else ""
1790        protection = " PROTECTION" if expression.args.get("protection") else ""
1791        return f"{no}FALLBACK{protection}"
1792
1793    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1794        no = "NO " if expression.args.get("no") else ""
1795        local = expression.args.get("local")
1796        local = f"{local} " if local else ""
1797        dual = "DUAL " if expression.args.get("dual") else ""
1798        before = "BEFORE " if expression.args.get("before") else ""
1799        after = "AFTER " if expression.args.get("after") else ""
1800        return f"{no}{local}{dual}{before}{after}JOURNAL"
1801
1802    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1803        freespace = self.sql(expression, "this")
1804        percent = " PERCENT" if expression.args.get("percent") else ""
1805        return f"FREESPACE={freespace}{percent}"
1806
1807    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1808        if expression.args.get("default"):
1809            property = "DEFAULT"
1810        elif expression.args.get("on"):
1811            property = "ON"
1812        else:
1813            property = "OFF"
1814        return f"CHECKSUM={property}"
1815
1816    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1817        if expression.args.get("no"):
1818            return "NO MERGEBLOCKRATIO"
1819        if expression.args.get("default"):
1820            return "DEFAULT MERGEBLOCKRATIO"
1821
1822        percent = " PERCENT" if expression.args.get("percent") else ""
1823        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1824
1825    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1826        default = expression.args.get("default")
1827        minimum = expression.args.get("minimum")
1828        maximum = expression.args.get("maximum")
1829        if default or minimum or maximum:
1830            if default:
1831                prop = "DEFAULT"
1832            elif minimum:
1833                prop = "MINIMUM"
1834            else:
1835                prop = "MAXIMUM"
1836            return f"{prop} DATABLOCKSIZE"
1837        units = expression.args.get("units")
1838        units = f" {units}" if units else ""
1839        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
1840
1841    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1842        autotemp = expression.args.get("autotemp")
1843        always = expression.args.get("always")
1844        default = expression.args.get("default")
1845        manual = expression.args.get("manual")
1846        never = expression.args.get("never")
1847
1848        if autotemp is not None:
1849            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1850        elif always:
1851            prop = "ALWAYS"
1852        elif default:
1853            prop = "DEFAULT"
1854        elif manual:
1855            prop = "MANUAL"
1856        elif never:
1857            prop = "NEVER"
1858        return f"BLOCKCOMPRESSION={prop}"
1859
1860    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1861        no = expression.args.get("no")
1862        no = " NO" if no else ""
1863        concurrent = expression.args.get("concurrent")
1864        concurrent = " CONCURRENT" if concurrent else ""
1865        target = self.sql(expression, "target")
1866        target = f" {target}" if target else ""
1867        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1868
1869    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1870        if isinstance(expression.this, list):
1871            return f"IN ({self.expressions(expression, key='this', flat=True)})"
1872        if expression.this:
1873            modulus = self.sql(expression, "this")
1874            remainder = self.sql(expression, "expression")
1875            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1876
1877        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1878        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1879        return f"FROM ({from_expressions}) TO ({to_expressions})"
1880
1881    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1882        this = self.sql(expression, "this")
1883
1884        for_values_or_default = expression.expression
1885        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1886            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1887        else:
1888            for_values_or_default = " DEFAULT"
1889
1890        return f"PARTITION OF {this}{for_values_or_default}"
1891
1892    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1893        kind = expression.args.get("kind")
1894        this = f" {self.sql(expression, 'this')}" if expression.this else ""
1895        for_or_in = expression.args.get("for_or_in")
1896        for_or_in = f" {for_or_in}" if for_or_in else ""
1897        lock_type = expression.args.get("lock_type")
1898        override = " OVERRIDE" if expression.args.get("override") else ""
1899        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1900
1901    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1902        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1903        statistics = expression.args.get("statistics")
1904        statistics_sql = ""
1905        if statistics is not None:
1906            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1907        return f"{data_sql}{statistics_sql}"
1908
1909    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1910        this = self.sql(expression, "this")
1911        this = f"HISTORY_TABLE={this}" if this else ""
1912        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
1913        data_consistency = (
1914            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
1915        )
1916        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
1917        retention_period = (
1918            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
1919        )
1920
1921        if this:
1922            on_sql = self.func("ON", this, data_consistency, retention_period)
1923        else:
1924            on_sql = "ON" if expression.args.get("on") else "OFF"
1925
1926        sql = f"SYSTEM_VERSIONING={on_sql}"
1927
1928        return f"WITH({sql})" if expression.args.get("with") else sql
1929
1930    def insert_sql(self, expression: exp.Insert) -> str:
1931        hint = self.sql(expression, "hint")
1932        overwrite = expression.args.get("overwrite")
1933
1934        if isinstance(expression.this, exp.Directory):
1935            this = " OVERWRITE" if overwrite else " INTO"
1936        else:
1937            this = self.INSERT_OVERWRITE if overwrite else " INTO"
1938
1939        stored = self.sql(expression, "stored")
1940        stored = f" {stored}" if stored else ""
1941        alternative = expression.args.get("alternative")
1942        alternative = f" OR {alternative}" if alternative else ""
1943        ignore = " IGNORE" if expression.args.get("ignore") else ""
1944        is_function = expression.args.get("is_function")
1945        if is_function:
1946            this = f"{this} FUNCTION"
1947        this = f"{this} {self.sql(expression, 'this')}"
1948
1949        exists = " IF EXISTS" if expression.args.get("exists") else ""
1950        where = self.sql(expression, "where")
1951        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
1952        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
1953        on_conflict = self.sql(expression, "conflict")
1954        on_conflict = f" {on_conflict}" if on_conflict else ""
1955        by_name = " BY NAME" if expression.args.get("by_name") else ""
1956        returning = self.sql(expression, "returning")
1957
1958        if self.RETURNING_END:
1959            expression_sql = f"{expression_sql}{on_conflict}{returning}"
1960        else:
1961            expression_sql = f"{returning}{expression_sql}{on_conflict}"
1962
1963        partition_by = self.sql(expression, "partition")
1964        partition_by = f" {partition_by}" if partition_by else ""
1965        settings = self.sql(expression, "settings")
1966        settings = f" {settings}" if settings else ""
1967
1968        source = self.sql(expression, "source")
1969        source = f"TABLE {source}" if source else ""
1970
1971        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
1972        return self.prepend_ctes(expression, sql)
1973
1974    def introducer_sql(self, expression: exp.Introducer) -> str:
1975        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
1976
1977    def kill_sql(self, expression: exp.Kill) -> str:
1978        kind = self.sql(expression, "kind")
1979        kind = f" {kind}" if kind else ""
1980        this = self.sql(expression, "this")
1981        this = f" {this}" if this else ""
1982        return f"KILL{kind}{this}"
1983
1984    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
1985        return expression.name
1986
1987    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
1988        return expression.name
1989
1990    def onconflict_sql(self, expression: exp.OnConflict) -> str:
1991        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
1992
1993        constraint = self.sql(expression, "constraint")
1994        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
1995
1996        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
1997        conflict_keys = f"({conflict_keys}) " if conflict_keys else " "
1998        action = self.sql(expression, "action")
1999
2000        expressions = self.expressions(expression, flat=True)
2001        if expressions:
2002            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2003            expressions = f" {set_keyword}{expressions}"
2004
2005        where = self.sql(expression, "where")
2006        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2007
2008    def returning_sql(self, expression: exp.Returning) -> str:
2009        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2010
2011    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2012        fields = self.sql(expression, "fields")
2013        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2014        escaped = self.sql(expression, "escaped")
2015        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2016        items = self.sql(expression, "collection_items")
2017        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2018        keys = self.sql(expression, "map_keys")
2019        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2020        lines = self.sql(expression, "lines")
2021        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2022        null = self.sql(expression, "null")
2023        null = f" NULL DEFINED AS {null}" if null else ""
2024        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2025
2026    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2027        return f"WITH ({self.expressions(expression, flat=True)})"
2028
2029    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2030        this = f"{self.sql(expression, 'this')} INDEX"
2031        target = self.sql(expression, "target")
2032        target = f" FOR {target}" if target else ""
2033        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2034
2035    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2036        this = self.sql(expression, "this")
2037        kind = self.sql(expression, "kind")
2038        expr = self.sql(expression, "expression")
2039        return f"{this} ({kind} => {expr})"
2040
2041    def table_parts(self, expression: exp.Table) -> str:
2042        return ".".join(
2043            self.sql(part)
2044            for part in (
2045                expression.args.get("catalog"),
2046                expression.args.get("db"),
2047                expression.args.get("this"),
2048            )
2049            if part is not None
2050        )
2051
2052    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2053        table = self.table_parts(expression)
2054        only = "ONLY " if expression.args.get("only") else ""
2055        partition = self.sql(expression, "partition")
2056        partition = f" {partition}" if partition else ""
2057        version = self.sql(expression, "version")
2058        version = f" {version}" if version else ""
2059        alias = self.sql(expression, "alias")
2060        alias = f"{sep}{alias}" if alias else ""
2061
2062        sample = self.sql(expression, "sample")
2063        if self.dialect.ALIAS_POST_TABLESAMPLE:
2064            sample_pre_alias = sample
2065            sample_post_alias = ""
2066        else:
2067            sample_pre_alias = ""
2068            sample_post_alias = sample
2069
2070        hints = self.expressions(expression, key="hints", sep=" ")
2071        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2072        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2073        joins = self.indent(
2074            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2075        )
2076        laterals = self.expressions(expression, key="laterals", sep="")
2077
2078        file_format = self.sql(expression, "format")
2079        if file_format:
2080            pattern = self.sql(expression, "pattern")
2081            pattern = f", PATTERN => {pattern}" if pattern else ""
2082            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2083
2084        ordinality = expression.args.get("ordinality") or ""
2085        if ordinality:
2086            ordinality = f" WITH ORDINALITY{alias}"
2087            alias = ""
2088
2089        when = self.sql(expression, "when")
2090        if when:
2091            table = f"{table} {when}"
2092
2093        changes = self.sql(expression, "changes")
2094        changes = f" {changes}" if changes else ""
2095
2096        rows_from = self.expressions(expression, key="rows_from")
2097        if rows_from:
2098            table = f"ROWS FROM {self.wrap(rows_from)}"
2099
2100        return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2101
2102    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2103        table = self.func("TABLE", expression.this)
2104        alias = self.sql(expression, "alias")
2105        alias = f" AS {alias}" if alias else ""
2106        sample = self.sql(expression, "sample")
2107        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2108        joins = self.indent(
2109            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2110        )
2111        return f"{table}{alias}{pivots}{sample}{joins}"
2112
2113    def tablesample_sql(
2114        self,
2115        expression: exp.TableSample,
2116        tablesample_keyword: t.Optional[str] = None,
2117    ) -> str:
2118        method = self.sql(expression, "method")
2119        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2120        numerator = self.sql(expression, "bucket_numerator")
2121        denominator = self.sql(expression, "bucket_denominator")
2122        field = self.sql(expression, "bucket_field")
2123        field = f" ON {field}" if field else ""
2124        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2125        seed = self.sql(expression, "seed")
2126        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2127
2128        size = self.sql(expression, "size")
2129        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2130            size = f"{size} ROWS"
2131
2132        percent = self.sql(expression, "percent")
2133        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2134            percent = f"{percent} PERCENT"
2135
2136        expr = f"{bucket}{percent}{size}"
2137        if self.TABLESAMPLE_REQUIRES_PARENS:
2138            expr = f"({expr})"
2139
2140        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2141
2142    def pivot_sql(self, expression: exp.Pivot) -> str:
2143        expressions = self.expressions(expression, flat=True)
2144        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2145
2146        group = self.sql(expression, "group")
2147
2148        if expression.this:
2149            this = self.sql(expression, "this")
2150            if not expressions:
2151                return f"UNPIVOT {this}"
2152
2153            on = f"{self.seg('ON')} {expressions}"
2154            into = self.sql(expression, "into")
2155            into = f"{self.seg('INTO')} {into}" if into else ""
2156            using = self.expressions(expression, key="using", flat=True)
2157            using = f"{self.seg('USING')} {using}" if using else ""
2158            return f"{direction} {this}{on}{into}{using}{group}"
2159
2160        alias = self.sql(expression, "alias")
2161        alias = f" AS {alias}" if alias else ""
2162
2163        fields = self.expressions(
2164            expression,
2165            "fields",
2166            sep=" ",
2167            dynamic=True,
2168            new_line=True,
2169            skip_first=True,
2170            skip_last=True,
2171        )
2172
2173        include_nulls = expression.args.get("include_nulls")
2174        if include_nulls is not None:
2175            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2176        else:
2177            nulls = ""
2178
2179        default_on_null = self.sql(expression, "default_on_null")
2180        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2181        return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2182
2183    def version_sql(self, expression: exp.Version) -> str:
2184        this = f"FOR {expression.name}"
2185        kind = expression.text("kind")
2186        expr = self.sql(expression, "expression")
2187        return f"{this} {kind} {expr}"
2188
2189    def tuple_sql(self, expression: exp.Tuple) -> str:
2190        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2191
2192    def update_sql(self, expression: exp.Update) -> str:
2193        this = self.sql(expression, "this")
2194        set_sql = self.expressions(expression, flat=True)
2195        from_sql = self.sql(expression, "from")
2196        where_sql = self.sql(expression, "where")
2197        returning = self.sql(expression, "returning")
2198        order = self.sql(expression, "order")
2199        limit = self.sql(expression, "limit")
2200        if self.RETURNING_END:
2201            expression_sql = f"{from_sql}{where_sql}{returning}"
2202        else:
2203            expression_sql = f"{returning}{from_sql}{where_sql}"
2204        options = self.expressions(expression, key="options")
2205        options = f" OPTION({options})" if options else ""
2206        sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}{options}"
2207        return self.prepend_ctes(expression, sql)
2208
2209    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2210        values_as_table = values_as_table and self.VALUES_AS_TABLE
2211
2212        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2213        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2214            args = self.expressions(expression)
2215            alias = self.sql(expression, "alias")
2216            values = f"VALUES{self.seg('')}{args}"
2217            values = (
2218                f"({values})"
2219                if self.WRAP_DERIVED_VALUES
2220                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2221                else values
2222            )
2223            return f"{values} AS {alias}" if alias else values
2224
2225        # Converts `VALUES...` expression into a series of select unions.
2226        alias_node = expression.args.get("alias")
2227        column_names = alias_node and alias_node.columns
2228
2229        selects: t.List[exp.Query] = []
2230
2231        for i, tup in enumerate(expression.expressions):
2232            row = tup.expressions
2233
2234            if i == 0 and column_names:
2235                row = [
2236                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2237                ]
2238
2239            selects.append(exp.Select(expressions=row))
2240
2241        if self.pretty:
2242            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2243            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2244            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2245            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2246            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2247
2248        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2249        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2250        return f"({unions}){alias}"
2251
2252    def var_sql(self, expression: exp.Var) -> str:
2253        return self.sql(expression, "this")
2254
2255    @unsupported_args("expressions")
2256    def into_sql(self, expression: exp.Into) -> str:
2257        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2258        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2259        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2260
2261    def from_sql(self, expression: exp.From) -> str:
2262        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2263
2264    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2265        grouping_sets = self.expressions(expression, indent=False)
2266        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2267
2268    def rollup_sql(self, expression: exp.Rollup) -> str:
2269        expressions = self.expressions(expression, indent=False)
2270        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2271
2272    def cube_sql(self, expression: exp.Cube) -> str:
2273        expressions = self.expressions(expression, indent=False)
2274        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2275
2276    def group_sql(self, expression: exp.Group) -> str:
2277        group_by_all = expression.args.get("all")
2278        if group_by_all is True:
2279            modifier = " ALL"
2280        elif group_by_all is False:
2281            modifier = " DISTINCT"
2282        else:
2283            modifier = ""
2284
2285        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2286
2287        grouping_sets = self.expressions(expression, key="grouping_sets")
2288        cube = self.expressions(expression, key="cube")
2289        rollup = self.expressions(expression, key="rollup")
2290
2291        groupings = csv(
2292            self.seg(grouping_sets) if grouping_sets else "",
2293            self.seg(cube) if cube else "",
2294            self.seg(rollup) if rollup else "",
2295            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2296            sep=self.GROUPINGS_SEP,
2297        )
2298
2299        if (
2300            expression.expressions
2301            and groupings
2302            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2303        ):
2304            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2305
2306        return f"{group_by}{groupings}"
2307
2308    def having_sql(self, expression: exp.Having) -> str:
2309        this = self.indent(self.sql(expression, "this"))
2310        return f"{self.seg('HAVING')}{self.sep()}{this}"
2311
2312    def connect_sql(self, expression: exp.Connect) -> str:
2313        start = self.sql(expression, "start")
2314        start = self.seg(f"START WITH {start}") if start else ""
2315        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2316        connect = self.sql(expression, "connect")
2317        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2318        return start + connect
2319
2320    def prior_sql(self, expression: exp.Prior) -> str:
2321        return f"PRIOR {self.sql(expression, 'this')}"
2322
2323    def join_sql(self, expression: exp.Join) -> str:
2324        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2325            side = None
2326        else:
2327            side = expression.side
2328
2329        op_sql = " ".join(
2330            op
2331            for op in (
2332                expression.method,
2333                "GLOBAL" if expression.args.get("global") else None,
2334                side,
2335                expression.kind,
2336                expression.hint if self.JOIN_HINTS else None,
2337            )
2338            if op
2339        )
2340        match_cond = self.sql(expression, "match_condition")
2341        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2342        on_sql = self.sql(expression, "on")
2343        using = expression.args.get("using")
2344
2345        if not on_sql and using:
2346            on_sql = csv(*(self.sql(column) for column in using))
2347
2348        this = expression.this
2349        this_sql = self.sql(this)
2350
2351        exprs = self.expressions(expression)
2352        if exprs:
2353            this_sql = f"{this_sql},{self.seg(exprs)}"
2354
2355        if on_sql:
2356            on_sql = self.indent(on_sql, skip_first=True)
2357            space = self.seg(" " * self.pad) if self.pretty else " "
2358            if using:
2359                on_sql = f"{space}USING ({on_sql})"
2360            else:
2361                on_sql = f"{space}ON {on_sql}"
2362        elif not op_sql:
2363            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2364                return f" {this_sql}"
2365
2366            return f", {this_sql}"
2367
2368        if op_sql != "STRAIGHT_JOIN":
2369            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2370
2371        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2372        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2373
2374    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2375        args = self.expressions(expression, flat=True)
2376        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2377        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2378
2379    def lateral_op(self, expression: exp.Lateral) -> str:
2380        cross_apply = expression.args.get("cross_apply")
2381
2382        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2383        if cross_apply is True:
2384            op = "INNER JOIN "
2385        elif cross_apply is False:
2386            op = "LEFT JOIN "
2387        else:
2388            op = ""
2389
2390        return f"{op}LATERAL"
2391
2392    def lateral_sql(self, expression: exp.Lateral) -> str:
2393        this = self.sql(expression, "this")
2394
2395        if expression.args.get("view"):
2396            alias = expression.args["alias"]
2397            columns = self.expressions(alias, key="columns", flat=True)
2398            table = f" {alias.name}" if alias.name else ""
2399            columns = f" AS {columns}" if columns else ""
2400            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2401            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2402
2403        alias = self.sql(expression, "alias")
2404        alias = f" AS {alias}" if alias else ""
2405
2406        ordinality = expression.args.get("ordinality") or ""
2407        if ordinality:
2408            ordinality = f" WITH ORDINALITY{alias}"
2409            alias = ""
2410
2411        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2412
2413    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2414        this = self.sql(expression, "this")
2415
2416        args = [
2417            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2418            for e in (expression.args.get(k) for k in ("offset", "expression"))
2419            if e
2420        ]
2421
2422        args_sql = ", ".join(self.sql(e) for e in args)
2423        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2424        expressions = self.expressions(expression, flat=True)
2425        limit_options = self.sql(expression, "limit_options")
2426        expressions = f" BY {expressions}" if expressions else ""
2427
2428        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2429
2430    def offset_sql(self, expression: exp.Offset) -> str:
2431        this = self.sql(expression, "this")
2432        value = expression.expression
2433        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2434        expressions = self.expressions(expression, flat=True)
2435        expressions = f" BY {expressions}" if expressions else ""
2436        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2437
2438    def setitem_sql(self, expression: exp.SetItem) -> str:
2439        kind = self.sql(expression, "kind")
2440        kind = f"{kind} " if kind else ""
2441        this = self.sql(expression, "this")
2442        expressions = self.expressions(expression)
2443        collate = self.sql(expression, "collate")
2444        collate = f" COLLATE {collate}" if collate else ""
2445        global_ = "GLOBAL " if expression.args.get("global") else ""
2446        return f"{global_}{kind}{this}{expressions}{collate}"
2447
2448    def set_sql(self, expression: exp.Set) -> str:
2449        expressions = f" {self.expressions(expression, flat=True)}"
2450        tag = " TAG" if expression.args.get("tag") else ""
2451        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2452
2453    def queryband_sql(self, expression: exp.QueryBand) -> str:
2454        this = self.sql(expression, "this")
2455        update = " UPDATE" if expression.args.get("update") else ""
2456        scope = self.sql(expression, "scope")
2457        scope = f" FOR {scope}" if scope else ""
2458
2459        return f"QUERY_BAND = {this}{update}{scope}"
2460
2461    def pragma_sql(self, expression: exp.Pragma) -> str:
2462        return f"PRAGMA {self.sql(expression, 'this')}"
2463
2464    def lock_sql(self, expression: exp.Lock) -> str:
2465        if not self.LOCKING_READS_SUPPORTED:
2466            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2467            return ""
2468
2469        update = expression.args["update"]
2470        key = expression.args.get("key")
2471        if update:
2472            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2473        else:
2474            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2475        expressions = self.expressions(expression, flat=True)
2476        expressions = f" OF {expressions}" if expressions else ""
2477        wait = expression.args.get("wait")
2478
2479        if wait is not None:
2480            if isinstance(wait, exp.Literal):
2481                wait = f" WAIT {self.sql(wait)}"
2482            else:
2483                wait = " NOWAIT" if wait else " SKIP LOCKED"
2484
2485        return f"{lock_type}{expressions}{wait or ''}"
2486
2487    def literal_sql(self, expression: exp.Literal) -> str:
2488        text = expression.this or ""
2489        if expression.is_string:
2490            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2491        return text
2492
2493    def escape_str(
2494        self,
2495        text: str,
2496        escape_backslash: bool = True,
2497        delimiter: t.Optional[str] = None,
2498        escaped_delimiter: t.Optional[str] = None,
2499    ) -> str:
2500        if self.dialect.ESCAPED_SEQUENCES:
2501            to_escaped = self.dialect.ESCAPED_SEQUENCES
2502            text = "".join(
2503                to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text
2504            )
2505
2506        delimiter = delimiter or self.dialect.QUOTE_END
2507        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2508
2509        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2510
2511    def loaddata_sql(self, expression: exp.LoadData) -> str:
2512        local = " LOCAL" if expression.args.get("local") else ""
2513        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2514        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2515        this = f" INTO TABLE {self.sql(expression, 'this')}"
2516        partition = self.sql(expression, "partition")
2517        partition = f" {partition}" if partition else ""
2518        input_format = self.sql(expression, "input_format")
2519        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2520        serde = self.sql(expression, "serde")
2521        serde = f" SERDE {serde}" if serde else ""
2522        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2523
2524    def null_sql(self, *_) -> str:
2525        return "NULL"
2526
2527    def boolean_sql(self, expression: exp.Boolean) -> str:
2528        return "TRUE" if expression.this else "FALSE"
2529
2530    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2531        this = self.sql(expression, "this")
2532        this = f"{this} " if this else this
2533        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2534        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
2535
2536    def withfill_sql(self, expression: exp.WithFill) -> str:
2537        from_sql = self.sql(expression, "from")
2538        from_sql = f" FROM {from_sql}" if from_sql else ""
2539        to_sql = self.sql(expression, "to")
2540        to_sql = f" TO {to_sql}" if to_sql else ""
2541        step_sql = self.sql(expression, "step")
2542        step_sql = f" STEP {step_sql}" if step_sql else ""
2543        interpolated_values = [
2544            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2545            if isinstance(e, exp.Alias)
2546            else self.sql(e, "this")
2547            for e in expression.args.get("interpolate") or []
2548        ]
2549        interpolate = (
2550            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2551        )
2552        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2553
2554    def cluster_sql(self, expression: exp.Cluster) -> str:
2555        return self.op_expressions("CLUSTER BY", expression)
2556
2557    def distribute_sql(self, expression: exp.Distribute) -> str:
2558        return self.op_expressions("DISTRIBUTE BY", expression)
2559
2560    def sort_sql(self, expression: exp.Sort) -> str:
2561        return self.op_expressions("SORT BY", expression)
2562
2563    def ordered_sql(self, expression: exp.Ordered) -> str:
2564        desc = expression.args.get("desc")
2565        asc = not desc
2566
2567        nulls_first = expression.args.get("nulls_first")
2568        nulls_last = not nulls_first
2569        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2570        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2571        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2572
2573        this = self.sql(expression, "this")
2574
2575        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2576        nulls_sort_change = ""
2577        if nulls_first and (
2578            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2579        ):
2580            nulls_sort_change = " NULLS FIRST"
2581        elif (
2582            nulls_last
2583            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2584            and not nulls_are_last
2585        ):
2586            nulls_sort_change = " NULLS LAST"
2587
2588        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2589        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2590            window = expression.find_ancestor(exp.Window, exp.Select)
2591            if isinstance(window, exp.Window) and window.args.get("spec"):
2592                self.unsupported(
2593                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2594                )
2595                nulls_sort_change = ""
2596            elif self.NULL_ORDERING_SUPPORTED is False and (
2597                (asc and nulls_sort_change == " NULLS LAST")
2598                or (desc and nulls_sort_change == " NULLS FIRST")
2599            ):
2600                # BigQuery does not allow these ordering/nulls combinations when used under
2601                # an aggregation func or under a window containing one
2602                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2603
2604                if isinstance(ancestor, exp.Window):
2605                    ancestor = ancestor.this
2606                if isinstance(ancestor, exp.AggFunc):
2607                    self.unsupported(
2608                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2609                    )
2610                    nulls_sort_change = ""
2611            elif self.NULL_ORDERING_SUPPORTED is None:
2612                if expression.this.is_int:
2613                    self.unsupported(
2614                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2615                    )
2616                elif not isinstance(expression.this, exp.Rand):
2617                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2618                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2619                nulls_sort_change = ""
2620
2621        with_fill = self.sql(expression, "with_fill")
2622        with_fill = f" {with_fill}" if with_fill else ""
2623
2624        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2625
2626    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2627        window_frame = self.sql(expression, "window_frame")
2628        window_frame = f"{window_frame} " if window_frame else ""
2629
2630        this = self.sql(expression, "this")
2631
2632        return f"{window_frame}{this}"
2633
2634    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2635        partition = self.partition_by_sql(expression)
2636        order = self.sql(expression, "order")
2637        measures = self.expressions(expression, key="measures")
2638        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2639        rows = self.sql(expression, "rows")
2640        rows = self.seg(rows) if rows else ""
2641        after = self.sql(expression, "after")
2642        after = self.seg(after) if after else ""
2643        pattern = self.sql(expression, "pattern")
2644        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2645        definition_sqls = [
2646            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2647            for definition in expression.args.get("define", [])
2648        ]
2649        definitions = self.expressions(sqls=definition_sqls)
2650        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2651        body = "".join(
2652            (
2653                partition,
2654                order,
2655                measures,
2656                rows,
2657                after,
2658                pattern,
2659                define,
2660            )
2661        )
2662        alias = self.sql(expression, "alias")
2663        alias = f" {alias}" if alias else ""
2664        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2665
2666    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2667        limit = expression.args.get("limit")
2668
2669        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2670            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2671        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2672            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2673
2674        return csv(
2675            *sqls,
2676            *[self.sql(join) for join in expression.args.get("joins") or []],
2677            self.sql(expression, "match"),
2678            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2679            self.sql(expression, "prewhere"),
2680            self.sql(expression, "where"),
2681            self.sql(expression, "connect"),
2682            self.sql(expression, "group"),
2683            self.sql(expression, "having"),
2684            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2685            self.sql(expression, "order"),
2686            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2687            *self.after_limit_modifiers(expression),
2688            self.options_modifier(expression),
2689            self.for_modifiers(expression),
2690            sep="",
2691        )
2692
2693    def options_modifier(self, expression: exp.Expression) -> str:
2694        options = self.expressions(expression, key="options")
2695        return f" {options}" if options else ""
2696
2697    def for_modifiers(self, expression: exp.Expression) -> str:
2698        for_modifiers = self.expressions(expression, key="for")
2699        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
2700
2701    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2702        self.unsupported("Unsupported query option.")
2703        return ""
2704
2705    def offset_limit_modifiers(
2706        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2707    ) -> t.List[str]:
2708        return [
2709            self.sql(expression, "offset") if fetch else self.sql(limit),
2710            self.sql(limit) if fetch else self.sql(expression, "offset"),
2711        ]
2712
2713    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2714        locks = self.expressions(expression, key="locks", sep=" ")
2715        locks = f" {locks}" if locks else ""
2716        return [locks, self.sql(expression, "sample")]
2717
2718    def select_sql(self, expression: exp.Select) -> str:
2719        into = expression.args.get("into")
2720        if not self.SUPPORTS_SELECT_INTO and into:
2721            into.pop()
2722
2723        hint = self.sql(expression, "hint")
2724        distinct = self.sql(expression, "distinct")
2725        distinct = f" {distinct}" if distinct else ""
2726        kind = self.sql(expression, "kind")
2727
2728        limit = expression.args.get("limit")
2729        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2730            top = self.limit_sql(limit, top=True)
2731            limit.pop()
2732        else:
2733            top = ""
2734
2735        expressions = self.expressions(expression)
2736
2737        if kind:
2738            if kind in self.SELECT_KINDS:
2739                kind = f" AS {kind}"
2740            else:
2741                if kind == "STRUCT":
2742                    expressions = self.expressions(
2743                        sqls=[
2744                            self.sql(
2745                                exp.Struct(
2746                                    expressions=[
2747                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2748                                        if isinstance(e, exp.Alias)
2749                                        else e
2750                                        for e in expression.expressions
2751                                    ]
2752                                )
2753                            )
2754                        ]
2755                    )
2756                kind = ""
2757
2758        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2759        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2760
2761        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2762        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2763        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2764        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2765        sql = self.query_modifiers(
2766            expression,
2767            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2768            self.sql(expression, "into", comment=False),
2769            self.sql(expression, "from", comment=False),
2770        )
2771
2772        # If both the CTE and SELECT clauses have comments, generate the latter earlier
2773        if expression.args.get("with"):
2774            sql = self.maybe_comment(sql, expression)
2775            expression.pop_comments()
2776
2777        sql = self.prepend_ctes(expression, sql)
2778
2779        if not self.SUPPORTS_SELECT_INTO and into:
2780            if into.args.get("temporary"):
2781                table_kind = " TEMPORARY"
2782            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
2783                table_kind = " UNLOGGED"
2784            else:
2785                table_kind = ""
2786            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
2787
2788        return sql
2789
2790    def schema_sql(self, expression: exp.Schema) -> str:
2791        this = self.sql(expression, "this")
2792        sql = self.schema_columns_sql(expression)
2793        return f"{this} {sql}" if this and sql else this or sql
2794
2795    def schema_columns_sql(self, expression: exp.Schema) -> str:
2796        if expression.expressions:
2797            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2798        return ""
2799
2800    def star_sql(self, expression: exp.Star) -> str:
2801        except_ = self.expressions(expression, key="except", flat=True)
2802        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2803        replace = self.expressions(expression, key="replace", flat=True)
2804        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
2805        rename = self.expressions(expression, key="rename", flat=True)
2806        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
2807        return f"*{except_}{replace}{rename}"
2808
2809    def parameter_sql(self, expression: exp.Parameter) -> str:
2810        this = self.sql(expression, "this")
2811        return f"{self.PARAMETER_TOKEN}{this}"
2812
2813    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2814        this = self.sql(expression, "this")
2815        kind = expression.text("kind")
2816        if kind:
2817            kind = f"{kind}."
2818        return f"@@{kind}{this}"
2819
2820    def placeholder_sql(self, expression: exp.Placeholder) -> str:
2821        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
2822
2823    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2824        alias = self.sql(expression, "alias")
2825        alias = f"{sep}{alias}" if alias else ""
2826        sample = self.sql(expression, "sample")
2827        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
2828            alias = f"{sample}{alias}"
2829
2830            # Set to None so it's not generated again by self.query_modifiers()
2831            expression.set("sample", None)
2832
2833        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2834        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2835        return self.prepend_ctes(expression, sql)
2836
2837    def qualify_sql(self, expression: exp.Qualify) -> str:
2838        this = self.indent(self.sql(expression, "this"))
2839        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
2840
2841    def unnest_sql(self, expression: exp.Unnest) -> str:
2842        args = self.expressions(expression, flat=True)
2843
2844        alias = expression.args.get("alias")
2845        offset = expression.args.get("offset")
2846
2847        if self.UNNEST_WITH_ORDINALITY:
2848            if alias and isinstance(offset, exp.Expression):
2849                alias.append("columns", offset)
2850
2851        if alias and self.dialect.UNNEST_COLUMN_ONLY:
2852            columns = alias.columns
2853            alias = self.sql(columns[0]) if columns else ""
2854        else:
2855            alias = self.sql(alias)
2856
2857        alias = f" AS {alias}" if alias else alias
2858        if self.UNNEST_WITH_ORDINALITY:
2859            suffix = f" WITH ORDINALITY{alias}" if offset else alias
2860        else:
2861            if isinstance(offset, exp.Expression):
2862                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
2863            elif offset:
2864                suffix = f"{alias} WITH OFFSET"
2865            else:
2866                suffix = alias
2867
2868        return f"UNNEST({args}){suffix}"
2869
2870    def prewhere_sql(self, expression: exp.PreWhere) -> str:
2871        return ""
2872
2873    def where_sql(self, expression: exp.Where) -> str:
2874        this = self.indent(self.sql(expression, "this"))
2875        return f"{self.seg('WHERE')}{self.sep()}{this}"
2876
2877    def window_sql(self, expression: exp.Window) -> str:
2878        this = self.sql(expression, "this")
2879        partition = self.partition_by_sql(expression)
2880        order = expression.args.get("order")
2881        order = self.order_sql(order, flat=True) if order else ""
2882        spec = self.sql(expression, "spec")
2883        alias = self.sql(expression, "alias")
2884        over = self.sql(expression, "over") or "OVER"
2885
2886        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
2887
2888        first = expression.args.get("first")
2889        if first is None:
2890            first = ""
2891        else:
2892            first = "FIRST" if first else "LAST"
2893
2894        if not partition and not order and not spec and alias:
2895            return f"{this} {alias}"
2896
2897        args = self.format_args(
2898            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
2899        )
2900        return f"{this} ({args})"
2901
2902    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
2903        partition = self.expressions(expression, key="partition_by", flat=True)
2904        return f"PARTITION BY {partition}" if partition else ""
2905
2906    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
2907        kind = self.sql(expression, "kind")
2908        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
2909        end = (
2910            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
2911            or "CURRENT ROW"
2912        )
2913
2914        window_spec = f"{kind} BETWEEN {start} AND {end}"
2915
2916        exclude = self.sql(expression, "exclude")
2917        if exclude:
2918            if self.SUPPORTS_WINDOW_EXCLUDE:
2919                window_spec += f" EXCLUDE {exclude}"
2920            else:
2921                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
2922
2923        return window_spec
2924
2925    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
2926        this = self.sql(expression, "this")
2927        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
2928        return f"{this} WITHIN GROUP ({expression_sql})"
2929
2930    def between_sql(self, expression: exp.Between) -> str:
2931        this = self.sql(expression, "this")
2932        low = self.sql(expression, "low")
2933        high = self.sql(expression, "high")
2934        symmetric = expression.args.get("symmetric")
2935
2936        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
2937            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
2938
2939        flag = (
2940            " SYMMETRIC"
2941            if symmetric
2942            else " ASYMMETRIC"
2943            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
2944            else ""  # silently drop ASYMMETRIC – semantics identical
2945        )
2946        return f"{this} BETWEEN{flag} {low} AND {high}"
2947
2948    def bracket_offset_expressions(
2949        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
2950    ) -> t.List[exp.Expression]:
2951        return apply_index_offset(
2952            expression.this,
2953            expression.expressions,
2954            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
2955            dialect=self.dialect,
2956        )
2957
2958    def bracket_sql(self, expression: exp.Bracket) -> str:
2959        expressions = self.bracket_offset_expressions(expression)
2960        expressions_sql = ", ".join(self.sql(e) for e in expressions)
2961        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
2962
2963    def all_sql(self, expression: exp.All) -> str:
2964        this = self.sql(expression, "this")
2965        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
2966            this = self.wrap(this)
2967        return f"ALL {this}"
2968
2969    def any_sql(self, expression: exp.Any) -> str:
2970        this = self.sql(expression, "this")
2971        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
2972            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
2973                this = self.wrap(this)
2974            return f"ANY{this}"
2975        return f"ANY {this}"
2976
2977    def exists_sql(self, expression: exp.Exists) -> str:
2978        return f"EXISTS{self.wrap(expression)}"
2979
2980    def case_sql(self, expression: exp.Case) -> str:
2981        this = self.sql(expression, "this")
2982        statements = [f"CASE {this}" if this else "CASE"]
2983
2984        for e in expression.args["ifs"]:
2985            statements.append(f"WHEN {self.sql(e, 'this')}")
2986            statements.append(f"THEN {self.sql(e, 'true')}")
2987
2988        default = self.sql(expression, "default")
2989
2990        if default:
2991            statements.append(f"ELSE {default}")
2992
2993        statements.append("END")
2994
2995        if self.pretty and self.too_wide(statements):
2996            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
2997
2998        return " ".join(statements)
2999
3000    def constraint_sql(self, expression: exp.Constraint) -> str:
3001        this = self.sql(expression, "this")
3002        expressions = self.expressions(expression, flat=True)
3003        return f"CONSTRAINT {this} {expressions}"
3004
3005    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3006        order = expression.args.get("order")
3007        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3008        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3009
3010    def extract_sql(self, expression: exp.Extract) -> str:
3011        from sqlglot.dialects.dialect import map_date_part
3012
3013        this = (
3014            map_date_part(expression.this, self.dialect)
3015            if self.NORMALIZE_EXTRACT_DATE_PARTS
3016            else expression.this
3017        )
3018        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3019        expression_sql = self.sql(expression, "expression")
3020
3021        return f"EXTRACT({this_sql} FROM {expression_sql})"
3022
3023    def trim_sql(self, expression: exp.Trim) -> str:
3024        trim_type = self.sql(expression, "position")
3025
3026        if trim_type == "LEADING":
3027            func_name = "LTRIM"
3028        elif trim_type == "TRAILING":
3029            func_name = "RTRIM"
3030        else:
3031            func_name = "TRIM"
3032
3033        return self.func(func_name, expression.this, expression.expression)
3034
3035    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
3036        args = expression.expressions
3037        if isinstance(expression, exp.ConcatWs):
3038            args = args[1:]  # Skip the delimiter
3039
3040        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3041            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
3042
3043        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
3044
3045            def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression:
3046                if not e.type:
3047                    from sqlglot.optimizer.annotate_types import annotate_types
3048
3049                    e = annotate_types(e, dialect=self.dialect)
3050
3051                if e.is_string or e.is_type(exp.DataType.Type.ARRAY):
3052                    return e
3053
3054                return exp.func("coalesce", e, exp.Literal.string(""))
3055
3056            args = [_wrap_with_coalesce(e) for e in args]
3057
3058        return args
3059
3060    def concat_sql(self, expression: exp.Concat) -> str:
3061        expressions = self.convert_concat_args(expression)
3062
3063        # Some dialects don't allow a single-argument CONCAT call
3064        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3065            return self.sql(expressions[0])
3066
3067        return self.func("CONCAT", *expressions)
3068
3069    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3070        return self.func(
3071            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3072        )
3073
3074    def check_sql(self, expression: exp.Check) -> str:
3075        this = self.sql(expression, key="this")
3076        return f"CHECK ({this})"
3077
3078    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3079        expressions = self.expressions(expression, flat=True)
3080        expressions = f" ({expressions})" if expressions else ""
3081        reference = self.sql(expression, "reference")
3082        reference = f" {reference}" if reference else ""
3083        delete = self.sql(expression, "delete")
3084        delete = f" ON DELETE {delete}" if delete else ""
3085        update = self.sql(expression, "update")
3086        update = f" ON UPDATE {update}" if update else ""
3087        options = self.expressions(expression, key="options", flat=True, sep=" ")
3088        options = f" {options}" if options else ""
3089        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3090
3091    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3092        expressions = self.expressions(expression, flat=True)
3093        include = self.sql(expression, "include")
3094        options = self.expressions(expression, key="options", flat=True, sep=" ")
3095        options = f" {options}" if options else ""
3096        return f"PRIMARY KEY ({expressions}){include}{options}"
3097
3098    def if_sql(self, expression: exp.If) -> str:
3099        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3100
3101    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3102        if self.MATCH_AGAINST_TABLE_PREFIX:
3103            expressions = []
3104            for expr in expression.expressions:
3105                if isinstance(expr, exp.Table):
3106                    expressions.append(f"TABLE {self.sql(expr)}")
3107                else:
3108                    expressions.append(expr)
3109        else:
3110            expressions = expression.expressions
3111
3112        modifier = expression.args.get("modifier")
3113        modifier = f" {modifier}" if modifier else ""
3114        return (
3115            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3116        )
3117
3118    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3119        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3120
3121    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3122        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3123
3124        if expression.args.get("escape"):
3125            path = self.escape_str(path)
3126
3127        if self.QUOTE_JSON_PATH:
3128            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3129
3130        return path
3131
3132    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3133        if isinstance(expression, exp.JSONPathPart):
3134            transform = self.TRANSFORMS.get(expression.__class__)
3135            if not callable(transform):
3136                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3137                return ""
3138
3139            return transform(self, expression)
3140
3141        if isinstance(expression, int):
3142            return str(expression)
3143
3144        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3145            escaped = expression.replace("'", "\\'")
3146            escaped = f"\\'{expression}\\'"
3147        else:
3148            escaped = expression.replace('"', '\\"')
3149            escaped = f'"{escaped}"'
3150
3151        return escaped
3152
3153    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3154        return f"{self.sql(expression, 'this')} FORMAT JSON"
3155
3156    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3157        # Output the Teradata column FORMAT override.
3158        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3159        this = self.sql(expression, "this")
3160        fmt = self.sql(expression, "format")
3161        return f"{this} (FORMAT {fmt})"
3162
3163    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3164        null_handling = expression.args.get("null_handling")
3165        null_handling = f" {null_handling}" if null_handling else ""
3166
3167        unique_keys = expression.args.get("unique_keys")
3168        if unique_keys is not None:
3169            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3170        else:
3171            unique_keys = ""
3172
3173        return_type = self.sql(expression, "return_type")
3174        return_type = f" RETURNING {return_type}" if return_type else ""
3175        encoding = self.sql(expression, "encoding")
3176        encoding = f" ENCODING {encoding}" if encoding else ""
3177
3178        return self.func(
3179            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3180            *expression.expressions,
3181            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3182        )
3183
3184    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3185        return self.jsonobject_sql(expression)
3186
3187    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3188        null_handling = expression.args.get("null_handling")
3189        null_handling = f" {null_handling}" if null_handling else ""
3190        return_type = self.sql(expression, "return_type")
3191        return_type = f" RETURNING {return_type}" if return_type else ""
3192        strict = " STRICT" if expression.args.get("strict") else ""
3193        return self.func(
3194            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3195        )
3196
3197    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3198        this = self.sql(expression, "this")
3199        order = self.sql(expression, "order")
3200        null_handling = expression.args.get("null_handling")
3201        null_handling = f" {null_handling}" if null_handling else ""
3202        return_type = self.sql(expression, "return_type")
3203        return_type = f" RETURNING {return_type}" if return_type else ""
3204        strict = " STRICT" if expression.args.get("strict") else ""
3205        return self.func(
3206            "JSON_ARRAYAGG",
3207            this,
3208            suffix=f"{order}{null_handling}{return_type}{strict})",
3209        )
3210
3211    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3212        path = self.sql(expression, "path")
3213        path = f" PATH {path}" if path else ""
3214        nested_schema = self.sql(expression, "nested_schema")
3215
3216        if nested_schema:
3217            return f"NESTED{path} {nested_schema}"
3218
3219        this = self.sql(expression, "this")
3220        kind = self.sql(expression, "kind")
3221        kind = f" {kind}" if kind else ""
3222
3223        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3224        return f"{this}{kind}{path}{ordinality}"
3225
3226    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3227        return self.func("COLUMNS", *expression.expressions)
3228
3229    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3230        this = self.sql(expression, "this")
3231        path = self.sql(expression, "path")
3232        path = f", {path}" if path else ""
3233        error_handling = expression.args.get("error_handling")
3234        error_handling = f" {error_handling}" if error_handling else ""
3235        empty_handling = expression.args.get("empty_handling")
3236        empty_handling = f" {empty_handling}" if empty_handling else ""
3237        schema = self.sql(expression, "schema")
3238        return self.func(
3239            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3240        )
3241
3242    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3243        this = self.sql(expression, "this")
3244        kind = self.sql(expression, "kind")
3245        path = self.sql(expression, "path")
3246        path = f" {path}" if path else ""
3247        as_json = " AS JSON" if expression.args.get("as_json") else ""
3248        return f"{this} {kind}{path}{as_json}"
3249
3250    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3251        this = self.sql(expression, "this")
3252        path = self.sql(expression, "path")
3253        path = f", {path}" if path else ""
3254        expressions = self.expressions(expression)
3255        with_ = (
3256            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3257            if expressions
3258            else ""
3259        )
3260        return f"OPENJSON({this}{path}){with_}"
3261
3262    def in_sql(self, expression: exp.In) -> str:
3263        query = expression.args.get("query")
3264        unnest = expression.args.get("unnest")
3265        field = expression.args.get("field")
3266        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3267
3268        if query:
3269            in_sql = self.sql(query)
3270        elif unnest:
3271            in_sql = self.in_unnest_op(unnest)
3272        elif field:
3273            in_sql = self.sql(field)
3274        else:
3275            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3276
3277        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3278
3279    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3280        return f"(SELECT {self.sql(unnest)})"
3281
3282    def interval_sql(self, expression: exp.Interval) -> str:
3283        unit_expression = expression.args.get("unit")
3284        unit = self.sql(unit_expression) if unit_expression else ""
3285        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3286            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3287        unit = f" {unit}" if unit else ""
3288
3289        if self.SINGLE_STRING_INTERVAL:
3290            this = expression.this.name if expression.this else ""
3291            if this:
3292                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3293                    return f"INTERVAL '{this}'{unit}"
3294                return f"INTERVAL '{this}{unit}'"
3295            return f"INTERVAL{unit}"
3296
3297        this = self.sql(expression, "this")
3298        if this:
3299            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3300            this = f" {this}" if unwrapped else f" ({this})"
3301
3302        return f"INTERVAL{this}{unit}"
3303
3304    def return_sql(self, expression: exp.Return) -> str:
3305        return f"RETURN {self.sql(expression, 'this')}"
3306
3307    def reference_sql(self, expression: exp.Reference) -> str:
3308        this = self.sql(expression, "this")
3309        expressions = self.expressions(expression, flat=True)
3310        expressions = f"({expressions})" if expressions else ""
3311        options = self.expressions(expression, key="options", flat=True, sep=" ")
3312        options = f" {options}" if options else ""
3313        return f"REFERENCES {this}{expressions}{options}"
3314
3315    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3316        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3317        parent = expression.parent
3318        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3319        return self.func(
3320            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3321        )
3322
3323    def paren_sql(self, expression: exp.Paren) -> str:
3324        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3325        return f"({sql}{self.seg(')', sep='')}"
3326
3327    def neg_sql(self, expression: exp.Neg) -> str:
3328        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3329        this_sql = self.sql(expression, "this")
3330        sep = " " if this_sql[0] == "-" else ""
3331        return f"-{sep}{this_sql}"
3332
3333    def not_sql(self, expression: exp.Not) -> str:
3334        return f"NOT {self.sql(expression, 'this')}"
3335
3336    def alias_sql(self, expression: exp.Alias) -> str:
3337        alias = self.sql(expression, "alias")
3338        alias = f" AS {alias}" if alias else ""
3339        return f"{self.sql(expression, 'this')}{alias}"
3340
3341    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3342        alias = expression.args["alias"]
3343
3344        parent = expression.parent
3345        pivot = parent and parent.parent
3346
3347        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3348            identifier_alias = isinstance(alias, exp.Identifier)
3349            literal_alias = isinstance(alias, exp.Literal)
3350
3351            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3352                alias.replace(exp.Literal.string(alias.output_name))
3353            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3354                alias.replace(exp.to_identifier(alias.output_name))
3355
3356        return self.alias_sql(expression)
3357
3358    def aliases_sql(self, expression: exp.Aliases) -> str:
3359        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3360
3361    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3362        this = self.sql(expression, "this")
3363        index = self.sql(expression, "expression")
3364        return f"{this} AT {index}"
3365
3366    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3367        this = self.sql(expression, "this")
3368        zone = self.sql(expression, "zone")
3369        return f"{this} AT TIME ZONE {zone}"
3370
3371    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3372        this = self.sql(expression, "this")
3373        zone = self.sql(expression, "zone")
3374        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3375
3376    def add_sql(self, expression: exp.Add) -> str:
3377        return self.binary(expression, "+")
3378
3379    def and_sql(
3380        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3381    ) -> str:
3382        return self.connector_sql(expression, "AND", stack)
3383
3384    def or_sql(
3385        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3386    ) -> str:
3387        return self.connector_sql(expression, "OR", stack)
3388
3389    def xor_sql(
3390        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3391    ) -> str:
3392        return self.connector_sql(expression, "XOR", stack)
3393
3394    def connector_sql(
3395        self,
3396        expression: exp.Connector,
3397        op: str,
3398        stack: t.Optional[t.List[str | exp.Expression]] = None,
3399    ) -> str:
3400        if stack is not None:
3401            if expression.expressions:
3402                stack.append(self.expressions(expression, sep=f" {op} "))
3403            else:
3404                stack.append(expression.right)
3405                if expression.comments and self.comments:
3406                    for comment in expression.comments:
3407                        if comment:
3408                            op += f" /*{self.sanitize_comment(comment)}*/"
3409                stack.extend((op, expression.left))
3410            return op
3411
3412        stack = [expression]
3413        sqls: t.List[str] = []
3414        ops = set()
3415
3416        while stack:
3417            node = stack.pop()
3418            if isinstance(node, exp.Connector):
3419                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3420            else:
3421                sql = self.sql(node)
3422                if sqls and sqls[-1] in ops:
3423                    sqls[-1] += f" {sql}"
3424                else:
3425                    sqls.append(sql)
3426
3427        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3428        return sep.join(sqls)
3429
3430    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3431        return self.binary(expression, "&")
3432
3433    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3434        return self.binary(expression, "<<")
3435
3436    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3437        return f"~{self.sql(expression, 'this')}"
3438
3439    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3440        return self.binary(expression, "|")
3441
3442    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3443        return self.binary(expression, ">>")
3444
3445    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3446        return self.binary(expression, "^")
3447
3448    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3449        format_sql = self.sql(expression, "format")
3450        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3451        to_sql = self.sql(expression, "to")
3452        to_sql = f" {to_sql}" if to_sql else ""
3453        action = self.sql(expression, "action")
3454        action = f" {action}" if action else ""
3455        default = self.sql(expression, "default")
3456        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3457        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3458
3459    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3460        zone = self.sql(expression, "this")
3461        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3462
3463    def collate_sql(self, expression: exp.Collate) -> str:
3464        if self.COLLATE_IS_FUNC:
3465            return self.function_fallback_sql(expression)
3466        return self.binary(expression, "COLLATE")
3467
3468    def command_sql(self, expression: exp.Command) -> str:
3469        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3470
3471    def comment_sql(self, expression: exp.Comment) -> str:
3472        this = self.sql(expression, "this")
3473        kind = expression.args["kind"]
3474        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3475        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3476        expression_sql = self.sql(expression, "expression")
3477        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3478
3479    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3480        this = self.sql(expression, "this")
3481        delete = " DELETE" if expression.args.get("delete") else ""
3482        recompress = self.sql(expression, "recompress")
3483        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3484        to_disk = self.sql(expression, "to_disk")
3485        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3486        to_volume = self.sql(expression, "to_volume")
3487        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3488        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3489
3490    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3491        where = self.sql(expression, "where")
3492        group = self.sql(expression, "group")
3493        aggregates = self.expressions(expression, key="aggregates")
3494        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3495
3496        if not (where or group or aggregates) and len(expression.expressions) == 1:
3497            return f"TTL {self.expressions(expression, flat=True)}"
3498
3499        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3500
3501    def transaction_sql(self, expression: exp.Transaction) -> str:
3502        modes = self.expressions(expression, key="modes")
3503        modes = f" {modes}" if modes else ""
3504        return f"BEGIN{modes}"
3505
3506    def commit_sql(self, expression: exp.Commit) -> str:
3507        chain = expression.args.get("chain")
3508        if chain is not None:
3509            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3510
3511        return f"COMMIT{chain or ''}"
3512
3513    def rollback_sql(self, expression: exp.Rollback) -> str:
3514        savepoint = expression.args.get("savepoint")
3515        savepoint = f" TO {savepoint}" if savepoint else ""
3516        return f"ROLLBACK{savepoint}"
3517
3518    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3519        this = self.sql(expression, "this")
3520
3521        dtype = self.sql(expression, "dtype")
3522        if dtype:
3523            collate = self.sql(expression, "collate")
3524            collate = f" COLLATE {collate}" if collate else ""
3525            using = self.sql(expression, "using")
3526            using = f" USING {using}" if using else ""
3527            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3528            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3529
3530        default = self.sql(expression, "default")
3531        if default:
3532            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3533
3534        comment = self.sql(expression, "comment")
3535        if comment:
3536            return f"ALTER COLUMN {this} COMMENT {comment}"
3537
3538        visible = expression.args.get("visible")
3539        if visible:
3540            return f"ALTER COLUMN {this} SET {visible}"
3541
3542        allow_null = expression.args.get("allow_null")
3543        drop = expression.args.get("drop")
3544
3545        if not drop and not allow_null:
3546            self.unsupported("Unsupported ALTER COLUMN syntax")
3547
3548        if allow_null is not None:
3549            keyword = "DROP" if drop else "SET"
3550            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3551
3552        return f"ALTER COLUMN {this} DROP DEFAULT"
3553
3554    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3555        this = self.sql(expression, "this")
3556
3557        visible = expression.args.get("visible")
3558        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3559
3560        return f"ALTER INDEX {this} {visible_sql}"
3561
3562    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3563        this = self.sql(expression, "this")
3564        if not isinstance(expression.this, exp.Var):
3565            this = f"KEY DISTKEY {this}"
3566        return f"ALTER DISTSTYLE {this}"
3567
3568    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3569        compound = " COMPOUND" if expression.args.get("compound") else ""
3570        this = self.sql(expression, "this")
3571        expressions = self.expressions(expression, flat=True)
3572        expressions = f"({expressions})" if expressions else ""
3573        return f"ALTER{compound} SORTKEY {this or expressions}"
3574
3575    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
3576        if not self.RENAME_TABLE_WITH_DB:
3577            # Remove db from tables
3578            expression = expression.transform(
3579                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3580            ).assert_is(exp.AlterRename)
3581        this = self.sql(expression, "this")
3582        to_kw = " TO" if include_to else ""
3583        return f"RENAME{to_kw} {this}"
3584
3585    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3586        exists = " IF EXISTS" if expression.args.get("exists") else ""
3587        old_column = self.sql(expression, "this")
3588        new_column = self.sql(expression, "to")
3589        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3590
3591    def alterset_sql(self, expression: exp.AlterSet) -> str:
3592        exprs = self.expressions(expression, flat=True)
3593        if self.ALTER_SET_WRAPPED:
3594            exprs = f"({exprs})"
3595
3596        return f"SET {exprs}"
3597
3598    def alter_sql(self, expression: exp.Alter) -> str:
3599        actions = expression.args["actions"]
3600
3601        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3602            actions[0], exp.ColumnDef
3603        ):
3604            actions_sql = self.expressions(expression, key="actions", flat=True)
3605            actions_sql = f"ADD {actions_sql}"
3606        else:
3607            actions_list = []
3608            for action in actions:
3609                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3610                    action_sql = self.add_column_sql(action)
3611                else:
3612                    action_sql = self.sql(action)
3613                    if isinstance(action, exp.Query):
3614                        action_sql = f"AS {action_sql}"
3615
3616                actions_list.append(action_sql)
3617
3618            actions_sql = self.format_args(*actions_list).lstrip("\n")
3619
3620        exists = " IF EXISTS" if expression.args.get("exists") else ""
3621        on_cluster = self.sql(expression, "cluster")
3622        on_cluster = f" {on_cluster}" if on_cluster else ""
3623        only = " ONLY" if expression.args.get("only") else ""
3624        options = self.expressions(expression, key="options")
3625        options = f", {options}" if options else ""
3626        kind = self.sql(expression, "kind")
3627        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3628        check = " WITH CHECK" if expression.args.get("check") else ""
3629        cascade = (
3630            " CASCADE"
3631            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
3632            else ""
3633        )
3634        this = self.sql(expression, "this")
3635        this = f" {this}" if this else ""
3636
3637        return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
3638
3639    def altersession_sql(self, expression: exp.AlterSession) -> str:
3640        items_sql = self.expressions(expression, flat=True)
3641        keyword = "UNSET" if expression.args.get("unset") else "SET"
3642        return f"{keyword} {items_sql}"
3643
3644    def add_column_sql(self, expression: exp.Expression) -> str:
3645        sql = self.sql(expression)
3646        if isinstance(expression, exp.Schema):
3647            column_text = " COLUMNS"
3648        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3649            column_text = " COLUMN"
3650        else:
3651            column_text = ""
3652
3653        return f"ADD{column_text} {sql}"
3654
3655    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3656        expressions = self.expressions(expression)
3657        exists = " IF EXISTS " if expression.args.get("exists") else " "
3658        return f"DROP{exists}{expressions}"
3659
3660    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3661        return f"ADD {self.expressions(expression, indent=False)}"
3662
3663    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3664        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3665        location = self.sql(expression, "location")
3666        location = f" {location}" if location else ""
3667        return f"ADD {exists}{self.sql(expression.this)}{location}"
3668
3669    def distinct_sql(self, expression: exp.Distinct) -> str:
3670        this = self.expressions(expression, flat=True)
3671
3672        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3673            case = exp.case()
3674            for arg in expression.expressions:
3675                case = case.when(arg.is_(exp.null()), exp.null())
3676            this = self.sql(case.else_(f"({this})"))
3677
3678        this = f" {this}" if this else ""
3679
3680        on = self.sql(expression, "on")
3681        on = f" ON {on}" if on else ""
3682        return f"DISTINCT{this}{on}"
3683
3684    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3685        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
3686
3687    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3688        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
3689
3690    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3691        this_sql = self.sql(expression, "this")
3692        expression_sql = self.sql(expression, "expression")
3693        kind = "MAX" if expression.args.get("max") else "MIN"
3694        return f"{this_sql} HAVING {kind} {expression_sql}"
3695
3696    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3697        return self.sql(
3698            exp.Cast(
3699                this=exp.Div(this=expression.this, expression=expression.expression),
3700                to=exp.DataType(this=exp.DataType.Type.INT),
3701            )
3702        )
3703
3704    def dpipe_sql(self, expression: exp.DPipe) -> str:
3705        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3706            return self.func(
3707                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3708            )
3709        return self.binary(expression, "||")
3710
3711    def div_sql(self, expression: exp.Div) -> str:
3712        l, r = expression.left, expression.right
3713
3714        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3715            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3716
3717        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3718            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3719                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3720
3721        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3722            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3723                return self.sql(
3724                    exp.cast(
3725                        l / r,
3726                        to=exp.DataType.Type.BIGINT,
3727                    )
3728                )
3729
3730        return self.binary(expression, "/")
3731
3732    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3733        n = exp._wrap(expression.this, exp.Binary)
3734        d = exp._wrap(expression.expression, exp.Binary)
3735        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
3736
3737    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3738        return self.binary(expression, "OVERLAPS")
3739
3740    def distance_sql(self, expression: exp.Distance) -> str:
3741        return self.binary(expression, "<->")
3742
3743    def dot_sql(self, expression: exp.Dot) -> str:
3744        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
3745
3746    def eq_sql(self, expression: exp.EQ) -> str:
3747        return self.binary(expression, "=")
3748
3749    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3750        return self.binary(expression, ":=")
3751
3752    def escape_sql(self, expression: exp.Escape) -> str:
3753        return self.binary(expression, "ESCAPE")
3754
3755    def glob_sql(self, expression: exp.Glob) -> str:
3756        return self.binary(expression, "GLOB")
3757
3758    def gt_sql(self, expression: exp.GT) -> str:
3759        return self.binary(expression, ">")
3760
3761    def gte_sql(self, expression: exp.GTE) -> str:
3762        return self.binary(expression, ">=")
3763
3764    def is_sql(self, expression: exp.Is) -> str:
3765        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
3766            return self.sql(
3767                expression.this if expression.expression.this else exp.not_(expression.this)
3768            )
3769        return self.binary(expression, "IS")
3770
3771    def _like_sql(self, expression: exp.Like | exp.ILike) -> str:
3772        this = expression.this
3773        rhs = expression.expression
3774
3775        if isinstance(expression, exp.Like):
3776            exp_class: t.Type[exp.Like | exp.ILike] = exp.Like
3777            op = "LIKE"
3778        else:
3779            exp_class = exp.ILike
3780            op = "ILIKE"
3781
3782        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
3783            exprs = rhs.this.unnest()
3784
3785            if isinstance(exprs, exp.Tuple):
3786                exprs = exprs.expressions
3787
3788            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
3789
3790            like_expr: exp.Expression = exp_class(this=this, expression=exprs[0])
3791            for expr in exprs[1:]:
3792                like_expr = connective(like_expr, exp_class(this=this, expression=expr))
3793
3794            return self.sql(like_expr)
3795
3796        return self.binary(expression, op)
3797
3798    def like_sql(self, expression: exp.Like) -> str:
3799        return self._like_sql(expression)
3800
3801    def ilike_sql(self, expression: exp.ILike) -> str:
3802        return self._like_sql(expression)
3803
3804    def similarto_sql(self, expression: exp.SimilarTo) -> str:
3805        return self.binary(expression, "SIMILAR TO")
3806
3807    def lt_sql(self, expression: exp.LT) -> str:
3808        return self.binary(expression, "<")
3809
3810    def lte_sql(self, expression: exp.LTE) -> str:
3811        return self.binary(expression, "<=")
3812
3813    def mod_sql(self, expression: exp.Mod) -> str:
3814        return self.binary(expression, "%")
3815
3816    def mul_sql(self, expression: exp.Mul) -> str:
3817        return self.binary(expression, "*")
3818
3819    def neq_sql(self, expression: exp.NEQ) -> str:
3820        return self.binary(expression, "<>")
3821
3822    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
3823        return self.binary(expression, "IS NOT DISTINCT FROM")
3824
3825    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3826        return self.binary(expression, "IS DISTINCT FROM")
3827
3828    def slice_sql(self, expression: exp.Slice) -> str:
3829        return self.binary(expression, ":")
3830
3831    def sub_sql(self, expression: exp.Sub) -> str:
3832        return self.binary(expression, "-")
3833
3834    def trycast_sql(self, expression: exp.TryCast) -> str:
3835        return self.cast_sql(expression, safe_prefix="TRY_")
3836
3837    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
3838        return self.cast_sql(expression)
3839
3840    def try_sql(self, expression: exp.Try) -> str:
3841        if not self.TRY_SUPPORTED:
3842            self.unsupported("Unsupported TRY function")
3843            return self.sql(expression, "this")
3844
3845        return self.func("TRY", expression.this)
3846
3847    def log_sql(self, expression: exp.Log) -> str:
3848        this = expression.this
3849        expr = expression.expression
3850
3851        if self.dialect.LOG_BASE_FIRST is False:
3852            this, expr = expr, this
3853        elif self.dialect.LOG_BASE_FIRST is None and expr:
3854            if this.name in ("2", "10"):
3855                return self.func(f"LOG{this.name}", expr)
3856
3857            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
3858
3859        return self.func("LOG", this, expr)
3860
3861    def use_sql(self, expression: exp.Use) -> str:
3862        kind = self.sql(expression, "kind")
3863        kind = f" {kind}" if kind else ""
3864        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
3865        this = f" {this}" if this else ""
3866        return f"USE{kind}{this}"
3867
3868    def binary(self, expression: exp.Binary, op: str) -> str:
3869        sqls: t.List[str] = []
3870        stack: t.List[t.Union[str, exp.Expression]] = [expression]
3871        binary_type = type(expression)
3872
3873        while stack:
3874            node = stack.pop()
3875
3876            if type(node) is binary_type:
3877                op_func = node.args.get("operator")
3878                if op_func:
3879                    op = f"OPERATOR({self.sql(op_func)})"
3880
3881                stack.append(node.right)
3882                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
3883                stack.append(node.left)
3884            else:
3885                sqls.append(self.sql(node))
3886
3887        return "".join(sqls)
3888
3889    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
3890        to_clause = self.sql(expression, "to")
3891        if to_clause:
3892            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
3893
3894        return self.function_fallback_sql(expression)
3895
3896    def function_fallback_sql(self, expression: exp.Func) -> str:
3897        args = []
3898
3899        for key in expression.arg_types:
3900            arg_value = expression.args.get(key)
3901
3902            if isinstance(arg_value, list):
3903                for value in arg_value:
3904                    args.append(value)
3905            elif arg_value is not None:
3906                args.append(arg_value)
3907
3908        if self.dialect.PRESERVE_ORIGINAL_NAMES:
3909            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
3910        else:
3911            name = expression.sql_name()
3912
3913        return self.func(name, *args)
3914
3915    def func(
3916        self,
3917        name: str,
3918        *args: t.Optional[exp.Expression | str],
3919        prefix: str = "(",
3920        suffix: str = ")",
3921        normalize: bool = True,
3922    ) -> str:
3923        name = self.normalize_func(name) if normalize else name
3924        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
3925
3926    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
3927        arg_sqls = tuple(
3928            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
3929        )
3930        if self.pretty and self.too_wide(arg_sqls):
3931            return self.indent(
3932                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
3933            )
3934        return sep.join(arg_sqls)
3935
3936    def too_wide(self, args: t.Iterable) -> bool:
3937        return sum(len(arg) for arg in args) > self.max_text_width
3938
3939    def format_time(
3940        self,
3941        expression: exp.Expression,
3942        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
3943        inverse_time_trie: t.Optional[t.Dict] = None,
3944    ) -> t.Optional[str]:
3945        return format_time(
3946            self.sql(expression, "format"),
3947            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
3948            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
3949        )
3950
3951    def expressions(
3952        self,
3953        expression: t.Optional[exp.Expression] = None,
3954        key: t.Optional[str] = None,
3955        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
3956        flat: bool = False,
3957        indent: bool = True,
3958        skip_first: bool = False,
3959        skip_last: bool = False,
3960        sep: str = ", ",
3961        prefix: str = "",
3962        dynamic: bool = False,
3963        new_line: bool = False,
3964    ) -> str:
3965        expressions = expression.args.get(key or "expressions") if expression else sqls
3966
3967        if not expressions:
3968            return ""
3969
3970        if flat:
3971            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
3972
3973        num_sqls = len(expressions)
3974        result_sqls = []
3975
3976        for i, e in enumerate(expressions):
3977            sql = self.sql(e, comment=False)
3978            if not sql:
3979                continue
3980
3981            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
3982
3983            if self.pretty:
3984                if self.leading_comma:
3985                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
3986                else:
3987                    result_sqls.append(
3988                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
3989                    )
3990            else:
3991                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
3992
3993        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
3994            if new_line:
3995                result_sqls.insert(0, "")
3996                result_sqls.append("")
3997            result_sql = "\n".join(s.rstrip() for s in result_sqls)
3998        else:
3999            result_sql = "".join(result_sqls)
4000
4001        return (
4002            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4003            if indent
4004            else result_sql
4005        )
4006
4007    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
4008        flat = flat or isinstance(expression.parent, exp.Properties)
4009        expressions_sql = self.expressions(expression, flat=flat)
4010        if flat:
4011            return f"{op} {expressions_sql}"
4012        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4013
4014    def naked_property(self, expression: exp.Property) -> str:
4015        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4016        if not property_name:
4017            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4018        return f"{property_name} {self.sql(expression, 'this')}"
4019
4020    def tag_sql(self, expression: exp.Tag) -> str:
4021        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4022
4023    def token_sql(self, token_type: TokenType) -> str:
4024        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4025
4026    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4027        this = self.sql(expression, "this")
4028        expressions = self.no_identify(self.expressions, expression)
4029        expressions = (
4030            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4031        )
4032        return f"{this}{expressions}" if expressions.strip() != "" else this
4033
4034    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4035        this = self.sql(expression, "this")
4036        expressions = self.expressions(expression, flat=True)
4037        return f"{this}({expressions})"
4038
4039    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4040        return self.binary(expression, "=>")
4041
4042    def when_sql(self, expression: exp.When) -> str:
4043        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4044        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4045        condition = self.sql(expression, "condition")
4046        condition = f" AND {condition}" if condition else ""
4047
4048        then_expression = expression.args.get("then")
4049        if isinstance(then_expression, exp.Insert):
4050            this = self.sql(then_expression, "this")
4051            this = f"INSERT {this}" if this else "INSERT"
4052            then = self.sql(then_expression, "expression")
4053            then = f"{this} VALUES {then}" if then else this
4054        elif isinstance(then_expression, exp.Update):
4055            if isinstance(then_expression.args.get("expressions"), exp.Star):
4056                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4057            else:
4058                then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}"
4059        else:
4060            then = self.sql(then_expression)
4061        return f"WHEN {matched}{source}{condition} THEN {then}"
4062
4063    def whens_sql(self, expression: exp.Whens) -> str:
4064        return self.expressions(expression, sep=" ", indent=False)
4065
4066    def merge_sql(self, expression: exp.Merge) -> str:
4067        table = expression.this
4068        table_alias = ""
4069
4070        hints = table.args.get("hints")
4071        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4072            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4073            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4074
4075        this = self.sql(table)
4076        using = f"USING {self.sql(expression, 'using')}"
4077        on = f"ON {self.sql(expression, 'on')}"
4078        whens = self.sql(expression, "whens")
4079
4080        returning = self.sql(expression, "returning")
4081        if returning:
4082            whens = f"{whens}{returning}"
4083
4084        sep = self.sep()
4085
4086        return self.prepend_ctes(
4087            expression,
4088            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4089        )
4090
4091    @unsupported_args("format")
4092    def tochar_sql(self, expression: exp.ToChar) -> str:
4093        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
4094
4095    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4096        if not self.SUPPORTS_TO_NUMBER:
4097            self.unsupported("Unsupported TO_NUMBER function")
4098            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4099
4100        fmt = expression.args.get("format")
4101        if not fmt:
4102            self.unsupported("Conversion format is required for TO_NUMBER")
4103            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4104
4105        return self.func("TO_NUMBER", expression.this, fmt)
4106
4107    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4108        this = self.sql(expression, "this")
4109        kind = self.sql(expression, "kind")
4110        settings_sql = self.expressions(expression, key="settings", sep=" ")
4111        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4112        return f"{this}({kind}{args})"
4113
4114    def dictrange_sql(self, expression: exp.DictRange) -> str:
4115        this = self.sql(expression, "this")
4116        max = self.sql(expression, "max")
4117        min = self.sql(expression, "min")
4118        return f"{this}(MIN {min} MAX {max})"
4119
4120    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4121        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4122
4123    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4124        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4125
4126    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4127    def uniquekeyproperty_sql(
4128        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4129    ) -> str:
4130        return f"{prefix} ({self.expressions(expression, flat=True)})"
4131
4132    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4133    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4134        expressions = self.expressions(expression, flat=True)
4135        expressions = f" {self.wrap(expressions)}" if expressions else ""
4136        buckets = self.sql(expression, "buckets")
4137        kind = self.sql(expression, "kind")
4138        buckets = f" BUCKETS {buckets}" if buckets else ""
4139        order = self.sql(expression, "order")
4140        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4141
4142    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4143        return ""
4144
4145    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4146        expressions = self.expressions(expression, key="expressions", flat=True)
4147        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4148        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4149        buckets = self.sql(expression, "buckets")
4150        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4151
4152    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4153        this = self.sql(expression, "this")
4154        having = self.sql(expression, "having")
4155
4156        if having:
4157            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4158
4159        return self.func("ANY_VALUE", this)
4160
4161    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4162        transform = self.func("TRANSFORM", *expression.expressions)
4163        row_format_before = self.sql(expression, "row_format_before")
4164        row_format_before = f" {row_format_before}" if row_format_before else ""
4165        record_writer = self.sql(expression, "record_writer")
4166        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4167        using = f" USING {self.sql(expression, 'command_script')}"
4168        schema = self.sql(expression, "schema")
4169        schema = f" AS {schema}" if schema else ""
4170        row_format_after = self.sql(expression, "row_format_after")
4171        row_format_after = f" {row_format_after}" if row_format_after else ""
4172        record_reader = self.sql(expression, "record_reader")
4173        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4174        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4175
4176    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4177        key_block_size = self.sql(expression, "key_block_size")
4178        if key_block_size:
4179            return f"KEY_BLOCK_SIZE = {key_block_size}"
4180
4181        using = self.sql(expression, "using")
4182        if using:
4183            return f"USING {using}"
4184
4185        parser = self.sql(expression, "parser")
4186        if parser:
4187            return f"WITH PARSER {parser}"
4188
4189        comment = self.sql(expression, "comment")
4190        if comment:
4191            return f"COMMENT {comment}"
4192
4193        visible = expression.args.get("visible")
4194        if visible is not None:
4195            return "VISIBLE" if visible else "INVISIBLE"
4196
4197        engine_attr = self.sql(expression, "engine_attr")
4198        if engine_attr:
4199            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4200
4201        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4202        if secondary_engine_attr:
4203            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4204
4205        self.unsupported("Unsupported index constraint option.")
4206        return ""
4207
4208    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4209        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4210        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4211
4212    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4213        kind = self.sql(expression, "kind")
4214        kind = f"{kind} INDEX" if kind else "INDEX"
4215        this = self.sql(expression, "this")
4216        this = f" {this}" if this else ""
4217        index_type = self.sql(expression, "index_type")
4218        index_type = f" USING {index_type}" if index_type else ""
4219        expressions = self.expressions(expression, flat=True)
4220        expressions = f" ({expressions})" if expressions else ""
4221        options = self.expressions(expression, key="options", sep=" ")
4222        options = f" {options}" if options else ""
4223        return f"{kind}{this}{index_type}{expressions}{options}"
4224
4225    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4226        if self.NVL2_SUPPORTED:
4227            return self.function_fallback_sql(expression)
4228
4229        case = exp.Case().when(
4230            expression.this.is_(exp.null()).not_(copy=False),
4231            expression.args["true"],
4232            copy=False,
4233        )
4234        else_cond = expression.args.get("false")
4235        if else_cond:
4236            case.else_(else_cond, copy=False)
4237
4238        return self.sql(case)
4239
4240    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4241        this = self.sql(expression, "this")
4242        expr = self.sql(expression, "expression")
4243        iterator = self.sql(expression, "iterator")
4244        condition = self.sql(expression, "condition")
4245        condition = f" IF {condition}" if condition else ""
4246        return f"{this} FOR {expr} IN {iterator}{condition}"
4247
4248    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4249        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4250
4251    def opclass_sql(self, expression: exp.Opclass) -> str:
4252        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4253
4254    def _ml_sql(self, expression: exp.Func, name: str) -> str:
4255        model = self.sql(expression, "this")
4256        model = f"MODEL {model}"
4257        expr = expression.expression
4258        if expr:
4259            expr_sql = self.sql(expression, "expression")
4260            expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql
4261        else:
4262            expr_sql = None
4263
4264        parameters = self.sql(expression, "params_struct") or None
4265
4266        return self.func(name, model, expr_sql, parameters)
4267
4268    def predict_sql(self, expression: exp.Predict) -> str:
4269        return self._ml_sql(expression, "PREDICT")
4270
4271    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4272        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4273        return self._ml_sql(expression, name)
4274
4275    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
4276        return self._ml_sql(expression, "TRANSLATE")
4277
4278    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
4279        return self._ml_sql(expression, "FORECAST")
4280
4281    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4282        this_sql = self.sql(expression, "this")
4283        if isinstance(expression.this, exp.Table):
4284            this_sql = f"TABLE {this_sql}"
4285
4286        return self.func(
4287            "FEATURES_AT_TIME",
4288            this_sql,
4289            expression.args.get("time"),
4290            expression.args.get("num_rows"),
4291            expression.args.get("ignore_feature_nulls"),
4292        )
4293
4294    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
4295        this_sql = self.sql(expression, "this")
4296        if isinstance(expression.this, exp.Table):
4297            this_sql = f"TABLE {this_sql}"
4298
4299        query_table = self.sql(expression, "query_table")
4300        if isinstance(expression.args["query_table"], exp.Table):
4301            query_table = f"TABLE {query_table}"
4302
4303        return self.func(
4304            "VECTOR_SEARCH",
4305            this_sql,
4306            expression.args.get("column_to_search"),
4307            query_table,
4308            expression.args.get("query_column_to_search"),
4309            expression.args.get("top_k"),
4310            expression.args.get("distance_type"),
4311            expression.args.get("options"),
4312        )
4313
4314    def forin_sql(self, expression: exp.ForIn) -> str:
4315        this = self.sql(expression, "this")
4316        expression_sql = self.sql(expression, "expression")
4317        return f"FOR {this} DO {expression_sql}"
4318
4319    def refresh_sql(self, expression: exp.Refresh) -> str:
4320        this = self.sql(expression, "this")
4321        table = "" if isinstance(expression.this, exp.Literal) else "TABLE "
4322        return f"REFRESH {table}{this}"
4323
4324    def toarray_sql(self, expression: exp.ToArray) -> str:
4325        arg = expression.this
4326        if not arg.type:
4327            from sqlglot.optimizer.annotate_types import annotate_types
4328
4329            arg = annotate_types(arg, dialect=self.dialect)
4330
4331        if arg.is_type(exp.DataType.Type.ARRAY):
4332            return self.sql(arg)
4333
4334        cond_for_null = arg.is_(exp.null())
4335        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4336
4337    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4338        this = expression.this
4339        time_format = self.format_time(expression)
4340
4341        if time_format:
4342            return self.sql(
4343                exp.cast(
4344                    exp.StrToTime(this=this, format=expression.args["format"]),
4345                    exp.DataType.Type.TIME,
4346                )
4347            )
4348
4349        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4350            return self.sql(this)
4351
4352        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4353
4354    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4355        this = expression.this
4356        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4357            return self.sql(this)
4358
4359        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4360
4361    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4362        this = expression.this
4363        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4364            return self.sql(this)
4365
4366        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4367
4368    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4369        this = expression.this
4370        time_format = self.format_time(expression)
4371
4372        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4373            return self.sql(
4374                exp.cast(
4375                    exp.StrToTime(this=this, format=expression.args["format"]),
4376                    exp.DataType.Type.DATE,
4377                )
4378            )
4379
4380        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4381            return self.sql(this)
4382
4383        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4384
4385    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4386        return self.sql(
4387            exp.func(
4388                "DATEDIFF",
4389                expression.this,
4390                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4391                "day",
4392            )
4393        )
4394
4395    def lastday_sql(self, expression: exp.LastDay) -> str:
4396        if self.LAST_DAY_SUPPORTS_DATE_PART:
4397            return self.function_fallback_sql(expression)
4398
4399        unit = expression.text("unit")
4400        if unit and unit != "MONTH":
4401            self.unsupported("Date parts are not supported in LAST_DAY.")
4402
4403        return self.func("LAST_DAY", expression.this)
4404
4405    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4406        from sqlglot.dialects.dialect import unit_to_str
4407
4408        return self.func(
4409            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4410        )
4411
4412    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4413        if self.CAN_IMPLEMENT_ARRAY_ANY:
4414            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4415            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4416            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4417            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4418
4419        from sqlglot.dialects import Dialect
4420
4421        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4422        if self.dialect.__class__ != Dialect:
4423            self.unsupported("ARRAY_ANY is unsupported")
4424
4425        return self.function_fallback_sql(expression)
4426
4427    def struct_sql(self, expression: exp.Struct) -> str:
4428        expression.set(
4429            "expressions",
4430            [
4431                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4432                if isinstance(e, exp.PropertyEQ)
4433                else e
4434                for e in expression.expressions
4435            ],
4436        )
4437
4438        return self.function_fallback_sql(expression)
4439
4440    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4441        low = self.sql(expression, "this")
4442        high = self.sql(expression, "expression")
4443
4444        return f"{low} TO {high}"
4445
4446    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4447        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4448        tables = f" {self.expressions(expression)}"
4449
4450        exists = " IF EXISTS" if expression.args.get("exists") else ""
4451
4452        on_cluster = self.sql(expression, "cluster")
4453        on_cluster = f" {on_cluster}" if on_cluster else ""
4454
4455        identity = self.sql(expression, "identity")
4456        identity = f" {identity} IDENTITY" if identity else ""
4457
4458        option = self.sql(expression, "option")
4459        option = f" {option}" if option else ""
4460
4461        partition = self.sql(expression, "partition")
4462        partition = f" {partition}" if partition else ""
4463
4464        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4465
4466    # This transpiles T-SQL's CONVERT function
4467    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4468    def convert_sql(self, expression: exp.Convert) -> str:
4469        to = expression.this
4470        value = expression.expression
4471        style = expression.args.get("style")
4472        safe = expression.args.get("safe")
4473        strict = expression.args.get("strict")
4474
4475        if not to or not value:
4476            return ""
4477
4478        # Retrieve length of datatype and override to default if not specified
4479        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4480            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4481
4482        transformed: t.Optional[exp.Expression] = None
4483        cast = exp.Cast if strict else exp.TryCast
4484
4485        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4486        if isinstance(style, exp.Literal) and style.is_int:
4487            from sqlglot.dialects.tsql import TSQL
4488
4489            style_value = style.name
4490            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4491            if not converted_style:
4492                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4493
4494            fmt = exp.Literal.string(converted_style)
4495
4496            if to.this == exp.DataType.Type.DATE:
4497                transformed = exp.StrToDate(this=value, format=fmt)
4498            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4499                transformed = exp.StrToTime(this=value, format=fmt)
4500            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4501                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4502            elif to.this == exp.DataType.Type.TEXT:
4503                transformed = exp.TimeToStr(this=value, format=fmt)
4504
4505        if not transformed:
4506            transformed = cast(this=value, to=to, safe=safe)
4507
4508        return self.sql(transformed)
4509
4510    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4511        this = expression.this
4512        if isinstance(this, exp.JSONPathWildcard):
4513            this = self.json_path_part(this)
4514            return f".{this}" if this else ""
4515
4516        if self.SAFE_JSON_PATH_KEY_RE.match(this):
4517            return f".{this}"
4518
4519        this = self.json_path_part(this)
4520        return (
4521            f"[{this}]"
4522            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
4523            else f".{this}"
4524        )
4525
4526    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
4527        this = self.json_path_part(expression.this)
4528        return f"[{this}]" if this else ""
4529
4530    def _simplify_unless_literal(self, expression: E) -> E:
4531        if not isinstance(expression, exp.Literal):
4532            from sqlglot.optimizer.simplify import simplify
4533
4534            expression = simplify(expression, dialect=self.dialect)
4535
4536        return expression
4537
4538    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
4539        this = expression.this
4540        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
4541            self.unsupported(
4542                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
4543            )
4544            return self.sql(this)
4545
4546        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
4547            # The first modifier here will be the one closest to the AggFunc's arg
4548            mods = sorted(
4549                expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
4550                key=lambda x: 0
4551                if isinstance(x, exp.HavingMax)
4552                else (1 if isinstance(x, exp.Order) else 2),
4553            )
4554
4555            if mods:
4556                mod = mods[0]
4557                this = expression.__class__(this=mod.this.copy())
4558                this.meta["inline"] = True
4559                mod.this.replace(this)
4560                return self.sql(expression.this)
4561
4562            agg_func = expression.find(exp.AggFunc)
4563
4564            if agg_func:
4565                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
4566                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
4567
4568        return f"{self.sql(expression, 'this')} {text}"
4569
4570    def _replace_line_breaks(self, string: str) -> str:
4571        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
4572        if self.pretty:
4573            return string.replace("\n", self.SENTINEL_LINE_BREAK)
4574        return string
4575
4576    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4577        option = self.sql(expression, "this")
4578
4579        if expression.expressions:
4580            upper = option.upper()
4581
4582            # Snowflake FILE_FORMAT options are separated by whitespace
4583            sep = " " if upper == "FILE_FORMAT" else ", "
4584
4585            # Databricks copy/format options do not set their list of values with EQ
4586            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4587            values = self.expressions(expression, flat=True, sep=sep)
4588            return f"{option}{op}({values})"
4589
4590        value = self.sql(expression, "expression")
4591
4592        if not value:
4593            return option
4594
4595        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4596
4597        return f"{option}{op}{value}"
4598
4599    def credentials_sql(self, expression: exp.Credentials) -> str:
4600        cred_expr = expression.args.get("credentials")
4601        if isinstance(cred_expr, exp.Literal):
4602            # Redshift case: CREDENTIALS <string>
4603            credentials = self.sql(expression, "credentials")
4604            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4605        else:
4606            # Snowflake case: CREDENTIALS = (...)
4607            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4608            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4609
4610        storage = self.sql(expression, "storage")
4611        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4612
4613        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4614        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4615
4616        iam_role = self.sql(expression, "iam_role")
4617        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4618
4619        region = self.sql(expression, "region")
4620        region = f" REGION {region}" if region else ""
4621
4622        return f"{credentials}{storage}{encryption}{iam_role}{region}"
4623
4624    def copy_sql(self, expression: exp.Copy) -> str:
4625        this = self.sql(expression, "this")
4626        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4627
4628        credentials = self.sql(expression, "credentials")
4629        credentials = self.seg(credentials) if credentials else ""
4630        files = self.expressions(expression, key="files", flat=True)
4631        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
4632
4633        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4634        params = self.expressions(
4635            expression,
4636            key="params",
4637            sep=sep,
4638            new_line=True,
4639            skip_last=True,
4640            skip_first=True,
4641            indent=self.COPY_PARAMS_ARE_WRAPPED,
4642        )
4643
4644        if params:
4645            if self.COPY_PARAMS_ARE_WRAPPED:
4646                params = f" WITH ({params})"
4647            elif not self.pretty and (files or credentials):
4648                params = f" {params}"
4649
4650        return f"COPY{this}{kind} {files}{credentials}{params}"
4651
4652    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4653        return ""
4654
4655    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4656        on_sql = "ON" if expression.args.get("on") else "OFF"
4657        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4658        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4659        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4660        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4661
4662        if filter_col or retention_period:
4663            on_sql = self.func("ON", filter_col, retention_period)
4664
4665        return f"DATA_DELETION={on_sql}"
4666
4667    def maskingpolicycolumnconstraint_sql(
4668        self, expression: exp.MaskingPolicyColumnConstraint
4669    ) -> str:
4670        this = self.sql(expression, "this")
4671        expressions = self.expressions(expression, flat=True)
4672        expressions = f" USING ({expressions})" if expressions else ""
4673        return f"MASKING POLICY {this}{expressions}"
4674
4675    def gapfill_sql(self, expression: exp.GapFill) -> str:
4676        this = self.sql(expression, "this")
4677        this = f"TABLE {this}"
4678        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
4679
4680    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4681        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
4682
4683    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4684        this = self.sql(expression, "this")
4685        expr = expression.expression
4686
4687        if isinstance(expr, exp.Func):
4688            # T-SQL's CLR functions are case sensitive
4689            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4690        else:
4691            expr = self.sql(expression, "expression")
4692
4693        return self.scope_resolution(expr, this)
4694
4695    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4696        if self.PARSE_JSON_NAME is None:
4697            return self.sql(expression.this)
4698
4699        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
4700
4701    def rand_sql(self, expression: exp.Rand) -> str:
4702        lower = self.sql(expression, "lower")
4703        upper = self.sql(expression, "upper")
4704
4705        if lower and upper:
4706            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4707        return self.func("RAND", expression.this)
4708
4709    def changes_sql(self, expression: exp.Changes) -> str:
4710        information = self.sql(expression, "information")
4711        information = f"INFORMATION => {information}"
4712        at_before = self.sql(expression, "at_before")
4713        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4714        end = self.sql(expression, "end")
4715        end = f"{self.seg('')}{end}" if end else ""
4716
4717        return f"CHANGES ({information}){at_before}{end}"
4718
4719    def pad_sql(self, expression: exp.Pad) -> str:
4720        prefix = "L" if expression.args.get("is_left") else "R"
4721
4722        fill_pattern = self.sql(expression, "fill_pattern") or None
4723        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4724            fill_pattern = "' '"
4725
4726        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
4727
4728    def summarize_sql(self, expression: exp.Summarize) -> str:
4729        table = " TABLE" if expression.args.get("table") else ""
4730        return f"SUMMARIZE{table} {self.sql(expression.this)}"
4731
4732    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4733        generate_series = exp.GenerateSeries(**expression.args)
4734
4735        parent = expression.parent
4736        if isinstance(parent, (exp.Alias, exp.TableAlias)):
4737            parent = parent.parent
4738
4739        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
4740            return self.sql(exp.Unnest(expressions=[generate_series]))
4741
4742        if isinstance(parent, exp.Select):
4743            self.unsupported("GenerateSeries projection unnesting is not supported.")
4744
4745        return self.sql(generate_series)
4746
4747    def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str:
4748        exprs = expression.expressions
4749        if not self.ARRAY_CONCAT_IS_VAR_LEN:
4750            if len(exprs) == 0:
4751                rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[])
4752            else:
4753                rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs)
4754        else:
4755            rhs = self.expressions(expression)  # type: ignore
4756
4757        return self.func(name, expression.this, rhs or None)
4758
4759    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
4760        if self.SUPPORTS_CONVERT_TIMEZONE:
4761            return self.function_fallback_sql(expression)
4762
4763        source_tz = expression.args.get("source_tz")
4764        target_tz = expression.args.get("target_tz")
4765        timestamp = expression.args.get("timestamp")
4766
4767        if source_tz and timestamp:
4768            timestamp = exp.AtTimeZone(
4769                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
4770            )
4771
4772        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
4773
4774        return self.sql(expr)
4775
4776    def json_sql(self, expression: exp.JSON) -> str:
4777        this = self.sql(expression, "this")
4778        this = f" {this}" if this else ""
4779
4780        _with = expression.args.get("with")
4781
4782        if _with is None:
4783            with_sql = ""
4784        elif not _with:
4785            with_sql = " WITHOUT"
4786        else:
4787            with_sql = " WITH"
4788
4789        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
4790
4791        return f"JSON{this}{with_sql}{unique_sql}"
4792
4793    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
4794        def _generate_on_options(arg: t.Any) -> str:
4795            return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}"
4796
4797        path = self.sql(expression, "path")
4798        returning = self.sql(expression, "returning")
4799        returning = f" RETURNING {returning}" if returning else ""
4800
4801        on_condition = self.sql(expression, "on_condition")
4802        on_condition = f" {on_condition}" if on_condition else ""
4803
4804        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4805
4806    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
4807        else_ = "ELSE " if expression.args.get("else_") else ""
4808        condition = self.sql(expression, "expression")
4809        condition = f"WHEN {condition} THEN " if condition else else_
4810        insert = self.sql(expression, "this")[len("INSERT") :].strip()
4811        return f"{condition}{insert}"
4812
4813    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
4814        kind = self.sql(expression, "kind")
4815        expressions = self.seg(self.expressions(expression, sep=" "))
4816        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
4817        return res
4818
4819    def oncondition_sql(self, expression: exp.OnCondition) -> str:
4820        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
4821        empty = expression.args.get("empty")
4822        empty = (
4823            f"DEFAULT {empty} ON EMPTY"
4824            if isinstance(empty, exp.Expression)
4825            else self.sql(expression, "empty")
4826        )
4827
4828        error = expression.args.get("error")
4829        error = (
4830            f"DEFAULT {error} ON ERROR"
4831            if isinstance(error, exp.Expression)
4832            else self.sql(expression, "error")
4833        )
4834
4835        if error and empty:
4836            error = (
4837                f"{empty} {error}"
4838                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
4839                else f"{error} {empty}"
4840            )
4841            empty = ""
4842
4843        null = self.sql(expression, "null")
4844
4845        return f"{empty}{error}{null}"
4846
4847    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
4848        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
4849        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
4850
4851    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
4852        this = self.sql(expression, "this")
4853        path = self.sql(expression, "path")
4854
4855        passing = self.expressions(expression, "passing")
4856        passing = f" PASSING {passing}" if passing else ""
4857
4858        on_condition = self.sql(expression, "on_condition")
4859        on_condition = f" {on_condition}" if on_condition else ""
4860
4861        path = f"{path}{passing}{on_condition}"
4862
4863        return self.func("JSON_EXISTS", this, path)
4864
4865    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
4866        array_agg = self.function_fallback_sql(expression)
4867
4868        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
4869        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
4870        if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"):
4871            parent = expression.parent
4872            if isinstance(parent, exp.Filter):
4873                parent_cond = parent.expression.this
4874                parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_()))
4875            else:
4876                this = expression.this
4877                # Do not add the filter if the input is not a column (e.g. literal, struct etc)
4878                if this.find(exp.Column):
4879                    # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
4880                    this_sql = (
4881                        self.expressions(this)
4882                        if isinstance(this, exp.Distinct)
4883                        else self.sql(expression, "this")
4884                    )
4885
4886                    array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)"
4887
4888        return array_agg
4889
4890    def apply_sql(self, expression: exp.Apply) -> str:
4891        this = self.sql(expression, "this")
4892        expr = self.sql(expression, "expression")
4893
4894        return f"{this} APPLY({expr})"
4895
4896    def _grant_or_revoke_sql(
4897        self,
4898        expression: exp.Grant | exp.Revoke,
4899        keyword: str,
4900        preposition: str,
4901        grant_option_prefix: str = "",
4902        grant_option_suffix: str = "",
4903    ) -> str:
4904        privileges_sql = self.expressions(expression, key="privileges", flat=True)
4905
4906        kind = self.sql(expression, "kind")
4907        kind = f" {kind}" if kind else ""
4908
4909        securable = self.sql(expression, "securable")
4910        securable = f" {securable}" if securable else ""
4911
4912        principals = self.expressions(expression, key="principals", flat=True)
4913
4914        if not expression.args.get("grant_option"):
4915            grant_option_prefix = grant_option_suffix = ""
4916
4917        # cascade for revoke only
4918        cascade = self.sql(expression, "cascade")
4919        cascade = f" {cascade}" if cascade else ""
4920
4921        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
4922
4923    def grant_sql(self, expression: exp.Grant) -> str:
4924        return self._grant_or_revoke_sql(
4925            expression,
4926            keyword="GRANT",
4927            preposition="TO",
4928            grant_option_suffix=" WITH GRANT OPTION",
4929        )
4930
4931    def revoke_sql(self, expression: exp.Revoke) -> str:
4932        return self._grant_or_revoke_sql(
4933            expression,
4934            keyword="REVOKE",
4935            preposition="FROM",
4936            grant_option_prefix="GRANT OPTION FOR ",
4937        )
4938
4939    def grantprivilege_sql(self, expression: exp.GrantPrivilege):
4940        this = self.sql(expression, "this")
4941        columns = self.expressions(expression, flat=True)
4942        columns = f"({columns})" if columns else ""
4943
4944        return f"{this}{columns}"
4945
4946    def grantprincipal_sql(self, expression: exp.GrantPrincipal):
4947        this = self.sql(expression, "this")
4948
4949        kind = self.sql(expression, "kind")
4950        kind = f"{kind} " if kind else ""
4951
4952        return f"{kind}{this}"
4953
4954    def columns_sql(self, expression: exp.Columns):
4955        func = self.function_fallback_sql(expression)
4956        if expression.args.get("unpack"):
4957            func = f"*{func}"
4958
4959        return func
4960
4961    def overlay_sql(self, expression: exp.Overlay):
4962        this = self.sql(expression, "this")
4963        expr = self.sql(expression, "expression")
4964        from_sql = self.sql(expression, "from")
4965        for_sql = self.sql(expression, "for")
4966        for_sql = f" FOR {for_sql}" if for_sql else ""
4967
4968        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
4969
4970    @unsupported_args("format")
4971    def todouble_sql(self, expression: exp.ToDouble) -> str:
4972        return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4973
4974    def string_sql(self, expression: exp.String) -> str:
4975        this = expression.this
4976        zone = expression.args.get("zone")
4977
4978        if zone:
4979            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
4980            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
4981            # set for source_tz to transpile the time conversion before the STRING cast
4982            this = exp.ConvertTimezone(
4983                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
4984            )
4985
4986        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
4987
4988    def median_sql(self, expression: exp.Median):
4989        if not self.SUPPORTS_MEDIAN:
4990            return self.sql(
4991                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
4992            )
4993
4994        return self.function_fallback_sql(expression)
4995
4996    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
4997        filler = self.sql(expression, "this")
4998        filler = f" {filler}" if filler else ""
4999        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5000        return f"TRUNCATE{filler} {with_count}"
5001
5002    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5003        if self.SUPPORTS_UNIX_SECONDS:
5004            return self.function_fallback_sql(expression)
5005
5006        start_ts = exp.cast(
5007            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
5008        )
5009
5010        return self.sql(
5011            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5012        )
5013
5014    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5015        dim = expression.expression
5016
5017        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5018        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5019            if not (dim.is_int and dim.name == "1"):
5020                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5021            dim = None
5022
5023        # If dimension is required but not specified, default initialize it
5024        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5025            dim = exp.Literal.number(1)
5026
5027        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5028
5029    def attach_sql(self, expression: exp.Attach) -> str:
5030        this = self.sql(expression, "this")
5031        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5032        expressions = self.expressions(expression)
5033        expressions = f" ({expressions})" if expressions else ""
5034
5035        return f"ATTACH{exists_sql} {this}{expressions}"
5036
5037    def detach_sql(self, expression: exp.Detach) -> str:
5038        this = self.sql(expression, "this")
5039        # the DATABASE keyword is required if IF EXISTS is set
5040        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
5041        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5042        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
5043
5044        return f"DETACH{exists_sql} {this}"
5045
5046    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5047        this = self.sql(expression, "this")
5048        value = self.sql(expression, "expression")
5049        value = f" {value}" if value else ""
5050        return f"{this}{value}"
5051
5052    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5053        return (
5054            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5055        )
5056
5057    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5058        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5059        encode = f"{encode} {self.sql(expression, 'this')}"
5060
5061        properties = expression.args.get("properties")
5062        if properties:
5063            encode = f"{encode} {self.properties(properties)}"
5064
5065        return encode
5066
5067    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5068        this = self.sql(expression, "this")
5069        include = f"INCLUDE {this}"
5070
5071        column_def = self.sql(expression, "column_def")
5072        if column_def:
5073            include = f"{include} {column_def}"
5074
5075        alias = self.sql(expression, "alias")
5076        if alias:
5077            include = f"{include} AS {alias}"
5078
5079        return include
5080
5081    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5082        name = f"NAME {self.sql(expression, 'this')}"
5083        return self.func("XMLELEMENT", name, *expression.expressions)
5084
5085    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5086        this = self.sql(expression, "this")
5087        expr = self.sql(expression, "expression")
5088        expr = f"({expr})" if expr else ""
5089        return f"{this}{expr}"
5090
5091    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5092        partitions = self.expressions(expression, "partition_expressions")
5093        create = self.expressions(expression, "create_expressions")
5094        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
5095
5096    def partitionbyrangepropertydynamic_sql(
5097        self, expression: exp.PartitionByRangePropertyDynamic
5098    ) -> str:
5099        start = self.sql(expression, "start")
5100        end = self.sql(expression, "end")
5101
5102        every = expression.args["every"]
5103        if isinstance(every, exp.Interval) and every.this.is_string:
5104            every.this.replace(exp.Literal.number(every.name))
5105
5106        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5107
5108    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5109        name = self.sql(expression, "this")
5110        values = self.expressions(expression, flat=True)
5111
5112        return f"NAME {name} VALUE {values}"
5113
5114    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5115        kind = self.sql(expression, "kind")
5116        sample = self.sql(expression, "sample")
5117        return f"SAMPLE {sample} {kind}"
5118
5119    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5120        kind = self.sql(expression, "kind")
5121        option = self.sql(expression, "option")
5122        option = f" {option}" if option else ""
5123        this = self.sql(expression, "this")
5124        this = f" {this}" if this else ""
5125        columns = self.expressions(expression)
5126        columns = f" {columns}" if columns else ""
5127        return f"{kind}{option} STATISTICS{this}{columns}"
5128
5129    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5130        this = self.sql(expression, "this")
5131        columns = self.expressions(expression)
5132        inner_expression = self.sql(expression, "expression")
5133        inner_expression = f" {inner_expression}" if inner_expression else ""
5134        update_options = self.sql(expression, "update_options")
5135        update_options = f" {update_options} UPDATE" if update_options else ""
5136        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
5137
5138    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5139        kind = self.sql(expression, "kind")
5140        kind = f" {kind}" if kind else ""
5141        return f"DELETE{kind} STATISTICS"
5142
5143    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5144        inner_expression = self.sql(expression, "expression")
5145        return f"LIST CHAINED ROWS{inner_expression}"
5146
5147    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5148        kind = self.sql(expression, "kind")
5149        this = self.sql(expression, "this")
5150        this = f" {this}" if this else ""
5151        inner_expression = self.sql(expression, "expression")
5152        return f"VALIDATE {kind}{this}{inner_expression}"
5153
5154    def analyze_sql(self, expression: exp.Analyze) -> str:
5155        options = self.expressions(expression, key="options", sep=" ")
5156        options = f" {options}" if options else ""
5157        kind = self.sql(expression, "kind")
5158        kind = f" {kind}" if kind else ""
5159        this = self.sql(expression, "this")
5160        this = f" {this}" if this else ""
5161        mode = self.sql(expression, "mode")
5162        mode = f" {mode}" if mode else ""
5163        properties = self.sql(expression, "properties")
5164        properties = f" {properties}" if properties else ""
5165        partition = self.sql(expression, "partition")
5166        partition = f" {partition}" if partition else ""
5167        inner_expression = self.sql(expression, "expression")
5168        inner_expression = f" {inner_expression}" if inner_expression else ""
5169        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5170
5171    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5172        this = self.sql(expression, "this")
5173        namespaces = self.expressions(expression, key="namespaces")
5174        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5175        passing = self.expressions(expression, key="passing")
5176        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5177        columns = self.expressions(expression, key="columns")
5178        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5179        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5180        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5181
5182    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5183        this = self.sql(expression, "this")
5184        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
5185
5186    def export_sql(self, expression: exp.Export) -> str:
5187        this = self.sql(expression, "this")
5188        connection = self.sql(expression, "connection")
5189        connection = f"WITH CONNECTION {connection} " if connection else ""
5190        options = self.sql(expression, "options")
5191        return f"EXPORT DATA {connection}{options} AS {this}"
5192
5193    def declare_sql(self, expression: exp.Declare) -> str:
5194        return f"DECLARE {self.expressions(expression, flat=True)}"
5195
5196    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5197        variable = self.sql(expression, "this")
5198        default = self.sql(expression, "default")
5199        default = f" = {default}" if default else ""
5200
5201        kind = self.sql(expression, "kind")
5202        if isinstance(expression.args.get("kind"), exp.Schema):
5203            kind = f"TABLE {kind}"
5204
5205        return f"{variable} AS {kind}{default}"
5206
5207    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5208        kind = self.sql(expression, "kind")
5209        this = self.sql(expression, "this")
5210        set = self.sql(expression, "expression")
5211        using = self.sql(expression, "using")
5212        using = f" USING {using}" if using else ""
5213
5214        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5215
5216        return f"{kind_sql} {this} SET {set}{using}"
5217
5218    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5219        params = self.expressions(expression, key="params", flat=True)
5220        return self.func(expression.name, *expression.expressions) + f"({params})"
5221
5222    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5223        return self.func(expression.name, *expression.expressions)
5224
5225    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5226        return self.anonymousaggfunc_sql(expression)
5227
5228    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5229        return self.parameterizedagg_sql(expression)
5230
5231    def show_sql(self, expression: exp.Show) -> str:
5232        self.unsupported("Unsupported SHOW statement")
5233        return ""
5234
5235    def install_sql(self, expression: exp.Install) -> str:
5236        self.unsupported("Unsupported INSTALL statement")
5237        return ""
5238
5239    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5240        # Snowflake GET/PUT statements:
5241        #   PUT <file> <internalStage> <properties>
5242        #   GET <internalStage> <file> <properties>
5243        props = expression.args.get("properties")
5244        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5245        this = self.sql(expression, "this")
5246        target = self.sql(expression, "target")
5247
5248        if isinstance(expression, exp.Put):
5249            return f"PUT {this} {target}{props_sql}"
5250        else:
5251            return f"GET {target} {this}{props_sql}"
5252
5253    def translatecharacters_sql(self, expression: exp.TranslateCharacters):
5254        this = self.sql(expression, "this")
5255        expr = self.sql(expression, "expression")
5256        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5257        return f"TRANSLATE({this} USING {expr}{with_error})"
5258
5259    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5260        if self.SUPPORTS_DECODE_CASE:
5261            return self.func("DECODE", *expression.expressions)
5262
5263        expression, *expressions = expression.expressions
5264
5265        ifs = []
5266        for search, result in zip(expressions[::2], expressions[1::2]):
5267            if isinstance(search, exp.Literal):
5268                ifs.append(exp.If(this=expression.eq(search), true=result))
5269            elif isinstance(search, exp.Null):
5270                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5271            else:
5272                if isinstance(search, exp.Binary):
5273                    search = exp.paren(search)
5274
5275                cond = exp.or_(
5276                    expression.eq(search),
5277                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5278                    copy=False,
5279                )
5280                ifs.append(exp.If(this=cond, true=result))
5281
5282        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5283        return self.sql(case)
5284
5285    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5286        this = self.sql(expression, "this")
5287        this = self.seg(this, sep="")
5288        dimensions = self.expressions(
5289            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5290        )
5291        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5292        metrics = self.expressions(
5293            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5294        )
5295        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5296        where = self.sql(expression, "where")
5297        where = self.seg(f"WHERE {where}") if where else ""
5298        body = self.indent(this + metrics + dimensions + where, skip_first=True)
5299        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5300
5301    def getextract_sql(self, expression: exp.GetExtract) -> str:
5302        this = expression.this
5303        expr = expression.expression
5304
5305        if not this.type or not expression.type:
5306            from sqlglot.optimizer.annotate_types import annotate_types
5307
5308            this = annotate_types(this, dialect=self.dialect)
5309
5310        if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)):
5311            return self.sql(exp.Bracket(this=this, expressions=[expr]))
5312
5313        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
5314
5315    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
5316        return self.sql(
5317            exp.DateAdd(
5318                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
5319                expression=expression.this,
5320                unit=exp.var("DAY"),
5321            )
5322        )
5323
5324    def space_sql(self: Generator, expression: exp.Space) -> str:
5325        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
5326
5327    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
5328        return f"BUILD {self.sql(expression, 'this')}"
5329
5330    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
5331        method = self.sql(expression, "method")
5332        kind = expression.args.get("kind")
5333        if not kind:
5334            return f"REFRESH {method}"
5335
5336        every = self.sql(expression, "every")
5337        unit = self.sql(expression, "unit")
5338        every = f" EVERY {every} {unit}" if every else ""
5339        starts = self.sql(expression, "starts")
5340        starts = f" STARTS {starts}" if starts else ""
5341
5342        return f"REFRESH {method} ON {kind}{every}{starts}"
5343
5344    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
5345        self.unsupported("The model!attribute syntax is not supported")
5346        return ""
5347
5348    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
5349        return self.func("DIRECTORY", expression.this)

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

Apply generic preprocessing transformations to a given expression.

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