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

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

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

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

Arguments:
  • pretty: Whether to format the produced SQL string. Default: False.
  • identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
  • normalize: Whether to normalize identifiers to lowercase. Default: False.
  • pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
  • indent: The indentation size in a formatted string. For example, this affects the indentation of subqueries and filters under a WHERE clause. Default: 2.
  • normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
  • unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
  • max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
  • leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
  • max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
  • comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.Dialect, Type[sqlglot.dialects.Dialect], NoneType] = None)
724    def __init__(
725        self,
726        pretty: t.Optional[bool] = None,
727        identify: str | bool = False,
728        normalize: bool = False,
729        pad: int = 2,
730        indent: int = 2,
731        normalize_functions: t.Optional[str | bool] = None,
732        unsupported_level: ErrorLevel = ErrorLevel.WARN,
733        max_unsupported: int = 3,
734        leading_comma: bool = False,
735        max_text_width: int = 80,
736        comments: bool = True,
737        dialect: DialectType = None,
738    ):
739        import sqlglot
740        from sqlglot.dialects import Dialect
741
742        self.pretty = pretty if pretty is not None else sqlglot.pretty
743        self.identify = identify
744        self.normalize = normalize
745        self.pad = pad
746        self._indent = indent
747        self.unsupported_level = unsupported_level
748        self.max_unsupported = max_unsupported
749        self.leading_comma = leading_comma
750        self.max_text_width = max_text_width
751        self.comments = comments
752        self.dialect = Dialect.get_or_raise(dialect)
753
754        # This is both a Dialect property and a Generator argument, so we prioritize the latter
755        self.normalize_functions = (
756            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
757        )
758
759        self.unsupported_messages: t.List[str] = []
760        self._escaped_quote_end: str = (
761            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
762        )
763        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
764
765        self._next_name = name_sequence("_t")
766
767        self._identifier_start = self.dialect.IDENTIFIER_START
768        self._identifier_end = self.dialect.IDENTIFIER_END
769
770        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'}
UNSUPPORTED_TYPES: set[sqlglot.expressions.DataType.Type] = set()
TIME_PART_SINGULARS = {'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS = {'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
TOKEN_MAPPING: Dict[sqlglot.tokens.TokenType, str] = {}
STRUCT_DELIMITER = ('<', '>')
PARAMETER_TOKEN = '@'
NAMED_PLACEHOLDER_TOKEN = ':'
EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: Set[str] = set()
PROPERTIES_LOCATION = {<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
RESERVED_KEYWORDS: Set[str] = set()
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES = {<Type.NCHAR: 'NCHAR'>, <Type.VARCHAR: 'VARCHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.CHAR: 'CHAR'>}
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:
772    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
773        """
774        Generates the SQL string corresponding to the given syntax tree.
775
776        Args:
777            expression: The syntax tree.
778            copy: Whether to copy the expression. The generator performs mutations so
779                it is safer to copy.
780
781        Returns:
782            The SQL string corresponding to `expression`.
783        """
784        if copy:
785            expression = expression.copy()
786
787        expression = self.preprocess(expression)
788
789        self.unsupported_messages = []
790        sql = self.sql(expression).strip()
791
792        if self.pretty:
793            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
794
795        if self.unsupported_level == ErrorLevel.IGNORE:
796            return sql
797
798        if self.unsupported_level == ErrorLevel.WARN:
799            for msg in self.unsupported_messages:
800                logger.warning(msg)
801        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
802            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
803
804        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:
806    def preprocess(self, expression: exp.Expression) -> exp.Expression:
807        """Apply generic preprocessing transformations to a given expression."""
808        expression = self._move_ctes_to_top_level(expression)
809
810        if self.ENSURE_BOOLS:
811            from sqlglot.transforms import ensure_bools
812
813            expression = ensure_bools(expression)
814
815        return expression

Apply generic preprocessing transformations to a given expression.

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