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

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)
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_identifier_end = self.dialect.IDENTIFIER_END * 2
778
779        self._next_name = name_sequence("_t")
780
781        self._identifier_start = self.dialect.IDENTIFIER_START
782        self._identifier_end = self.dialect.IDENTIFIER_END
783
784        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.NVARCHAR: 'NVARCHAR'>, <Type.VARCHAR: 'VARCHAR'>, <Type.NCHAR: 'NCHAR'>}
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:
786    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
787        """
788        Generates the SQL string corresponding to the given syntax tree.
789
790        Args:
791            expression: The syntax tree.
792            copy: Whether to copy the expression. The generator performs mutations so
793                it is safer to copy.
794
795        Returns:
796            The SQL string corresponding to `expression`.
797        """
798        if copy:
799            expression = expression.copy()
800
801        expression = self.preprocess(expression)
802
803        self.unsupported_messages = []
804        sql = self.sql(expression).strip()
805
806        if self.pretty:
807            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
808
809        if self.unsupported_level == ErrorLevel.IGNORE:
810            return sql
811
812        if self.unsupported_level == ErrorLevel.WARN:
813            for msg in self.unsupported_messages:
814                logger.warning(msg)
815        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
816            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
817
818        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:
820    def preprocess(self, expression: exp.Expression) -> exp.Expression:
821        """Apply generic preprocessing transformations to a given expression."""
822        expression = self._move_ctes_to_top_level(expression)
823
824        if self.ENSURE_BOOLS:
825            from sqlglot.transforms import ensure_bools
826
827            expression = ensure_bools(expression)
828
829        return expression

Apply generic preprocessing transformations to a given expression.

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