Edit on GitHub

sqlglot.generator

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

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

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

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)
720    def __init__(
721        self,
722        pretty: t.Optional[bool] = None,
723        identify: str | bool = False,
724        normalize: bool = False,
725        pad: int = 2,
726        indent: int = 2,
727        normalize_functions: t.Optional[str | bool] = None,
728        unsupported_level: ErrorLevel = ErrorLevel.WARN,
729        max_unsupported: int = 3,
730        leading_comma: bool = False,
731        max_text_width: int = 80,
732        comments: bool = True,
733        dialect: DialectType = None,
734    ):
735        import sqlglot
736        from sqlglot.dialects import Dialect
737
738        self.pretty = pretty if pretty is not None else sqlglot.pretty
739        self.identify = identify
740        self.normalize = normalize
741        self.pad = pad
742        self._indent = indent
743        self.unsupported_level = unsupported_level
744        self.max_unsupported = max_unsupported
745        self.leading_comma = leading_comma
746        self.max_text_width = max_text_width
747        self.comments = comments
748        self.dialect = Dialect.get_or_raise(dialect)
749
750        # This is both a Dialect property and a Generator argument, so we prioritize the latter
751        self.normalize_functions = (
752            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
753        )
754
755        self.unsupported_messages: t.List[str] = []
756        self._escaped_quote_end: str = (
757            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
758        )
759        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
760
761        self._next_name = name_sequence("_t")
762
763        self._identifier_start = self.dialect.IDENTIFIER_START
764        self._identifier_end = self.dialect.IDENTIFIER_END
765
766        self._quote_json_path_key_using_brackets = True
TRANSFORMS: Dict[Type[sqlglot.expressions.Expression], Callable[..., str]] = {<class 'sqlglot.expressions.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Uuid'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.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
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
TYPE_MAPPING = {<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TIME_PART_SINGULARS = {'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS = {'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
TOKEN_MAPPING: Dict[sqlglot.tokens.TokenType, str] = {}
STRUCT_DELIMITER = ('<', '>')
PARAMETER_TOKEN = '@'
NAMED_PLACEHOLDER_TOKEN = ':'
EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: Set[str] = set()
PROPERTIES_LOCATION = {<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
RESERVED_KEYWORDS: Set[str] = set()
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES = {<Type.VARCHAR: 'VARCHAR'>, <Type.CHAR: 'CHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.NCHAR: 'NCHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: Set[Type[sqlglot.expressions.Expression]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: Tuple[Type[sqlglot.expressions.Expression], ...] = ()
SENTINEL_LINE_BREAK = '__SQLGLOT__LB__'
pretty
identify
normalize
pad
unsupported_level
max_unsupported
leading_comma
max_text_width
comments
dialect
normalize_functions
unsupported_messages: List[str]
def generate( self, expression: sqlglot.expressions.Expression, copy: bool = True) -> str:
768    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
769        """
770        Generates the SQL string corresponding to the given syntax tree.
771
772        Args:
773            expression: The syntax tree.
774            copy: Whether to copy the expression. The generator performs mutations so
775                it is safer to copy.
776
777        Returns:
778            The SQL string corresponding to `expression`.
779        """
780        if copy:
781            expression = expression.copy()
782
783        expression = self.preprocess(expression)
784
785        self.unsupported_messages = []
786        sql = self.sql(expression).strip()
787
788        if self.pretty:
789            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
790
791        if self.unsupported_level == ErrorLevel.IGNORE:
792            return sql
793
794        if self.unsupported_level == ErrorLevel.WARN:
795            for msg in self.unsupported_messages:
796                logger.warning(msg)
797        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
798            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
799
800        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:
802    def preprocess(self, expression: exp.Expression) -> exp.Expression:
803        """Apply generic preprocessing transformations to a given expression."""
804        expression = self._move_ctes_to_top_level(expression)
805
806        if self.ENSURE_BOOLS:
807            from sqlglot.transforms import ensure_bools
808
809            expression = ensure_bools(expression)
810
811        return expression

Apply generic preprocessing transformations to a given expression.

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