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

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

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

Generator converts a given syntax tree to the corresponding SQL string.

Arguments:
  • pretty: Whether to format the produced SQL string. Default: False.
  • identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
  • normalize: Whether to normalize identifiers to lowercase. Default: False.
  • pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
  • indent: The indentation size in a formatted string. For example, this affects the indentation of subqueries and filters under a WHERE clause. Default: 2.
  • normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
  • unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
  • max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
  • leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
  • max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
  • comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.Dialect, Type[sqlglot.dialects.Dialect], NoneType] = None)
722    def __init__(
723        self,
724        pretty: t.Optional[bool] = None,
725        identify: str | bool = False,
726        normalize: bool = False,
727        pad: int = 2,
728        indent: int = 2,
729        normalize_functions: t.Optional[str | bool] = None,
730        unsupported_level: ErrorLevel = ErrorLevel.WARN,
731        max_unsupported: int = 3,
732        leading_comma: bool = False,
733        max_text_width: int = 80,
734        comments: bool = True,
735        dialect: DialectType = None,
736    ):
737        import sqlglot
738        from sqlglot.dialects import Dialect
739
740        self.pretty = pretty if pretty is not None else sqlglot.pretty
741        self.identify = identify
742        self.normalize = normalize
743        self.pad = pad
744        self._indent = indent
745        self.unsupported_level = unsupported_level
746        self.max_unsupported = max_unsupported
747        self.leading_comma = leading_comma
748        self.max_text_width = max_text_width
749        self.comments = comments
750        self.dialect = Dialect.get_or_raise(dialect)
751
752        # This is both a Dialect property and a Generator argument, so we prioritize the latter
753        self.normalize_functions = (
754            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
755        )
756
757        self.unsupported_messages: t.List[str] = []
758        self._escaped_quote_end: str = (
759            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
760        )
761        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
762
763        self._next_name = name_sequence("_t")
764
765        self._identifier_start = self.dialect.IDENTIFIER_START
766        self._identifier_end = self.dialect.IDENTIFIER_END
767
768        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.NCHAR: 'NCHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.CHAR: 'CHAR'>, <Type.VARCHAR: 'VARCHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: Set[Type[sqlglot.expressions.Expression]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: Tuple[Type[sqlglot.expressions.Expression], ...] = ()
SAFE_JSON_PATH_KEY_RE = re.compile('^[_a-zA-Z][\\w]*$')
SENTINEL_LINE_BREAK = '__SQLGLOT__LB__'
pretty
identify
normalize
pad
unsupported_level
max_unsupported
leading_comma
max_text_width
comments
dialect
normalize_functions
unsupported_messages: List[str]
def generate( self, expression: sqlglot.expressions.Expression, copy: bool = True) -> str:
770    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
771        """
772        Generates the SQL string corresponding to the given syntax tree.
773
774        Args:
775            expression: The syntax tree.
776            copy: Whether to copy the expression. The generator performs mutations so
777                it is safer to copy.
778
779        Returns:
780            The SQL string corresponding to `expression`.
781        """
782        if copy:
783            expression = expression.copy()
784
785        expression = self.preprocess(expression)
786
787        self.unsupported_messages = []
788        sql = self.sql(expression).strip()
789
790        if self.pretty:
791            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
792
793        if self.unsupported_level == ErrorLevel.IGNORE:
794            return sql
795
796        if self.unsupported_level == ErrorLevel.WARN:
797            for msg in self.unsupported_messages:
798                logger.warning(msg)
799        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
800            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
801
802        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:
804    def preprocess(self, expression: exp.Expression) -> exp.Expression:
805        """Apply generic preprocessing transformations to a given expression."""
806        expression = self._move_ctes_to_top_level(expression)
807
808        if self.ENSURE_BOOLS:
809            from sqlglot.transforms import ensure_bools
810
811            expression = ensure_bools(expression)
812
813        return expression

Apply generic preprocessing transformations to a given expression.

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