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.ProjectionPolicyColumnConstraint: lambda self,
 182        e: f"PROJECTION POLICY {self.sql(e, 'this')}",
 183        exp.Put: lambda self, e: self.get_put_sql(e),
 184        exp.RemoteWithConnectionModelProperty: lambda self,
 185        e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
 186        exp.ReturnsProperty: lambda self, e: (
 187            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 188        ),
 189        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 190        exp.SecureProperty: lambda *_: "SECURE",
 191        exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}",
 192        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 193        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 194        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 195        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 196        exp.SqlReadWriteProperty: lambda _, e: e.name,
 197        exp.SqlSecurityProperty: lambda _,
 198        e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}",
 199        exp.StabilityProperty: lambda _, e: e.name,
 200        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 201        exp.StreamingTableProperty: lambda *_: "STREAMING",
 202        exp.StrictProperty: lambda *_: "STRICT",
 203        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 204        exp.TableColumn: lambda self, e: self.sql(e.this),
 205        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 206        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 207        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 208        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 209        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 210        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 211        exp.TransientProperty: lambda *_: "TRANSIENT",
 212        exp.Union: lambda self, e: self.set_operations(e),
 213        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 214        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 215        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 216        exp.Uuid: lambda *_: "UUID()",
 217        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 218        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 219        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 220        exp.VolatileProperty: lambda *_: "VOLATILE",
 221        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 222        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 223        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 224        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 225        exp.ForceProperty: lambda *_: "FORCE",
 226    }
 227
 228    # Whether null ordering is supported in order by
 229    # True: Full Support, None: No support, False: No support for certain cases
 230    # such as window specifications, aggregate functions etc
 231    NULL_ORDERING_SUPPORTED: t.Optional[bool] = True
 232
 233    # Whether ignore nulls is inside the agg or outside.
 234    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 235    IGNORE_NULLS_IN_FUNC = False
 236
 237    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 238    LOCKING_READS_SUPPORTED = False
 239
 240    # Whether the EXCEPT and INTERSECT operations can return duplicates
 241    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 242
 243    # Wrap derived values in parens, usually standard but spark doesn't support it
 244    WRAP_DERIVED_VALUES = True
 245
 246    # Whether create function uses an AS before the RETURN
 247    CREATE_FUNCTION_RETURN_AS = True
 248
 249    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 250    MATCHED_BY_SOURCE = True
 251
 252    # Whether the INTERVAL expression works only with values like '1 day'
 253    SINGLE_STRING_INTERVAL = False
 254
 255    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 256    INTERVAL_ALLOWS_PLURAL_FORM = True
 257
 258    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 259    LIMIT_FETCH = "ALL"
 260
 261    # Whether limit and fetch allows expresions or just limits
 262    LIMIT_ONLY_LITERALS = False
 263
 264    # Whether a table is allowed to be renamed with a db
 265    RENAME_TABLE_WITH_DB = True
 266
 267    # The separator for grouping sets and rollups
 268    GROUPINGS_SEP = ","
 269
 270    # The string used for creating an index on a table
 271    INDEX_ON = "ON"
 272
 273    # Whether join hints should be generated
 274    JOIN_HINTS = True
 275
 276    # Whether table hints should be generated
 277    TABLE_HINTS = True
 278
 279    # Whether query hints should be generated
 280    QUERY_HINTS = True
 281
 282    # What kind of separator to use for query hints
 283    QUERY_HINT_SEP = ", "
 284
 285    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 286    IS_BOOL_ALLOWED = True
 287
 288    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 289    DUPLICATE_KEY_UPDATE_WITH_SET = True
 290
 291    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 292    LIMIT_IS_TOP = False
 293
 294    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 295    RETURNING_END = True
 296
 297    # Whether to generate an unquoted value for EXTRACT's date part argument
 298    EXTRACT_ALLOWS_QUOTES = True
 299
 300    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 301    TZ_TO_WITH_TIME_ZONE = False
 302
 303    # Whether the NVL2 function is supported
 304    NVL2_SUPPORTED = True
 305
 306    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 307    SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE")
 308
 309    # Whether VALUES statements can be used as derived tables.
 310    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 311    # SELECT * VALUES into SELECT UNION
 312    VALUES_AS_TABLE = True
 313
 314    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 315    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 316
 317    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 318    UNNEST_WITH_ORDINALITY = True
 319
 320    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 321    AGGREGATE_FILTER_SUPPORTED = True
 322
 323    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 324    SEMI_ANTI_JOIN_WITH_SIDE = True
 325
 326    # Whether to include the type of a computed column in the CREATE DDL
 327    COMPUTED_COLUMN_WITH_TYPE = True
 328
 329    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 330    SUPPORTS_TABLE_COPY = True
 331
 332    # Whether parentheses are required around the table sample's expression
 333    TABLESAMPLE_REQUIRES_PARENS = True
 334
 335    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 336    TABLESAMPLE_SIZE_IS_ROWS = True
 337
 338    # The keyword(s) to use when generating a sample clause
 339    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 340
 341    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 342    TABLESAMPLE_WITH_METHOD = True
 343
 344    # The keyword to use when specifying the seed of a sample clause
 345    TABLESAMPLE_SEED_KEYWORD = "SEED"
 346
 347    # Whether COLLATE is a function instead of a binary operator
 348    COLLATE_IS_FUNC = False
 349
 350    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 351    DATA_TYPE_SPECIFIERS_ALLOWED = False
 352
 353    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 354    ENSURE_BOOLS = False
 355
 356    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 357    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 358
 359    # Whether CONCAT requires >1 arguments
 360    SUPPORTS_SINGLE_ARG_CONCAT = True
 361
 362    # Whether LAST_DAY function supports a date part argument
 363    LAST_DAY_SUPPORTS_DATE_PART = True
 364
 365    # Whether named columns are allowed in table aliases
 366    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 367
 368    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 369    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 370
 371    # What delimiter to use for separating JSON key/value pairs
 372    JSON_KEY_VALUE_PAIR_SEP = ":"
 373
 374    # INSERT OVERWRITE TABLE x override
 375    INSERT_OVERWRITE = " OVERWRITE TABLE"
 376
 377    # Whether the SELECT .. INTO syntax is used instead of CTAS
 378    SUPPORTS_SELECT_INTO = False
 379
 380    # Whether UNLOGGED tables can be created
 381    SUPPORTS_UNLOGGED_TABLES = False
 382
 383    # Whether the CREATE TABLE LIKE statement is supported
 384    SUPPORTS_CREATE_TABLE_LIKE = True
 385
 386    # Whether the LikeProperty needs to be specified inside of the schema clause
 387    LIKE_PROPERTY_INSIDE_SCHEMA = False
 388
 389    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 390    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 391    MULTI_ARG_DISTINCT = True
 392
 393    # Whether the JSON extraction operators expect a value of type JSON
 394    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 395
 396    # Whether bracketed keys like ["foo"] are supported in JSON paths
 397    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 398
 399    # Whether to escape keys using single quotes in JSON paths
 400    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 401
 402    # The JSONPathPart expressions supported by this dialect
 403    SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy()
 404
 405    # Whether any(f(x) for x in array) can be implemented by this dialect
 406    CAN_IMPLEMENT_ARRAY_ANY = False
 407
 408    # Whether the function TO_NUMBER is supported
 409    SUPPORTS_TO_NUMBER = True
 410
 411    # Whether EXCLUDE in window specification is supported
 412    SUPPORTS_WINDOW_EXCLUDE = False
 413
 414    # Whether or not set op modifiers apply to the outer set op or select.
 415    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 416    # True means limit 1 happens after the set op, False means it it happens on y.
 417    SET_OP_MODIFIERS = True
 418
 419    # Whether parameters from COPY statement are wrapped in parentheses
 420    COPY_PARAMS_ARE_WRAPPED = True
 421
 422    # Whether values of params are set with "=" token or empty space
 423    COPY_PARAMS_EQ_REQUIRED = False
 424
 425    # Whether COPY statement has INTO keyword
 426    COPY_HAS_INTO_KEYWORD = True
 427
 428    # Whether the conditional TRY(expression) function is supported
 429    TRY_SUPPORTED = True
 430
 431    # Whether the UESCAPE syntax in unicode strings is supported
 432    SUPPORTS_UESCAPE = True
 433
 434    # The keyword to use when generating a star projection with excluded columns
 435    STAR_EXCEPT = "EXCEPT"
 436
 437    # The HEX function name
 438    HEX_FUNC = "HEX"
 439
 440    # The keywords to use when prefixing & separating WITH based properties
 441    WITH_PROPERTIES_PREFIX = "WITH"
 442
 443    # Whether to quote the generated expression of exp.JsonPath
 444    QUOTE_JSON_PATH = True
 445
 446    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 447    PAD_FILL_PATTERN_IS_REQUIRED = False
 448
 449    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 450    SUPPORTS_EXPLODING_PROJECTIONS = True
 451
 452    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 453    ARRAY_CONCAT_IS_VAR_LEN = True
 454
 455    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 456    SUPPORTS_CONVERT_TIMEZONE = False
 457
 458    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 459    SUPPORTS_MEDIAN = True
 460
 461    # Whether UNIX_SECONDS(timestamp) is supported
 462    SUPPORTS_UNIX_SECONDS = False
 463
 464    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 465    ALTER_SET_WRAPPED = False
 466
 467    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 468    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 469    # TODO: The normalization should be done by default once we've tested it across all dialects.
 470    NORMALIZE_EXTRACT_DATE_PARTS = False
 471
 472    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 473    PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
 474
 475    # The function name of the exp.ArraySize expression
 476    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 477
 478    # The syntax to use when altering the type of a column
 479    ALTER_SET_TYPE = "SET DATA TYPE"
 480
 481    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 482    # None -> Doesn't support it at all
 483    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 484    # True (Postgres) -> Explicitly requires it
 485    ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None
 486
 487    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 488    SUPPORTS_DECODE_CASE = True
 489
 490    TYPE_MAPPING = {
 491        exp.DataType.Type.DATETIME2: "TIMESTAMP",
 492        exp.DataType.Type.NCHAR: "CHAR",
 493        exp.DataType.Type.NVARCHAR: "VARCHAR",
 494        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 495        exp.DataType.Type.LONGTEXT: "TEXT",
 496        exp.DataType.Type.TINYTEXT: "TEXT",
 497        exp.DataType.Type.BLOB: "VARBINARY",
 498        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 499        exp.DataType.Type.LONGBLOB: "BLOB",
 500        exp.DataType.Type.TINYBLOB: "BLOB",
 501        exp.DataType.Type.INET: "INET",
 502        exp.DataType.Type.ROWVERSION: "VARBINARY",
 503        exp.DataType.Type.SMALLDATETIME: "TIMESTAMP",
 504    }
 505
 506    TIME_PART_SINGULARS = {
 507        "MICROSECONDS": "MICROSECOND",
 508        "SECONDS": "SECOND",
 509        "MINUTES": "MINUTE",
 510        "HOURS": "HOUR",
 511        "DAYS": "DAY",
 512        "WEEKS": "WEEK",
 513        "MONTHS": "MONTH",
 514        "QUARTERS": "QUARTER",
 515        "YEARS": "YEAR",
 516    }
 517
 518    AFTER_HAVING_MODIFIER_TRANSFORMS = {
 519        "cluster": lambda self, e: self.sql(e, "cluster"),
 520        "distribute": lambda self, e: self.sql(e, "distribute"),
 521        "sort": lambda self, e: self.sql(e, "sort"),
 522        "windows": lambda self, e: (
 523            self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
 524            if e.args.get("windows")
 525            else ""
 526        ),
 527        "qualify": lambda self, e: self.sql(e, "qualify"),
 528    }
 529
 530    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 531
 532    STRUCT_DELIMITER = ("<", ">")
 533
 534    PARAMETER_TOKEN = "@"
 535    NAMED_PLACEHOLDER_TOKEN = ":"
 536
 537    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set()
 538
 539    PROPERTIES_LOCATION = {
 540        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 541        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 542        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 543        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 544        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 545        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 546        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 547        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 548        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 549        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 550        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 551        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 552        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 553        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 554        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 555        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 556        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 557        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 558        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 559        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 560        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 561        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 562        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 563        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 564        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 565        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 566        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 567        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 568        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 569        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 570        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 571        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 572        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 573        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 574        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 575        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 576        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 577        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 578        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 579        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 580        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 581        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 582        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 583        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 584        exp.LogProperty: exp.Properties.Location.POST_NAME,
 585        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 586        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 587        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 588        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 589        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 590        exp.Order: exp.Properties.Location.POST_SCHEMA,
 591        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 592        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 593        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 594        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 595        exp.Property: exp.Properties.Location.POST_WITH,
 596        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 597        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 598        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 599        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 600        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 601        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 602        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 603        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 604        exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA,
 605        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 606        exp.Set: exp.Properties.Location.POST_SCHEMA,
 607        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 608        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 609        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 610        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 611        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 612        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 613        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 614        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 615        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 616        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 617        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 618        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 619        exp.Tags: exp.Properties.Location.POST_WITH,
 620        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 621        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 622        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 623        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 624        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 625        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 626        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 627        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 628        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 629        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 630        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 631        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 632        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 633        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 634        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 635    }
 636
 637    # Keywords that can't be used as unquoted identifier names
 638    RESERVED_KEYWORDS: t.Set[str] = set()
 639
 640    # Expressions whose comments are separated from them for better formatting
 641    WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 642        exp.Command,
 643        exp.Create,
 644        exp.Describe,
 645        exp.Delete,
 646        exp.Drop,
 647        exp.From,
 648        exp.Insert,
 649        exp.Join,
 650        exp.MultitableInserts,
 651        exp.Order,
 652        exp.Group,
 653        exp.Having,
 654        exp.Select,
 655        exp.SetOperation,
 656        exp.Update,
 657        exp.Where,
 658        exp.With,
 659    )
 660
 661    # Expressions that should not have their comments generated in maybe_comment
 662    EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 663        exp.Binary,
 664        exp.SetOperation,
 665    )
 666
 667    # Expressions that can remain unwrapped when appearing in the context of an INTERVAL
 668    UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = (
 669        exp.Column,
 670        exp.Literal,
 671        exp.Neg,
 672        exp.Paren,
 673    )
 674
 675    PARAMETERIZABLE_TEXT_TYPES = {
 676        exp.DataType.Type.NVARCHAR,
 677        exp.DataType.Type.VARCHAR,
 678        exp.DataType.Type.CHAR,
 679        exp.DataType.Type.NCHAR,
 680    }
 681
 682    # Expressions that need to have all CTEs under them bubbled up to them
 683    EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set()
 684
 685    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = ()
 686
 687    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 688
 689    __slots__ = (
 690        "pretty",
 691        "identify",
 692        "normalize",
 693        "pad",
 694        "_indent",
 695        "normalize_functions",
 696        "unsupported_level",
 697        "max_unsupported",
 698        "leading_comma",
 699        "max_text_width",
 700        "comments",
 701        "dialect",
 702        "unsupported_messages",
 703        "_escaped_quote_end",
 704        "_escaped_identifier_end",
 705        "_next_name",
 706        "_identifier_start",
 707        "_identifier_end",
 708        "_quote_json_path_key_using_brackets",
 709    )
 710
 711    def __init__(
 712        self,
 713        pretty: t.Optional[bool] = None,
 714        identify: str | bool = False,
 715        normalize: bool = False,
 716        pad: int = 2,
 717        indent: int = 2,
 718        normalize_functions: t.Optional[str | bool] = None,
 719        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 720        max_unsupported: int = 3,
 721        leading_comma: bool = False,
 722        max_text_width: int = 80,
 723        comments: bool = True,
 724        dialect: DialectType = None,
 725    ):
 726        import sqlglot
 727        from sqlglot.dialects import Dialect
 728
 729        self.pretty = pretty if pretty is not None else sqlglot.pretty
 730        self.identify = identify
 731        self.normalize = normalize
 732        self.pad = pad
 733        self._indent = indent
 734        self.unsupported_level = unsupported_level
 735        self.max_unsupported = max_unsupported
 736        self.leading_comma = leading_comma
 737        self.max_text_width = max_text_width
 738        self.comments = comments
 739        self.dialect = Dialect.get_or_raise(dialect)
 740
 741        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 742        self.normalize_functions = (
 743            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 744        )
 745
 746        self.unsupported_messages: t.List[str] = []
 747        self._escaped_quote_end: str = (
 748            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 749        )
 750        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 751
 752        self._next_name = name_sequence("_t")
 753
 754        self._identifier_start = self.dialect.IDENTIFIER_START
 755        self._identifier_end = self.dialect.IDENTIFIER_END
 756
 757        self._quote_json_path_key_using_brackets = True
 758
 759    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
 760        """
 761        Generates the SQL string corresponding to the given syntax tree.
 762
 763        Args:
 764            expression: The syntax tree.
 765            copy: Whether to copy the expression. The generator performs mutations so
 766                it is safer to copy.
 767
 768        Returns:
 769            The SQL string corresponding to `expression`.
 770        """
 771        if copy:
 772            expression = expression.copy()
 773
 774        expression = self.preprocess(expression)
 775
 776        self.unsupported_messages = []
 777        sql = self.sql(expression).strip()
 778
 779        if self.pretty:
 780            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 781
 782        if self.unsupported_level == ErrorLevel.IGNORE:
 783            return sql
 784
 785        if self.unsupported_level == ErrorLevel.WARN:
 786            for msg in self.unsupported_messages:
 787                logger.warning(msg)
 788        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 789            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 790
 791        return sql
 792
 793    def preprocess(self, expression: exp.Expression) -> exp.Expression:
 794        """Apply generic preprocessing transformations to a given expression."""
 795        expression = self._move_ctes_to_top_level(expression)
 796
 797        if self.ENSURE_BOOLS:
 798            from sqlglot.transforms import ensure_bools
 799
 800            expression = ensure_bools(expression)
 801
 802        return expression
 803
 804    def _move_ctes_to_top_level(self, expression: E) -> E:
 805        if (
 806            not expression.parent
 807            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 808            and any(node.parent is not expression for node in expression.find_all(exp.With))
 809        ):
 810            from sqlglot.transforms import move_ctes_to_top_level
 811
 812            expression = move_ctes_to_top_level(expression)
 813        return expression
 814
 815    def unsupported(self, message: str) -> None:
 816        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 817            raise UnsupportedError(message)
 818        self.unsupported_messages.append(message)
 819
 820    def sep(self, sep: str = " ") -> str:
 821        return f"{sep.strip()}\n" if self.pretty else sep
 822
 823    def seg(self, sql: str, sep: str = " ") -> str:
 824        return f"{self.sep(sep)}{sql}"
 825
 826    def sanitize_comment(self, comment: str) -> str:
 827        comment = " " + comment if comment[0].strip() else comment
 828        comment = comment + " " if comment[-1].strip() else comment
 829
 830        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
 831            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
 832            comment = comment.replace("*/", "* /")
 833
 834        return comment
 835
 836    def maybe_comment(
 837        self,
 838        sql: str,
 839        expression: t.Optional[exp.Expression] = None,
 840        comments: t.Optional[t.List[str]] = None,
 841        separated: bool = False,
 842    ) -> str:
 843        comments = (
 844            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 845            if self.comments
 846            else None
 847        )
 848
 849        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 850            return sql
 851
 852        comments_sql = " ".join(
 853            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
 854        )
 855
 856        if not comments_sql:
 857            return sql
 858
 859        comments_sql = self._replace_line_breaks(comments_sql)
 860
 861        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 862            return (
 863                f"{self.sep()}{comments_sql}{sql}"
 864                if not sql or sql[0].isspace()
 865                else f"{comments_sql}{self.sep()}{sql}"
 866            )
 867
 868        return f"{sql} {comments_sql}"
 869
 870    def wrap(self, expression: exp.Expression | str) -> str:
 871        this_sql = (
 872            self.sql(expression)
 873            if isinstance(expression, exp.UNWRAPPED_QUERIES)
 874            else self.sql(expression, "this")
 875        )
 876        if not this_sql:
 877            return "()"
 878
 879        this_sql = self.indent(this_sql, level=1, pad=0)
 880        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 881
 882    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 883        original = self.identify
 884        self.identify = False
 885        result = func(*args, **kwargs)
 886        self.identify = original
 887        return result
 888
 889    def normalize_func(self, name: str) -> str:
 890        if self.normalize_functions == "upper" or self.normalize_functions is True:
 891            return name.upper()
 892        if self.normalize_functions == "lower":
 893            return name.lower()
 894        return name
 895
 896    def indent(
 897        self,
 898        sql: str,
 899        level: int = 0,
 900        pad: t.Optional[int] = None,
 901        skip_first: bool = False,
 902        skip_last: bool = False,
 903    ) -> str:
 904        if not self.pretty or not sql:
 905            return sql
 906
 907        pad = self.pad if pad is None else pad
 908        lines = sql.split("\n")
 909
 910        return "\n".join(
 911            (
 912                line
 913                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 914                else f"{' ' * (level * self._indent + pad)}{line}"
 915            )
 916            for i, line in enumerate(lines)
 917        )
 918
 919    def sql(
 920        self,
 921        expression: t.Optional[str | exp.Expression],
 922        key: t.Optional[str] = None,
 923        comment: bool = True,
 924    ) -> str:
 925        if not expression:
 926            return ""
 927
 928        if isinstance(expression, str):
 929            return expression
 930
 931        if key:
 932            value = expression.args.get(key)
 933            if value:
 934                return self.sql(value)
 935            return ""
 936
 937        transform = self.TRANSFORMS.get(expression.__class__)
 938
 939        if callable(transform):
 940            sql = transform(self, expression)
 941        elif isinstance(expression, exp.Expression):
 942            exp_handler_name = f"{expression.key}_sql"
 943
 944            if hasattr(self, exp_handler_name):
 945                sql = getattr(self, exp_handler_name)(expression)
 946            elif isinstance(expression, exp.Func):
 947                sql = self.function_fallback_sql(expression)
 948            elif isinstance(expression, exp.Property):
 949                sql = self.property_sql(expression)
 950            else:
 951                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
 952        else:
 953            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
 954
 955        return self.maybe_comment(sql, expression) if self.comments and comment else sql
 956
 957    def uncache_sql(self, expression: exp.Uncache) -> str:
 958        table = self.sql(expression, "this")
 959        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
 960        return f"UNCACHE TABLE{exists_sql} {table}"
 961
 962    def cache_sql(self, expression: exp.Cache) -> str:
 963        lazy = " LAZY" if expression.args.get("lazy") else ""
 964        table = self.sql(expression, "this")
 965        options = expression.args.get("options")
 966        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
 967        sql = self.sql(expression, "expression")
 968        sql = f" AS{self.sep()}{sql}" if sql else ""
 969        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
 970        return self.prepend_ctes(expression, sql)
 971
 972    def characterset_sql(self, expression: exp.CharacterSet) -> str:
 973        if isinstance(expression.parent, exp.Cast):
 974            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
 975        default = "DEFAULT " if expression.args.get("default") else ""
 976        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
 977
 978    def column_parts(self, expression: exp.Column) -> str:
 979        return ".".join(
 980            self.sql(part)
 981            for part in (
 982                expression.args.get("catalog"),
 983                expression.args.get("db"),
 984                expression.args.get("table"),
 985                expression.args.get("this"),
 986            )
 987            if part
 988        )
 989
 990    def column_sql(self, expression: exp.Column) -> str:
 991        join_mark = " (+)" if expression.args.get("join_mark") else ""
 992
 993        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
 994            join_mark = ""
 995            self.unsupported("Outer join syntax using the (+) operator is not supported.")
 996
 997        return f"{self.column_parts(expression)}{join_mark}"
 998
 999    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1000        this = self.sql(expression, "this")
1001        this = f" {this}" if this else ""
1002        position = self.sql(expression, "position")
1003        return f"{position}{this}"
1004
1005    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1006        column = self.sql(expression, "this")
1007        kind = self.sql(expression, "kind")
1008        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1009        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1010        kind = f"{sep}{kind}" if kind else ""
1011        constraints = f" {constraints}" if constraints else ""
1012        position = self.sql(expression, "position")
1013        position = f" {position}" if position else ""
1014
1015        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1016            kind = ""
1017
1018        return f"{exists}{column}{kind}{constraints}{position}"
1019
1020    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1021        this = self.sql(expression, "this")
1022        kind_sql = self.sql(expression, "kind").strip()
1023        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1024
1025    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1026        this = self.sql(expression, "this")
1027        if expression.args.get("not_null"):
1028            persisted = " PERSISTED NOT NULL"
1029        elif expression.args.get("persisted"):
1030            persisted = " PERSISTED"
1031        else:
1032            persisted = ""
1033
1034        return f"AS {this}{persisted}"
1035
1036    def autoincrementcolumnconstraint_sql(self, _) -> str:
1037        return self.token_sql(TokenType.AUTO_INCREMENT)
1038
1039    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1040        if isinstance(expression.this, list):
1041            this = self.wrap(self.expressions(expression, key="this", flat=True))
1042        else:
1043            this = self.sql(expression, "this")
1044
1045        return f"COMPRESS {this}"
1046
1047    def generatedasidentitycolumnconstraint_sql(
1048        self, expression: exp.GeneratedAsIdentityColumnConstraint
1049    ) -> str:
1050        this = ""
1051        if expression.this is not None:
1052            on_null = " ON NULL" if expression.args.get("on_null") else ""
1053            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1054
1055        start = expression.args.get("start")
1056        start = f"START WITH {start}" if start else ""
1057        increment = expression.args.get("increment")
1058        increment = f" INCREMENT BY {increment}" if increment else ""
1059        minvalue = expression.args.get("minvalue")
1060        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1061        maxvalue = expression.args.get("maxvalue")
1062        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1063        cycle = expression.args.get("cycle")
1064        cycle_sql = ""
1065
1066        if cycle is not None:
1067            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1068            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1069
1070        sequence_opts = ""
1071        if start or increment or cycle_sql:
1072            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1073            sequence_opts = f" ({sequence_opts.strip()})"
1074
1075        expr = self.sql(expression, "expression")
1076        expr = f"({expr})" if expr else "IDENTITY"
1077
1078        return f"GENERATED{this} AS {expr}{sequence_opts}"
1079
1080    def generatedasrowcolumnconstraint_sql(
1081        self, expression: exp.GeneratedAsRowColumnConstraint
1082    ) -> str:
1083        start = "START" if expression.args.get("start") else "END"
1084        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1085        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1086
1087    def periodforsystemtimeconstraint_sql(
1088        self, expression: exp.PeriodForSystemTimeConstraint
1089    ) -> str:
1090        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1091
1092    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1093        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1094
1095    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1096        desc = expression.args.get("desc")
1097        if desc is not None:
1098            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1099        options = self.expressions(expression, key="options", flat=True, sep=" ")
1100        options = f" {options}" if options else ""
1101        return f"PRIMARY KEY{options}"
1102
1103    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1104        this = self.sql(expression, "this")
1105        this = f" {this}" if this else ""
1106        index_type = expression.args.get("index_type")
1107        index_type = f" USING {index_type}" if index_type else ""
1108        on_conflict = self.sql(expression, "on_conflict")
1109        on_conflict = f" {on_conflict}" if on_conflict else ""
1110        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1111        options = self.expressions(expression, key="options", flat=True, sep=" ")
1112        options = f" {options}" if options else ""
1113        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1114
1115    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1116        return self.sql(expression, "this")
1117
1118    def create_sql(self, expression: exp.Create) -> str:
1119        kind = self.sql(expression, "kind")
1120        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1121        properties = expression.args.get("properties")
1122        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1123
1124        this = self.createable_sql(expression, properties_locs)
1125
1126        properties_sql = ""
1127        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1128            exp.Properties.Location.POST_WITH
1129        ):
1130            properties_sql = self.sql(
1131                exp.Properties(
1132                    expressions=[
1133                        *properties_locs[exp.Properties.Location.POST_SCHEMA],
1134                        *properties_locs[exp.Properties.Location.POST_WITH],
1135                    ]
1136                )
1137            )
1138
1139            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1140                properties_sql = self.sep() + properties_sql
1141            elif not self.pretty:
1142                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1143                properties_sql = f" {properties_sql}"
1144
1145        begin = " BEGIN" if expression.args.get("begin") else ""
1146        end = " END" if expression.args.get("end") else ""
1147
1148        expression_sql = self.sql(expression, "expression")
1149        if expression_sql:
1150            expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
1151
1152            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1153                postalias_props_sql = ""
1154                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1155                    postalias_props_sql = self.properties(
1156                        exp.Properties(
1157                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1158                        ),
1159                        wrapped=False,
1160                    )
1161                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1162                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1163
1164        postindex_props_sql = ""
1165        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1166            postindex_props_sql = self.properties(
1167                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1168                wrapped=False,
1169                prefix=" ",
1170            )
1171
1172        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1173        indexes = f" {indexes}" if indexes else ""
1174        index_sql = indexes + postindex_props_sql
1175
1176        replace = " OR REPLACE" if expression.args.get("replace") else ""
1177        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1178        unique = " UNIQUE" if expression.args.get("unique") else ""
1179
1180        clustered = expression.args.get("clustered")
1181        if clustered is None:
1182            clustered_sql = ""
1183        elif clustered:
1184            clustered_sql = " CLUSTERED COLUMNSTORE"
1185        else:
1186            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1187
1188        postcreate_props_sql = ""
1189        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1190            postcreate_props_sql = self.properties(
1191                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1192                sep=" ",
1193                prefix=" ",
1194                wrapped=False,
1195            )
1196
1197        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1198
1199        postexpression_props_sql = ""
1200        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1201            postexpression_props_sql = self.properties(
1202                exp.Properties(
1203                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1204                ),
1205                sep=" ",
1206                prefix=" ",
1207                wrapped=False,
1208            )
1209
1210        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1211        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1212        no_schema_binding = (
1213            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1214        )
1215
1216        clone = self.sql(expression, "clone")
1217        clone = f" {clone}" if clone else ""
1218
1219        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1220            properties_expression = f"{expression_sql}{properties_sql}"
1221        else:
1222            properties_expression = f"{properties_sql}{expression_sql}"
1223
1224        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1225        return self.prepend_ctes(expression, expression_sql)
1226
1227    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1228        start = self.sql(expression, "start")
1229        start = f"START WITH {start}" if start else ""
1230        increment = self.sql(expression, "increment")
1231        increment = f" INCREMENT BY {increment}" if increment else ""
1232        minvalue = self.sql(expression, "minvalue")
1233        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1234        maxvalue = self.sql(expression, "maxvalue")
1235        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1236        owned = self.sql(expression, "owned")
1237        owned = f" OWNED BY {owned}" if owned else ""
1238
1239        cache = expression.args.get("cache")
1240        if cache is None:
1241            cache_str = ""
1242        elif cache is True:
1243            cache_str = " CACHE"
1244        else:
1245            cache_str = f" CACHE {cache}"
1246
1247        options = self.expressions(expression, key="options", flat=True, sep=" ")
1248        options = f" {options}" if options else ""
1249
1250        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1251
1252    def clone_sql(self, expression: exp.Clone) -> str:
1253        this = self.sql(expression, "this")
1254        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1255        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1256        return f"{shallow}{keyword} {this}"
1257
1258    def describe_sql(self, expression: exp.Describe) -> str:
1259        style = expression.args.get("style")
1260        style = f" {style}" if style else ""
1261        partition = self.sql(expression, "partition")
1262        partition = f" {partition}" if partition else ""
1263        format = self.sql(expression, "format")
1264        format = f" {format}" if format else ""
1265
1266        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1267
1268    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1269        tag = self.sql(expression, "tag")
1270        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1271
1272    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1273        with_ = self.sql(expression, "with")
1274        if with_:
1275            sql = f"{with_}{self.sep()}{sql}"
1276        return sql
1277
1278    def with_sql(self, expression: exp.With) -> str:
1279        sql = self.expressions(expression, flat=True)
1280        recursive = (
1281            "RECURSIVE "
1282            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1283            else ""
1284        )
1285        search = self.sql(expression, "search")
1286        search = f" {search}" if search else ""
1287
1288        return f"WITH {recursive}{sql}{search}"
1289
1290    def cte_sql(self, expression: exp.CTE) -> str:
1291        alias = expression.args.get("alias")
1292        if alias:
1293            alias.add_comments(expression.pop_comments())
1294
1295        alias_sql = self.sql(expression, "alias")
1296
1297        materialized = expression.args.get("materialized")
1298        if materialized is False:
1299            materialized = "NOT MATERIALIZED "
1300        elif materialized:
1301            materialized = "MATERIALIZED "
1302
1303        return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1304
1305    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1306        alias = self.sql(expression, "this")
1307        columns = self.expressions(expression, key="columns", flat=True)
1308        columns = f"({columns})" if columns else ""
1309
1310        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1311            columns = ""
1312            self.unsupported("Named columns are not supported in table alias.")
1313
1314        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1315            alias = self._next_name()
1316
1317        return f"{alias}{columns}"
1318
1319    def bitstring_sql(self, expression: exp.BitString) -> str:
1320        this = self.sql(expression, "this")
1321        if self.dialect.BIT_START:
1322            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1323        return f"{int(this, 2)}"
1324
1325    def hexstring_sql(
1326        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1327    ) -> str:
1328        this = self.sql(expression, "this")
1329        is_integer_type = expression.args.get("is_integer")
1330
1331        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1332            not self.dialect.HEX_START and not binary_function_repr
1333        ):
1334            # Integer representation will be returned if:
1335            # - The read dialect treats the hex value as integer literal but not the write
1336            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1337            return f"{int(this, 16)}"
1338
1339        if not is_integer_type:
1340            # Read dialect treats the hex value as BINARY/BLOB
1341            if binary_function_repr:
1342                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1343                return self.func(binary_function_repr, exp.Literal.string(this))
1344            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1345                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1346                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1347
1348        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1349
1350    def bytestring_sql(self, expression: exp.ByteString) -> str:
1351        this = self.sql(expression, "this")
1352        if self.dialect.BYTE_START:
1353            return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}"
1354        return this
1355
1356    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1357        this = self.sql(expression, "this")
1358        escape = expression.args.get("escape")
1359
1360        if self.dialect.UNICODE_START:
1361            escape_substitute = r"\\\1"
1362            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1363        else:
1364            escape_substitute = r"\\u\1"
1365            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1366
1367        if escape:
1368            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1369            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1370        else:
1371            escape_pattern = ESCAPED_UNICODE_RE
1372            escape_sql = ""
1373
1374        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1375            this = escape_pattern.sub(escape_substitute, this)
1376
1377        return f"{left_quote}{this}{right_quote}{escape_sql}"
1378
1379    def rawstring_sql(self, expression: exp.RawString) -> str:
1380        string = expression.this
1381        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1382            string = string.replace("\\", "\\\\")
1383
1384        string = self.escape_str(string, escape_backslash=False)
1385        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1386
1387    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1388        this = self.sql(expression, "this")
1389        specifier = self.sql(expression, "expression")
1390        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1391        return f"{this}{specifier}"
1392
1393    def datatype_sql(self, expression: exp.DataType) -> str:
1394        nested = ""
1395        values = ""
1396        interior = self.expressions(expression, flat=True)
1397
1398        type_value = expression.this
1399        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1400            type_sql = self.sql(expression, "kind")
1401        else:
1402            type_sql = (
1403                self.TYPE_MAPPING.get(type_value, type_value.value)
1404                if isinstance(type_value, exp.DataType.Type)
1405                else type_value
1406            )
1407
1408        if interior:
1409            if expression.args.get("nested"):
1410                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1411                if expression.args.get("values") is not None:
1412                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1413                    values = self.expressions(expression, key="values", flat=True)
1414                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1415            elif type_value == exp.DataType.Type.INTERVAL:
1416                nested = f" {interior}"
1417            else:
1418                nested = f"({interior})"
1419
1420        type_sql = f"{type_sql}{nested}{values}"
1421        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1422            exp.DataType.Type.TIMETZ,
1423            exp.DataType.Type.TIMESTAMPTZ,
1424        ):
1425            type_sql = f"{type_sql} WITH TIME ZONE"
1426
1427        return type_sql
1428
1429    def directory_sql(self, expression: exp.Directory) -> str:
1430        local = "LOCAL " if expression.args.get("local") else ""
1431        row_format = self.sql(expression, "row_format")
1432        row_format = f" {row_format}" if row_format else ""
1433        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1434
1435    def delete_sql(self, expression: exp.Delete) -> str:
1436        this = self.sql(expression, "this")
1437        this = f" FROM {this}" if this else ""
1438        using = self.sql(expression, "using")
1439        using = f" USING {using}" if using else ""
1440        cluster = self.sql(expression, "cluster")
1441        cluster = f" {cluster}" if cluster else ""
1442        where = self.sql(expression, "where")
1443        returning = self.sql(expression, "returning")
1444        limit = self.sql(expression, "limit")
1445        tables = self.expressions(expression, key="tables")
1446        tables = f" {tables}" if tables else ""
1447        if self.RETURNING_END:
1448            expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}"
1449        else:
1450            expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}"
1451        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1452
1453    def drop_sql(self, expression: exp.Drop) -> str:
1454        this = self.sql(expression, "this")
1455        expressions = self.expressions(expression, flat=True)
1456        expressions = f" ({expressions})" if expressions else ""
1457        kind = expression.args["kind"]
1458        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1459        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1460        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1461        on_cluster = self.sql(expression, "cluster")
1462        on_cluster = f" {on_cluster}" if on_cluster else ""
1463        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1464        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1465        cascade = " CASCADE" if expression.args.get("cascade") else ""
1466        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1467        purge = " PURGE" if expression.args.get("purge") else ""
1468        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1469
1470    def set_operation(self, expression: exp.SetOperation) -> str:
1471        op_type = type(expression)
1472        op_name = op_type.key.upper()
1473
1474        distinct = expression.args.get("distinct")
1475        if (
1476            distinct is False
1477            and op_type in (exp.Except, exp.Intersect)
1478            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1479        ):
1480            self.unsupported(f"{op_name} ALL is not supported")
1481
1482        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1483
1484        if distinct is None:
1485            distinct = default_distinct
1486            if distinct is None:
1487                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1488
1489        if distinct is default_distinct:
1490            distinct_or_all = ""
1491        else:
1492            distinct_or_all = " DISTINCT" if distinct else " ALL"
1493
1494        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1495        side_kind = f"{side_kind} " if side_kind else ""
1496
1497        by_name = " BY NAME" if expression.args.get("by_name") else ""
1498        on = self.expressions(expression, key="on", flat=True)
1499        on = f" ON ({on})" if on else ""
1500
1501        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1502
1503    def set_operations(self, expression: exp.SetOperation) -> str:
1504        if not self.SET_OP_MODIFIERS:
1505            limit = expression.args.get("limit")
1506            order = expression.args.get("order")
1507
1508            if limit or order:
1509                select = self._move_ctes_to_top_level(
1510                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1511                )
1512
1513                if limit:
1514                    select = select.limit(limit.pop(), copy=False)
1515                if order:
1516                    select = select.order_by(order.pop(), copy=False)
1517                return self.sql(select)
1518
1519        sqls: t.List[str] = []
1520        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1521
1522        while stack:
1523            node = stack.pop()
1524
1525            if isinstance(node, exp.SetOperation):
1526                stack.append(node.expression)
1527                stack.append(
1528                    self.maybe_comment(
1529                        self.set_operation(node), comments=node.comments, separated=True
1530                    )
1531                )
1532                stack.append(node.this)
1533            else:
1534                sqls.append(self.sql(node))
1535
1536        this = self.sep().join(sqls)
1537        this = self.query_modifiers(expression, this)
1538        return self.prepend_ctes(expression, this)
1539
1540    def fetch_sql(self, expression: exp.Fetch) -> str:
1541        direction = expression.args.get("direction")
1542        direction = f" {direction}" if direction else ""
1543        count = self.sql(expression, "count")
1544        count = f" {count}" if count else ""
1545        limit_options = self.sql(expression, "limit_options")
1546        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1547        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1548
1549    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1550        percent = " PERCENT" if expression.args.get("percent") else ""
1551        rows = " ROWS" if expression.args.get("rows") else ""
1552        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1553        if not with_ties and rows:
1554            with_ties = " ONLY"
1555        return f"{percent}{rows}{with_ties}"
1556
1557    def filter_sql(self, expression: exp.Filter) -> str:
1558        if self.AGGREGATE_FILTER_SUPPORTED:
1559            this = self.sql(expression, "this")
1560            where = self.sql(expression, "expression").strip()
1561            return f"{this} FILTER({where})"
1562
1563        agg = expression.this
1564        agg_arg = agg.this
1565        cond = expression.expression.this
1566        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1567        return self.sql(agg)
1568
1569    def hint_sql(self, expression: exp.Hint) -> str:
1570        if not self.QUERY_HINTS:
1571            self.unsupported("Hints are not supported")
1572            return ""
1573
1574        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1575
1576    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1577        using = self.sql(expression, "using")
1578        using = f" USING {using}" if using else ""
1579        columns = self.expressions(expression, key="columns", flat=True)
1580        columns = f"({columns})" if columns else ""
1581        partition_by = self.expressions(expression, key="partition_by", flat=True)
1582        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1583        where = self.sql(expression, "where")
1584        include = self.expressions(expression, key="include", flat=True)
1585        if include:
1586            include = f" INCLUDE ({include})"
1587        with_storage = self.expressions(expression, key="with_storage", flat=True)
1588        with_storage = f" WITH ({with_storage})" if with_storage else ""
1589        tablespace = self.sql(expression, "tablespace")
1590        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1591        on = self.sql(expression, "on")
1592        on = f" ON {on}" if on else ""
1593
1594        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1595
1596    def index_sql(self, expression: exp.Index) -> str:
1597        unique = "UNIQUE " if expression.args.get("unique") else ""
1598        primary = "PRIMARY " if expression.args.get("primary") else ""
1599        amp = "AMP " if expression.args.get("amp") else ""
1600        name = self.sql(expression, "this")
1601        name = f"{name} " if name else ""
1602        table = self.sql(expression, "table")
1603        table = f"{self.INDEX_ON} {table}" if table else ""
1604
1605        index = "INDEX " if not table else ""
1606
1607        params = self.sql(expression, "params")
1608        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1609
1610    def identifier_sql(self, expression: exp.Identifier) -> str:
1611        text = expression.name
1612        lower = text.lower()
1613        text = lower if self.normalize and not expression.quoted else text
1614        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1615        if (
1616            expression.quoted
1617            or self.dialect.can_identify(text, self.identify)
1618            or lower in self.RESERVED_KEYWORDS
1619            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1620        ):
1621            text = f"{self._identifier_start}{text}{self._identifier_end}"
1622        return text
1623
1624    def hex_sql(self, expression: exp.Hex) -> str:
1625        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1626        if self.dialect.HEX_LOWERCASE:
1627            text = self.func("LOWER", text)
1628
1629        return text
1630
1631    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1632        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1633        if not self.dialect.HEX_LOWERCASE:
1634            text = self.func("LOWER", text)
1635        return text
1636
1637    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1638        input_format = self.sql(expression, "input_format")
1639        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1640        output_format = self.sql(expression, "output_format")
1641        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1642        return self.sep().join((input_format, output_format))
1643
1644    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1645        string = self.sql(exp.Literal.string(expression.name))
1646        return f"{prefix}{string}"
1647
1648    def partition_sql(self, expression: exp.Partition) -> str:
1649        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1650        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1651
1652    def properties_sql(self, expression: exp.Properties) -> str:
1653        root_properties = []
1654        with_properties = []
1655
1656        for p in expression.expressions:
1657            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1658            if p_loc == exp.Properties.Location.POST_WITH:
1659                with_properties.append(p)
1660            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1661                root_properties.append(p)
1662
1663        root_props = self.root_properties(exp.Properties(expressions=root_properties))
1664        with_props = self.with_properties(exp.Properties(expressions=with_properties))
1665
1666        if root_props and with_props and not self.pretty:
1667            with_props = " " + with_props
1668
1669        return root_props + with_props
1670
1671    def root_properties(self, properties: exp.Properties) -> str:
1672        if properties.expressions:
1673            return self.expressions(properties, indent=False, sep=" ")
1674        return ""
1675
1676    def properties(
1677        self,
1678        properties: exp.Properties,
1679        prefix: str = "",
1680        sep: str = ", ",
1681        suffix: str = "",
1682        wrapped: bool = True,
1683    ) -> str:
1684        if properties.expressions:
1685            expressions = self.expressions(properties, sep=sep, indent=False)
1686            if expressions:
1687                expressions = self.wrap(expressions) if wrapped else expressions
1688                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1689        return ""
1690
1691    def with_properties(self, properties: exp.Properties) -> str:
1692        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1693
1694    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1695        properties_locs = defaultdict(list)
1696        for p in properties.expressions:
1697            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1698            if p_loc != exp.Properties.Location.UNSUPPORTED:
1699                properties_locs[p_loc].append(p)
1700            else:
1701                self.unsupported(f"Unsupported property {p.key}")
1702
1703        return properties_locs
1704
1705    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1706        if isinstance(expression.this, exp.Dot):
1707            return self.sql(expression, "this")
1708        return f"'{expression.name}'" if string_key else expression.name
1709
1710    def property_sql(self, expression: exp.Property) -> str:
1711        property_cls = expression.__class__
1712        if property_cls == exp.Property:
1713            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1714
1715        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1716        if not property_name:
1717            self.unsupported(f"Unsupported property {expression.key}")
1718
1719        return f"{property_name}={self.sql(expression, 'this')}"
1720
1721    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1722        if self.SUPPORTS_CREATE_TABLE_LIKE:
1723            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1724            options = f" {options}" if options else ""
1725
1726            like = f"LIKE {self.sql(expression, 'this')}{options}"
1727            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1728                like = f"({like})"
1729
1730            return like
1731
1732        if expression.expressions:
1733            self.unsupported("Transpilation of LIKE property options is unsupported")
1734
1735        select = exp.select("*").from_(expression.this).limit(0)
1736        return f"AS {self.sql(select)}"
1737
1738    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1739        no = "NO " if expression.args.get("no") else ""
1740        protection = " PROTECTION" if expression.args.get("protection") else ""
1741        return f"{no}FALLBACK{protection}"
1742
1743    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1744        no = "NO " if expression.args.get("no") else ""
1745        local = expression.args.get("local")
1746        local = f"{local} " if local else ""
1747        dual = "DUAL " if expression.args.get("dual") else ""
1748        before = "BEFORE " if expression.args.get("before") else ""
1749        after = "AFTER " if expression.args.get("after") else ""
1750        return f"{no}{local}{dual}{before}{after}JOURNAL"
1751
1752    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1753        freespace = self.sql(expression, "this")
1754        percent = " PERCENT" if expression.args.get("percent") else ""
1755        return f"FREESPACE={freespace}{percent}"
1756
1757    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1758        if expression.args.get("default"):
1759            property = "DEFAULT"
1760        elif expression.args.get("on"):
1761            property = "ON"
1762        else:
1763            property = "OFF"
1764        return f"CHECKSUM={property}"
1765
1766    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1767        if expression.args.get("no"):
1768            return "NO MERGEBLOCKRATIO"
1769        if expression.args.get("default"):
1770            return "DEFAULT MERGEBLOCKRATIO"
1771
1772        percent = " PERCENT" if expression.args.get("percent") else ""
1773        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1774
1775    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1776        default = expression.args.get("default")
1777        minimum = expression.args.get("minimum")
1778        maximum = expression.args.get("maximum")
1779        if default or minimum or maximum:
1780            if default:
1781                prop = "DEFAULT"
1782            elif minimum:
1783                prop = "MINIMUM"
1784            else:
1785                prop = "MAXIMUM"
1786            return f"{prop} DATABLOCKSIZE"
1787        units = expression.args.get("units")
1788        units = f" {units}" if units else ""
1789        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
1790
1791    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1792        autotemp = expression.args.get("autotemp")
1793        always = expression.args.get("always")
1794        default = expression.args.get("default")
1795        manual = expression.args.get("manual")
1796        never = expression.args.get("never")
1797
1798        if autotemp is not None:
1799            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1800        elif always:
1801            prop = "ALWAYS"
1802        elif default:
1803            prop = "DEFAULT"
1804        elif manual:
1805            prop = "MANUAL"
1806        elif never:
1807            prop = "NEVER"
1808        return f"BLOCKCOMPRESSION={prop}"
1809
1810    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1811        no = expression.args.get("no")
1812        no = " NO" if no else ""
1813        concurrent = expression.args.get("concurrent")
1814        concurrent = " CONCURRENT" if concurrent else ""
1815        target = self.sql(expression, "target")
1816        target = f" {target}" if target else ""
1817        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1818
1819    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1820        if isinstance(expression.this, list):
1821            return f"IN ({self.expressions(expression, key='this', flat=True)})"
1822        if expression.this:
1823            modulus = self.sql(expression, "this")
1824            remainder = self.sql(expression, "expression")
1825            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1826
1827        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1828        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1829        return f"FROM ({from_expressions}) TO ({to_expressions})"
1830
1831    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1832        this = self.sql(expression, "this")
1833
1834        for_values_or_default = expression.expression
1835        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1836            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1837        else:
1838            for_values_or_default = " DEFAULT"
1839
1840        return f"PARTITION OF {this}{for_values_or_default}"
1841
1842    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1843        kind = expression.args.get("kind")
1844        this = f" {self.sql(expression, 'this')}" if expression.this else ""
1845        for_or_in = expression.args.get("for_or_in")
1846        for_or_in = f" {for_or_in}" if for_or_in else ""
1847        lock_type = expression.args.get("lock_type")
1848        override = " OVERRIDE" if expression.args.get("override") else ""
1849        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1850
1851    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1852        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1853        statistics = expression.args.get("statistics")
1854        statistics_sql = ""
1855        if statistics is not None:
1856            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1857        return f"{data_sql}{statistics_sql}"
1858
1859    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1860        this = self.sql(expression, "this")
1861        this = f"HISTORY_TABLE={this}" if this else ""
1862        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
1863        data_consistency = (
1864            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
1865        )
1866        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
1867        retention_period = (
1868            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
1869        )
1870
1871        if this:
1872            on_sql = self.func("ON", this, data_consistency, retention_period)
1873        else:
1874            on_sql = "ON" if expression.args.get("on") else "OFF"
1875
1876        sql = f"SYSTEM_VERSIONING={on_sql}"
1877
1878        return f"WITH({sql})" if expression.args.get("with") else sql
1879
1880    def insert_sql(self, expression: exp.Insert) -> str:
1881        hint = self.sql(expression, "hint")
1882        overwrite = expression.args.get("overwrite")
1883
1884        if isinstance(expression.this, exp.Directory):
1885            this = " OVERWRITE" if overwrite else " INTO"
1886        else:
1887            this = self.INSERT_OVERWRITE if overwrite else " INTO"
1888
1889        stored = self.sql(expression, "stored")
1890        stored = f" {stored}" if stored else ""
1891        alternative = expression.args.get("alternative")
1892        alternative = f" OR {alternative}" if alternative else ""
1893        ignore = " IGNORE" if expression.args.get("ignore") else ""
1894        is_function = expression.args.get("is_function")
1895        if is_function:
1896            this = f"{this} FUNCTION"
1897        this = f"{this} {self.sql(expression, 'this')}"
1898
1899        exists = " IF EXISTS" if expression.args.get("exists") else ""
1900        where = self.sql(expression, "where")
1901        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
1902        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
1903        on_conflict = self.sql(expression, "conflict")
1904        on_conflict = f" {on_conflict}" if on_conflict else ""
1905        by_name = " BY NAME" if expression.args.get("by_name") else ""
1906        returning = self.sql(expression, "returning")
1907
1908        if self.RETURNING_END:
1909            expression_sql = f"{expression_sql}{on_conflict}{returning}"
1910        else:
1911            expression_sql = f"{returning}{expression_sql}{on_conflict}"
1912
1913        partition_by = self.sql(expression, "partition")
1914        partition_by = f" {partition_by}" if partition_by else ""
1915        settings = self.sql(expression, "settings")
1916        settings = f" {settings}" if settings else ""
1917
1918        source = self.sql(expression, "source")
1919        source = f"TABLE {source}" if source else ""
1920
1921        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
1922        return self.prepend_ctes(expression, sql)
1923
1924    def introducer_sql(self, expression: exp.Introducer) -> str:
1925        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
1926
1927    def kill_sql(self, expression: exp.Kill) -> str:
1928        kind = self.sql(expression, "kind")
1929        kind = f" {kind}" if kind else ""
1930        this = self.sql(expression, "this")
1931        this = f" {this}" if this else ""
1932        return f"KILL{kind}{this}"
1933
1934    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
1935        return expression.name
1936
1937    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
1938        return expression.name
1939
1940    def onconflict_sql(self, expression: exp.OnConflict) -> str:
1941        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
1942
1943        constraint = self.sql(expression, "constraint")
1944        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
1945
1946        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
1947        conflict_keys = f"({conflict_keys}) " if conflict_keys else " "
1948        action = self.sql(expression, "action")
1949
1950        expressions = self.expressions(expression, flat=True)
1951        if expressions:
1952            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
1953            expressions = f" {set_keyword}{expressions}"
1954
1955        where = self.sql(expression, "where")
1956        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
1957
1958    def returning_sql(self, expression: exp.Returning) -> str:
1959        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
1960
1961    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
1962        fields = self.sql(expression, "fields")
1963        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
1964        escaped = self.sql(expression, "escaped")
1965        escaped = f" ESCAPED BY {escaped}" if escaped else ""
1966        items = self.sql(expression, "collection_items")
1967        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
1968        keys = self.sql(expression, "map_keys")
1969        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
1970        lines = self.sql(expression, "lines")
1971        lines = f" LINES TERMINATED BY {lines}" if lines else ""
1972        null = self.sql(expression, "null")
1973        null = f" NULL DEFINED AS {null}" if null else ""
1974        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
1975
1976    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
1977        return f"WITH ({self.expressions(expression, flat=True)})"
1978
1979    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
1980        this = f"{self.sql(expression, 'this')} INDEX"
1981        target = self.sql(expression, "target")
1982        target = f" FOR {target}" if target else ""
1983        return f"{this}{target} ({self.expressions(expression, flat=True)})"
1984
1985    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
1986        this = self.sql(expression, "this")
1987        kind = self.sql(expression, "kind")
1988        expr = self.sql(expression, "expression")
1989        return f"{this} ({kind} => {expr})"
1990
1991    def table_parts(self, expression: exp.Table) -> str:
1992        return ".".join(
1993            self.sql(part)
1994            for part in (
1995                expression.args.get("catalog"),
1996                expression.args.get("db"),
1997                expression.args.get("this"),
1998            )
1999            if part is not None
2000        )
2001
2002    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2003        table = self.table_parts(expression)
2004        only = "ONLY " if expression.args.get("only") else ""
2005        partition = self.sql(expression, "partition")
2006        partition = f" {partition}" if partition else ""
2007        version = self.sql(expression, "version")
2008        version = f" {version}" if version else ""
2009        alias = self.sql(expression, "alias")
2010        alias = f"{sep}{alias}" if alias else ""
2011
2012        sample = self.sql(expression, "sample")
2013        if self.dialect.ALIAS_POST_TABLESAMPLE:
2014            sample_pre_alias = sample
2015            sample_post_alias = ""
2016        else:
2017            sample_pre_alias = ""
2018            sample_post_alias = sample
2019
2020        hints = self.expressions(expression, key="hints", sep=" ")
2021        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2022        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2023        joins = self.indent(
2024            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2025        )
2026        laterals = self.expressions(expression, key="laterals", sep="")
2027
2028        file_format = self.sql(expression, "format")
2029        if file_format:
2030            pattern = self.sql(expression, "pattern")
2031            pattern = f", PATTERN => {pattern}" if pattern else ""
2032            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2033
2034        ordinality = expression.args.get("ordinality") or ""
2035        if ordinality:
2036            ordinality = f" WITH ORDINALITY{alias}"
2037            alias = ""
2038
2039        when = self.sql(expression, "when")
2040        if when:
2041            table = f"{table} {when}"
2042
2043        changes = self.sql(expression, "changes")
2044        changes = f" {changes}" if changes else ""
2045
2046        rows_from = self.expressions(expression, key="rows_from")
2047        if rows_from:
2048            table = f"ROWS FROM {self.wrap(rows_from)}"
2049
2050        return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2051
2052    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2053        table = self.func("TABLE", expression.this)
2054        alias = self.sql(expression, "alias")
2055        alias = f" AS {alias}" if alias else ""
2056        sample = self.sql(expression, "sample")
2057        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2058        joins = self.indent(
2059            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2060        )
2061        return f"{table}{alias}{pivots}{sample}{joins}"
2062
2063    def tablesample_sql(
2064        self,
2065        expression: exp.TableSample,
2066        tablesample_keyword: t.Optional[str] = None,
2067    ) -> str:
2068        method = self.sql(expression, "method")
2069        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2070        numerator = self.sql(expression, "bucket_numerator")
2071        denominator = self.sql(expression, "bucket_denominator")
2072        field = self.sql(expression, "bucket_field")
2073        field = f" ON {field}" if field else ""
2074        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2075        seed = self.sql(expression, "seed")
2076        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2077
2078        size = self.sql(expression, "size")
2079        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2080            size = f"{size} ROWS"
2081
2082        percent = self.sql(expression, "percent")
2083        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2084            percent = f"{percent} PERCENT"
2085
2086        expr = f"{bucket}{percent}{size}"
2087        if self.TABLESAMPLE_REQUIRES_PARENS:
2088            expr = f"({expr})"
2089
2090        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2091
2092    def pivot_sql(self, expression: exp.Pivot) -> str:
2093        expressions = self.expressions(expression, flat=True)
2094        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2095
2096        group = self.sql(expression, "group")
2097
2098        if expression.this:
2099            this = self.sql(expression, "this")
2100            if not expressions:
2101                return f"UNPIVOT {this}"
2102
2103            on = f"{self.seg('ON')} {expressions}"
2104            into = self.sql(expression, "into")
2105            into = f"{self.seg('INTO')} {into}" if into else ""
2106            using = self.expressions(expression, key="using", flat=True)
2107            using = f"{self.seg('USING')} {using}" if using else ""
2108            return f"{direction} {this}{on}{into}{using}{group}"
2109
2110        alias = self.sql(expression, "alias")
2111        alias = f" AS {alias}" if alias else ""
2112
2113        fields = self.expressions(
2114            expression,
2115            "fields",
2116            sep=" ",
2117            dynamic=True,
2118            new_line=True,
2119            skip_first=True,
2120            skip_last=True,
2121        )
2122
2123        include_nulls = expression.args.get("include_nulls")
2124        if include_nulls is not None:
2125            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2126        else:
2127            nulls = ""
2128
2129        default_on_null = self.sql(expression, "default_on_null")
2130        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2131        return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2132
2133    def version_sql(self, expression: exp.Version) -> str:
2134        this = f"FOR {expression.name}"
2135        kind = expression.text("kind")
2136        expr = self.sql(expression, "expression")
2137        return f"{this} {kind} {expr}"
2138
2139    def tuple_sql(self, expression: exp.Tuple) -> str:
2140        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2141
2142    def update_sql(self, expression: exp.Update) -> str:
2143        this = self.sql(expression, "this")
2144        set_sql = self.expressions(expression, flat=True)
2145        from_sql = self.sql(expression, "from")
2146        where_sql = self.sql(expression, "where")
2147        returning = self.sql(expression, "returning")
2148        order = self.sql(expression, "order")
2149        limit = self.sql(expression, "limit")
2150        if self.RETURNING_END:
2151            expression_sql = f"{from_sql}{where_sql}{returning}"
2152        else:
2153            expression_sql = f"{returning}{from_sql}{where_sql}"
2154        sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}"
2155        return self.prepend_ctes(expression, sql)
2156
2157    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2158        values_as_table = values_as_table and self.VALUES_AS_TABLE
2159
2160        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2161        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2162            args = self.expressions(expression)
2163            alias = self.sql(expression, "alias")
2164            values = f"VALUES{self.seg('')}{args}"
2165            values = (
2166                f"({values})"
2167                if self.WRAP_DERIVED_VALUES
2168                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2169                else values
2170            )
2171            return f"{values} AS {alias}" if alias else values
2172
2173        # Converts `VALUES...` expression into a series of select unions.
2174        alias_node = expression.args.get("alias")
2175        column_names = alias_node and alias_node.columns
2176
2177        selects: t.List[exp.Query] = []
2178
2179        for i, tup in enumerate(expression.expressions):
2180            row = tup.expressions
2181
2182            if i == 0 and column_names:
2183                row = [
2184                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2185                ]
2186
2187            selects.append(exp.Select(expressions=row))
2188
2189        if self.pretty:
2190            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2191            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2192            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2193            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2194            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2195
2196        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2197        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2198        return f"({unions}){alias}"
2199
2200    def var_sql(self, expression: exp.Var) -> str:
2201        return self.sql(expression, "this")
2202
2203    @unsupported_args("expressions")
2204    def into_sql(self, expression: exp.Into) -> str:
2205        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2206        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2207        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2208
2209    def from_sql(self, expression: exp.From) -> str:
2210        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2211
2212    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2213        grouping_sets = self.expressions(expression, indent=False)
2214        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2215
2216    def rollup_sql(self, expression: exp.Rollup) -> str:
2217        expressions = self.expressions(expression, indent=False)
2218        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2219
2220    def cube_sql(self, expression: exp.Cube) -> str:
2221        expressions = self.expressions(expression, indent=False)
2222        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2223
2224    def group_sql(self, expression: exp.Group) -> str:
2225        group_by_all = expression.args.get("all")
2226        if group_by_all is True:
2227            modifier = " ALL"
2228        elif group_by_all is False:
2229            modifier = " DISTINCT"
2230        else:
2231            modifier = ""
2232
2233        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2234
2235        grouping_sets = self.expressions(expression, key="grouping_sets")
2236        cube = self.expressions(expression, key="cube")
2237        rollup = self.expressions(expression, key="rollup")
2238
2239        groupings = csv(
2240            self.seg(grouping_sets) if grouping_sets else "",
2241            self.seg(cube) if cube else "",
2242            self.seg(rollup) if rollup else "",
2243            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2244            sep=self.GROUPINGS_SEP,
2245        )
2246
2247        if (
2248            expression.expressions
2249            and groupings
2250            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2251        ):
2252            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2253
2254        return f"{group_by}{groupings}"
2255
2256    def having_sql(self, expression: exp.Having) -> str:
2257        this = self.indent(self.sql(expression, "this"))
2258        return f"{self.seg('HAVING')}{self.sep()}{this}"
2259
2260    def connect_sql(self, expression: exp.Connect) -> str:
2261        start = self.sql(expression, "start")
2262        start = self.seg(f"START WITH {start}") if start else ""
2263        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2264        connect = self.sql(expression, "connect")
2265        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2266        return start + connect
2267
2268    def prior_sql(self, expression: exp.Prior) -> str:
2269        return f"PRIOR {self.sql(expression, 'this')}"
2270
2271    def join_sql(self, expression: exp.Join) -> str:
2272        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2273            side = None
2274        else:
2275            side = expression.side
2276
2277        op_sql = " ".join(
2278            op
2279            for op in (
2280                expression.method,
2281                "GLOBAL" if expression.args.get("global") else None,
2282                side,
2283                expression.kind,
2284                expression.hint if self.JOIN_HINTS else None,
2285            )
2286            if op
2287        )
2288        match_cond = self.sql(expression, "match_condition")
2289        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2290        on_sql = self.sql(expression, "on")
2291        using = expression.args.get("using")
2292
2293        if not on_sql and using:
2294            on_sql = csv(*(self.sql(column) for column in using))
2295
2296        this = expression.this
2297        this_sql = self.sql(this)
2298
2299        exprs = self.expressions(expression)
2300        if exprs:
2301            this_sql = f"{this_sql},{self.seg(exprs)}"
2302
2303        if on_sql:
2304            on_sql = self.indent(on_sql, skip_first=True)
2305            space = self.seg(" " * self.pad) if self.pretty else " "
2306            if using:
2307                on_sql = f"{space}USING ({on_sql})"
2308            else:
2309                on_sql = f"{space}ON {on_sql}"
2310        elif not op_sql:
2311            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2312                return f" {this_sql}"
2313
2314            return f", {this_sql}"
2315
2316        if op_sql != "STRAIGHT_JOIN":
2317            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2318
2319        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2320        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2321
2322    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2323        args = self.expressions(expression, flat=True)
2324        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2325        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2326
2327    def lateral_op(self, expression: exp.Lateral) -> str:
2328        cross_apply = expression.args.get("cross_apply")
2329
2330        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2331        if cross_apply is True:
2332            op = "INNER JOIN "
2333        elif cross_apply is False:
2334            op = "LEFT JOIN "
2335        else:
2336            op = ""
2337
2338        return f"{op}LATERAL"
2339
2340    def lateral_sql(self, expression: exp.Lateral) -> str:
2341        this = self.sql(expression, "this")
2342
2343        if expression.args.get("view"):
2344            alias = expression.args["alias"]
2345            columns = self.expressions(alias, key="columns", flat=True)
2346            table = f" {alias.name}" if alias.name else ""
2347            columns = f" AS {columns}" if columns else ""
2348            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2349            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2350
2351        alias = self.sql(expression, "alias")
2352        alias = f" AS {alias}" if alias else ""
2353
2354        ordinality = expression.args.get("ordinality") or ""
2355        if ordinality:
2356            ordinality = f" WITH ORDINALITY{alias}"
2357            alias = ""
2358
2359        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2360
2361    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2362        this = self.sql(expression, "this")
2363
2364        args = [
2365            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2366            for e in (expression.args.get(k) for k in ("offset", "expression"))
2367            if e
2368        ]
2369
2370        args_sql = ", ".join(self.sql(e) for e in args)
2371        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2372        expressions = self.expressions(expression, flat=True)
2373        limit_options = self.sql(expression, "limit_options")
2374        expressions = f" BY {expressions}" if expressions else ""
2375
2376        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2377
2378    def offset_sql(self, expression: exp.Offset) -> str:
2379        this = self.sql(expression, "this")
2380        value = expression.expression
2381        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2382        expressions = self.expressions(expression, flat=True)
2383        expressions = f" BY {expressions}" if expressions else ""
2384        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2385
2386    def setitem_sql(self, expression: exp.SetItem) -> str:
2387        kind = self.sql(expression, "kind")
2388        kind = f"{kind} " if kind else ""
2389        this = self.sql(expression, "this")
2390        expressions = self.expressions(expression)
2391        collate = self.sql(expression, "collate")
2392        collate = f" COLLATE {collate}" if collate else ""
2393        global_ = "GLOBAL " if expression.args.get("global") else ""
2394        return f"{global_}{kind}{this}{expressions}{collate}"
2395
2396    def set_sql(self, expression: exp.Set) -> str:
2397        expressions = f" {self.expressions(expression, flat=True)}"
2398        tag = " TAG" if expression.args.get("tag") else ""
2399        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2400
2401    def pragma_sql(self, expression: exp.Pragma) -> str:
2402        return f"PRAGMA {self.sql(expression, 'this')}"
2403
2404    def lock_sql(self, expression: exp.Lock) -> str:
2405        if not self.LOCKING_READS_SUPPORTED:
2406            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2407            return ""
2408
2409        lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE"
2410        expressions = self.expressions(expression, flat=True)
2411        expressions = f" OF {expressions}" if expressions else ""
2412        wait = expression.args.get("wait")
2413
2414        if wait is not None:
2415            if isinstance(wait, exp.Literal):
2416                wait = f" WAIT {self.sql(wait)}"
2417            else:
2418                wait = " NOWAIT" if wait else " SKIP LOCKED"
2419
2420        return f"{lock_type}{expressions}{wait or ''}"
2421
2422    def literal_sql(self, expression: exp.Literal) -> str:
2423        text = expression.this or ""
2424        if expression.is_string:
2425            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2426        return text
2427
2428    def escape_str(self, text: str, escape_backslash: bool = True) -> str:
2429        if self.dialect.ESCAPED_SEQUENCES:
2430            to_escaped = self.dialect.ESCAPED_SEQUENCES
2431            text = "".join(
2432                to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text
2433            )
2434
2435        return self._replace_line_breaks(text).replace(
2436            self.dialect.QUOTE_END, self._escaped_quote_end
2437        )
2438
2439    def loaddata_sql(self, expression: exp.LoadData) -> str:
2440        local = " LOCAL" if expression.args.get("local") else ""
2441        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2442        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2443        this = f" INTO TABLE {self.sql(expression, 'this')}"
2444        partition = self.sql(expression, "partition")
2445        partition = f" {partition}" if partition else ""
2446        input_format = self.sql(expression, "input_format")
2447        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2448        serde = self.sql(expression, "serde")
2449        serde = f" SERDE {serde}" if serde else ""
2450        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2451
2452    def null_sql(self, *_) -> str:
2453        return "NULL"
2454
2455    def boolean_sql(self, expression: exp.Boolean) -> str:
2456        return "TRUE" if expression.this else "FALSE"
2457
2458    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2459        this = self.sql(expression, "this")
2460        this = f"{this} " if this else this
2461        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2462        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
2463
2464    def withfill_sql(self, expression: exp.WithFill) -> str:
2465        from_sql = self.sql(expression, "from")
2466        from_sql = f" FROM {from_sql}" if from_sql else ""
2467        to_sql = self.sql(expression, "to")
2468        to_sql = f" TO {to_sql}" if to_sql else ""
2469        step_sql = self.sql(expression, "step")
2470        step_sql = f" STEP {step_sql}" if step_sql else ""
2471        interpolated_values = [
2472            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2473            if isinstance(e, exp.Alias)
2474            else self.sql(e, "this")
2475            for e in expression.args.get("interpolate") or []
2476        ]
2477        interpolate = (
2478            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2479        )
2480        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2481
2482    def cluster_sql(self, expression: exp.Cluster) -> str:
2483        return self.op_expressions("CLUSTER BY", expression)
2484
2485    def distribute_sql(self, expression: exp.Distribute) -> str:
2486        return self.op_expressions("DISTRIBUTE BY", expression)
2487
2488    def sort_sql(self, expression: exp.Sort) -> str:
2489        return self.op_expressions("SORT BY", expression)
2490
2491    def ordered_sql(self, expression: exp.Ordered) -> str:
2492        desc = expression.args.get("desc")
2493        asc = not desc
2494
2495        nulls_first = expression.args.get("nulls_first")
2496        nulls_last = not nulls_first
2497        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2498        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2499        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2500
2501        this = self.sql(expression, "this")
2502
2503        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2504        nulls_sort_change = ""
2505        if nulls_first and (
2506            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2507        ):
2508            nulls_sort_change = " NULLS FIRST"
2509        elif (
2510            nulls_last
2511            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2512            and not nulls_are_last
2513        ):
2514            nulls_sort_change = " NULLS LAST"
2515
2516        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2517        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2518            window = expression.find_ancestor(exp.Window, exp.Select)
2519            if isinstance(window, exp.Window) and window.args.get("spec"):
2520                self.unsupported(
2521                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2522                )
2523                nulls_sort_change = ""
2524            elif self.NULL_ORDERING_SUPPORTED is False and (
2525                (asc and nulls_sort_change == " NULLS LAST")
2526                or (desc and nulls_sort_change == " NULLS FIRST")
2527            ):
2528                # BigQuery does not allow these ordering/nulls combinations when used under
2529                # an aggregation func or under a window containing one
2530                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2531
2532                if isinstance(ancestor, exp.Window):
2533                    ancestor = ancestor.this
2534                if isinstance(ancestor, exp.AggFunc):
2535                    self.unsupported(
2536                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2537                    )
2538                    nulls_sort_change = ""
2539            elif self.NULL_ORDERING_SUPPORTED is None:
2540                if expression.this.is_int:
2541                    self.unsupported(
2542                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2543                    )
2544                elif not isinstance(expression.this, exp.Rand):
2545                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2546                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2547                nulls_sort_change = ""
2548
2549        with_fill = self.sql(expression, "with_fill")
2550        with_fill = f" {with_fill}" if with_fill else ""
2551
2552        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2553
2554    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2555        window_frame = self.sql(expression, "window_frame")
2556        window_frame = f"{window_frame} " if window_frame else ""
2557
2558        this = self.sql(expression, "this")
2559
2560        return f"{window_frame}{this}"
2561
2562    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2563        partition = self.partition_by_sql(expression)
2564        order = self.sql(expression, "order")
2565        measures = self.expressions(expression, key="measures")
2566        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2567        rows = self.sql(expression, "rows")
2568        rows = self.seg(rows) if rows else ""
2569        after = self.sql(expression, "after")
2570        after = self.seg(after) if after else ""
2571        pattern = self.sql(expression, "pattern")
2572        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2573        definition_sqls = [
2574            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2575            for definition in expression.args.get("define", [])
2576        ]
2577        definitions = self.expressions(sqls=definition_sqls)
2578        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2579        body = "".join(
2580            (
2581                partition,
2582                order,
2583                measures,
2584                rows,
2585                after,
2586                pattern,
2587                define,
2588            )
2589        )
2590        alias = self.sql(expression, "alias")
2591        alias = f" {alias}" if alias else ""
2592        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2593
2594    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2595        limit = expression.args.get("limit")
2596
2597        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2598            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2599        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2600            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2601
2602        return csv(
2603            *sqls,
2604            *[self.sql(join) for join in expression.args.get("joins") or []],
2605            self.sql(expression, "match"),
2606            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2607            self.sql(expression, "prewhere"),
2608            self.sql(expression, "where"),
2609            self.sql(expression, "connect"),
2610            self.sql(expression, "group"),
2611            self.sql(expression, "having"),
2612            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2613            self.sql(expression, "order"),
2614            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2615            *self.after_limit_modifiers(expression),
2616            self.options_modifier(expression),
2617            self.for_modifiers(expression),
2618            sep="",
2619        )
2620
2621    def options_modifier(self, expression: exp.Expression) -> str:
2622        options = self.expressions(expression, key="options")
2623        return f" {options}" if options else ""
2624
2625    def for_modifiers(self, expression: exp.Expression) -> str:
2626        for_modifiers = self.expressions(expression, key="for")
2627        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
2628
2629    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2630        self.unsupported("Unsupported query option.")
2631        return ""
2632
2633    def offset_limit_modifiers(
2634        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2635    ) -> t.List[str]:
2636        return [
2637            self.sql(expression, "offset") if fetch else self.sql(limit),
2638            self.sql(limit) if fetch else self.sql(expression, "offset"),
2639        ]
2640
2641    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2642        locks = self.expressions(expression, key="locks", sep=" ")
2643        locks = f" {locks}" if locks else ""
2644        return [locks, self.sql(expression, "sample")]
2645
2646    def select_sql(self, expression: exp.Select) -> str:
2647        into = expression.args.get("into")
2648        if not self.SUPPORTS_SELECT_INTO and into:
2649            into.pop()
2650
2651        hint = self.sql(expression, "hint")
2652        distinct = self.sql(expression, "distinct")
2653        distinct = f" {distinct}" if distinct else ""
2654        kind = self.sql(expression, "kind")
2655
2656        limit = expression.args.get("limit")
2657        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2658            top = self.limit_sql(limit, top=True)
2659            limit.pop()
2660        else:
2661            top = ""
2662
2663        expressions = self.expressions(expression)
2664
2665        if kind:
2666            if kind in self.SELECT_KINDS:
2667                kind = f" AS {kind}"
2668            else:
2669                if kind == "STRUCT":
2670                    expressions = self.expressions(
2671                        sqls=[
2672                            self.sql(
2673                                exp.Struct(
2674                                    expressions=[
2675                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2676                                        if isinstance(e, exp.Alias)
2677                                        else e
2678                                        for e in expression.expressions
2679                                    ]
2680                                )
2681                            )
2682                        ]
2683                    )
2684                kind = ""
2685
2686        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2687        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2688
2689        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2690        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2691        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2692        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2693        sql = self.query_modifiers(
2694            expression,
2695            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2696            self.sql(expression, "into", comment=False),
2697            self.sql(expression, "from", comment=False),
2698        )
2699
2700        # If both the CTE and SELECT clauses have comments, generate the latter earlier
2701        if expression.args.get("with"):
2702            sql = self.maybe_comment(sql, expression)
2703            expression.pop_comments()
2704
2705        sql = self.prepend_ctes(expression, sql)
2706
2707        if not self.SUPPORTS_SELECT_INTO and into:
2708            if into.args.get("temporary"):
2709                table_kind = " TEMPORARY"
2710            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
2711                table_kind = " UNLOGGED"
2712            else:
2713                table_kind = ""
2714            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
2715
2716        return sql
2717
2718    def schema_sql(self, expression: exp.Schema) -> str:
2719        this = self.sql(expression, "this")
2720        sql = self.schema_columns_sql(expression)
2721        return f"{this} {sql}" if this and sql else this or sql
2722
2723    def schema_columns_sql(self, expression: exp.Schema) -> str:
2724        if expression.expressions:
2725            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2726        return ""
2727
2728    def star_sql(self, expression: exp.Star) -> str:
2729        except_ = self.expressions(expression, key="except", flat=True)
2730        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2731        replace = self.expressions(expression, key="replace", flat=True)
2732        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
2733        rename = self.expressions(expression, key="rename", flat=True)
2734        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
2735        return f"*{except_}{replace}{rename}"
2736
2737    def parameter_sql(self, expression: exp.Parameter) -> str:
2738        this = self.sql(expression, "this")
2739        return f"{self.PARAMETER_TOKEN}{this}"
2740
2741    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2742        this = self.sql(expression, "this")
2743        kind = expression.text("kind")
2744        if kind:
2745            kind = f"{kind}."
2746        return f"@@{kind}{this}"
2747
2748    def placeholder_sql(self, expression: exp.Placeholder) -> str:
2749        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
2750
2751    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2752        alias = self.sql(expression, "alias")
2753        alias = f"{sep}{alias}" if alias else ""
2754        sample = self.sql(expression, "sample")
2755        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
2756            alias = f"{sample}{alias}"
2757
2758            # Set to None so it's not generated again by self.query_modifiers()
2759            expression.set("sample", None)
2760
2761        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2762        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2763        return self.prepend_ctes(expression, sql)
2764
2765    def qualify_sql(self, expression: exp.Qualify) -> str:
2766        this = self.indent(self.sql(expression, "this"))
2767        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
2768
2769    def unnest_sql(self, expression: exp.Unnest) -> str:
2770        args = self.expressions(expression, flat=True)
2771
2772        alias = expression.args.get("alias")
2773        offset = expression.args.get("offset")
2774
2775        if self.UNNEST_WITH_ORDINALITY:
2776            if alias and isinstance(offset, exp.Expression):
2777                alias.append("columns", offset)
2778
2779        if alias and self.dialect.UNNEST_COLUMN_ONLY:
2780            columns = alias.columns
2781            alias = self.sql(columns[0]) if columns else ""
2782        else:
2783            alias = self.sql(alias)
2784
2785        alias = f" AS {alias}" if alias else alias
2786        if self.UNNEST_WITH_ORDINALITY:
2787            suffix = f" WITH ORDINALITY{alias}" if offset else alias
2788        else:
2789            if isinstance(offset, exp.Expression):
2790                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
2791            elif offset:
2792                suffix = f"{alias} WITH OFFSET"
2793            else:
2794                suffix = alias
2795
2796        return f"UNNEST({args}){suffix}"
2797
2798    def prewhere_sql(self, expression: exp.PreWhere) -> str:
2799        return ""
2800
2801    def where_sql(self, expression: exp.Where) -> str:
2802        this = self.indent(self.sql(expression, "this"))
2803        return f"{self.seg('WHERE')}{self.sep()}{this}"
2804
2805    def window_sql(self, expression: exp.Window) -> str:
2806        this = self.sql(expression, "this")
2807        partition = self.partition_by_sql(expression)
2808        order = expression.args.get("order")
2809        order = self.order_sql(order, flat=True) if order else ""
2810        spec = self.sql(expression, "spec")
2811        alias = self.sql(expression, "alias")
2812        over = self.sql(expression, "over") or "OVER"
2813
2814        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
2815
2816        first = expression.args.get("first")
2817        if first is None:
2818            first = ""
2819        else:
2820            first = "FIRST" if first else "LAST"
2821
2822        if not partition and not order and not spec and alias:
2823            return f"{this} {alias}"
2824
2825        args = self.format_args(
2826            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
2827        )
2828        return f"{this} ({args})"
2829
2830    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
2831        partition = self.expressions(expression, key="partition_by", flat=True)
2832        return f"PARTITION BY {partition}" if partition else ""
2833
2834    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
2835        kind = self.sql(expression, "kind")
2836        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
2837        end = (
2838            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
2839            or "CURRENT ROW"
2840        )
2841
2842        window_spec = f"{kind} BETWEEN {start} AND {end}"
2843
2844        exclude = self.sql(expression, "exclude")
2845        if exclude:
2846            if self.SUPPORTS_WINDOW_EXCLUDE:
2847                window_spec += f" EXCLUDE {exclude}"
2848            else:
2849                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
2850
2851        return window_spec
2852
2853    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
2854        this = self.sql(expression, "this")
2855        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
2856        return f"{this} WITHIN GROUP ({expression_sql})"
2857
2858    def between_sql(self, expression: exp.Between) -> str:
2859        this = self.sql(expression, "this")
2860        low = self.sql(expression, "low")
2861        high = self.sql(expression, "high")
2862        return f"{this} BETWEEN {low} AND {high}"
2863
2864    def bracket_offset_expressions(
2865        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
2866    ) -> t.List[exp.Expression]:
2867        return apply_index_offset(
2868            expression.this,
2869            expression.expressions,
2870            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
2871            dialect=self.dialect,
2872        )
2873
2874    def bracket_sql(self, expression: exp.Bracket) -> str:
2875        expressions = self.bracket_offset_expressions(expression)
2876        expressions_sql = ", ".join(self.sql(e) for e in expressions)
2877        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
2878
2879    def all_sql(self, expression: exp.All) -> str:
2880        return f"ALL {self.wrap(expression)}"
2881
2882    def any_sql(self, expression: exp.Any) -> str:
2883        this = self.sql(expression, "this")
2884        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
2885            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
2886                this = self.wrap(this)
2887            return f"ANY{this}"
2888        return f"ANY {this}"
2889
2890    def exists_sql(self, expression: exp.Exists) -> str:
2891        return f"EXISTS{self.wrap(expression)}"
2892
2893    def case_sql(self, expression: exp.Case) -> str:
2894        this = self.sql(expression, "this")
2895        statements = [f"CASE {this}" if this else "CASE"]
2896
2897        for e in expression.args["ifs"]:
2898            statements.append(f"WHEN {self.sql(e, 'this')}")
2899            statements.append(f"THEN {self.sql(e, 'true')}")
2900
2901        default = self.sql(expression, "default")
2902
2903        if default:
2904            statements.append(f"ELSE {default}")
2905
2906        statements.append("END")
2907
2908        if self.pretty and self.too_wide(statements):
2909            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
2910
2911        return " ".join(statements)
2912
2913    def constraint_sql(self, expression: exp.Constraint) -> str:
2914        this = self.sql(expression, "this")
2915        expressions = self.expressions(expression, flat=True)
2916        return f"CONSTRAINT {this} {expressions}"
2917
2918    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
2919        order = expression.args.get("order")
2920        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
2921        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
2922
2923    def extract_sql(self, expression: exp.Extract) -> str:
2924        from sqlglot.dialects.dialect import map_date_part
2925
2926        this = (
2927            map_date_part(expression.this, self.dialect)
2928            if self.NORMALIZE_EXTRACT_DATE_PARTS
2929            else expression.this
2930        )
2931        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
2932        expression_sql = self.sql(expression, "expression")
2933
2934        return f"EXTRACT({this_sql} FROM {expression_sql})"
2935
2936    def trim_sql(self, expression: exp.Trim) -> str:
2937        trim_type = self.sql(expression, "position")
2938
2939        if trim_type == "LEADING":
2940            func_name = "LTRIM"
2941        elif trim_type == "TRAILING":
2942            func_name = "RTRIM"
2943        else:
2944            func_name = "TRIM"
2945
2946        return self.func(func_name, expression.this, expression.expression)
2947
2948    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
2949        args = expression.expressions
2950        if isinstance(expression, exp.ConcatWs):
2951            args = args[1:]  # Skip the delimiter
2952
2953        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
2954            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
2955
2956        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
2957            args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args]
2958
2959        return args
2960
2961    def concat_sql(self, expression: exp.Concat) -> str:
2962        expressions = self.convert_concat_args(expression)
2963
2964        # Some dialects don't allow a single-argument CONCAT call
2965        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
2966            return self.sql(expressions[0])
2967
2968        return self.func("CONCAT", *expressions)
2969
2970    def concatws_sql(self, expression: exp.ConcatWs) -> str:
2971        return self.func(
2972            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
2973        )
2974
2975    def check_sql(self, expression: exp.Check) -> str:
2976        this = self.sql(expression, key="this")
2977        return f"CHECK ({this})"
2978
2979    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
2980        expressions = self.expressions(expression, flat=True)
2981        expressions = f" ({expressions})" if expressions else ""
2982        reference = self.sql(expression, "reference")
2983        reference = f" {reference}" if reference else ""
2984        delete = self.sql(expression, "delete")
2985        delete = f" ON DELETE {delete}" if delete else ""
2986        update = self.sql(expression, "update")
2987        update = f" ON UPDATE {update}" if update else ""
2988        options = self.expressions(expression, key="options", flat=True, sep=" ")
2989        options = f" {options}" if options else ""
2990        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
2991
2992    def primarykey_sql(self, expression: exp.ForeignKey) -> str:
2993        expressions = self.expressions(expression, flat=True)
2994        options = self.expressions(expression, key="options", flat=True, sep=" ")
2995        options = f" {options}" if options else ""
2996        return f"PRIMARY KEY ({expressions}){options}"
2997
2998    def if_sql(self, expression: exp.If) -> str:
2999        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3000
3001    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3002        modifier = expression.args.get("modifier")
3003        modifier = f" {modifier}" if modifier else ""
3004        return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3005
3006    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3007        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3008
3009    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3010        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3011
3012        if expression.args.get("escape"):
3013            path = self.escape_str(path)
3014
3015        if self.QUOTE_JSON_PATH:
3016            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3017
3018        return path
3019
3020    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3021        if isinstance(expression, exp.JSONPathPart):
3022            transform = self.TRANSFORMS.get(expression.__class__)
3023            if not callable(transform):
3024                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3025                return ""
3026
3027            return transform(self, expression)
3028
3029        if isinstance(expression, int):
3030            return str(expression)
3031
3032        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3033            escaped = expression.replace("'", "\\'")
3034            escaped = f"\\'{expression}\\'"
3035        else:
3036            escaped = expression.replace('"', '\\"')
3037            escaped = f'"{escaped}"'
3038
3039        return escaped
3040
3041    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3042        return f"{self.sql(expression, 'this')} FORMAT JSON"
3043
3044    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3045        # Output the Teradata column FORMAT override.
3046        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3047        this = self.sql(expression, "this")
3048        fmt = self.sql(expression, "format")
3049        return f"{this} (FORMAT {fmt})"
3050
3051    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3052        null_handling = expression.args.get("null_handling")
3053        null_handling = f" {null_handling}" if null_handling else ""
3054
3055        unique_keys = expression.args.get("unique_keys")
3056        if unique_keys is not None:
3057            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3058        else:
3059            unique_keys = ""
3060
3061        return_type = self.sql(expression, "return_type")
3062        return_type = f" RETURNING {return_type}" if return_type else ""
3063        encoding = self.sql(expression, "encoding")
3064        encoding = f" ENCODING {encoding}" if encoding else ""
3065
3066        return self.func(
3067            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3068            *expression.expressions,
3069            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3070        )
3071
3072    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3073        return self.jsonobject_sql(expression)
3074
3075    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3076        null_handling = expression.args.get("null_handling")
3077        null_handling = f" {null_handling}" if null_handling else ""
3078        return_type = self.sql(expression, "return_type")
3079        return_type = f" RETURNING {return_type}" if return_type else ""
3080        strict = " STRICT" if expression.args.get("strict") else ""
3081        return self.func(
3082            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3083        )
3084
3085    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3086        this = self.sql(expression, "this")
3087        order = self.sql(expression, "order")
3088        null_handling = expression.args.get("null_handling")
3089        null_handling = f" {null_handling}" if null_handling else ""
3090        return_type = self.sql(expression, "return_type")
3091        return_type = f" RETURNING {return_type}" if return_type else ""
3092        strict = " STRICT" if expression.args.get("strict") else ""
3093        return self.func(
3094            "JSON_ARRAYAGG",
3095            this,
3096            suffix=f"{order}{null_handling}{return_type}{strict})",
3097        )
3098
3099    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3100        path = self.sql(expression, "path")
3101        path = f" PATH {path}" if path else ""
3102        nested_schema = self.sql(expression, "nested_schema")
3103
3104        if nested_schema:
3105            return f"NESTED{path} {nested_schema}"
3106
3107        this = self.sql(expression, "this")
3108        kind = self.sql(expression, "kind")
3109        kind = f" {kind}" if kind else ""
3110        return f"{this}{kind}{path}"
3111
3112    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3113        return self.func("COLUMNS", *expression.expressions)
3114
3115    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3116        this = self.sql(expression, "this")
3117        path = self.sql(expression, "path")
3118        path = f", {path}" if path else ""
3119        error_handling = expression.args.get("error_handling")
3120        error_handling = f" {error_handling}" if error_handling else ""
3121        empty_handling = expression.args.get("empty_handling")
3122        empty_handling = f" {empty_handling}" if empty_handling else ""
3123        schema = self.sql(expression, "schema")
3124        return self.func(
3125            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3126        )
3127
3128    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3129        this = self.sql(expression, "this")
3130        kind = self.sql(expression, "kind")
3131        path = self.sql(expression, "path")
3132        path = f" {path}" if path else ""
3133        as_json = " AS JSON" if expression.args.get("as_json") else ""
3134        return f"{this} {kind}{path}{as_json}"
3135
3136    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3137        this = self.sql(expression, "this")
3138        path = self.sql(expression, "path")
3139        path = f", {path}" if path else ""
3140        expressions = self.expressions(expression)
3141        with_ = (
3142            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3143            if expressions
3144            else ""
3145        )
3146        return f"OPENJSON({this}{path}){with_}"
3147
3148    def in_sql(self, expression: exp.In) -> str:
3149        query = expression.args.get("query")
3150        unnest = expression.args.get("unnest")
3151        field = expression.args.get("field")
3152        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3153
3154        if query:
3155            in_sql = self.sql(query)
3156        elif unnest:
3157            in_sql = self.in_unnest_op(unnest)
3158        elif field:
3159            in_sql = self.sql(field)
3160        else:
3161            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3162
3163        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3164
3165    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3166        return f"(SELECT {self.sql(unnest)})"
3167
3168    def interval_sql(self, expression: exp.Interval) -> str:
3169        unit = self.sql(expression, "unit")
3170        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3171            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3172        unit = f" {unit}" if unit else ""
3173
3174        if self.SINGLE_STRING_INTERVAL:
3175            this = expression.this.name if expression.this else ""
3176            return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}"
3177
3178        this = self.sql(expression, "this")
3179        if this:
3180            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3181            this = f" {this}" if unwrapped else f" ({this})"
3182
3183        return f"INTERVAL{this}{unit}"
3184
3185    def return_sql(self, expression: exp.Return) -> str:
3186        return f"RETURN {self.sql(expression, 'this')}"
3187
3188    def reference_sql(self, expression: exp.Reference) -> str:
3189        this = self.sql(expression, "this")
3190        expressions = self.expressions(expression, flat=True)
3191        expressions = f"({expressions})" if expressions else ""
3192        options = self.expressions(expression, key="options", flat=True, sep=" ")
3193        options = f" {options}" if options else ""
3194        return f"REFERENCES {this}{expressions}{options}"
3195
3196    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3197        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3198        parent = expression.parent
3199        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3200        return self.func(
3201            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3202        )
3203
3204    def paren_sql(self, expression: exp.Paren) -> str:
3205        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3206        return f"({sql}{self.seg(')', sep='')}"
3207
3208    def neg_sql(self, expression: exp.Neg) -> str:
3209        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3210        this_sql = self.sql(expression, "this")
3211        sep = " " if this_sql[0] == "-" else ""
3212        return f"-{sep}{this_sql}"
3213
3214    def not_sql(self, expression: exp.Not) -> str:
3215        return f"NOT {self.sql(expression, 'this')}"
3216
3217    def alias_sql(self, expression: exp.Alias) -> str:
3218        alias = self.sql(expression, "alias")
3219        alias = f" AS {alias}" if alias else ""
3220        return f"{self.sql(expression, 'this')}{alias}"
3221
3222    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3223        alias = expression.args["alias"]
3224
3225        parent = expression.parent
3226        pivot = parent and parent.parent
3227
3228        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3229            identifier_alias = isinstance(alias, exp.Identifier)
3230            literal_alias = isinstance(alias, exp.Literal)
3231
3232            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3233                alias.replace(exp.Literal.string(alias.output_name))
3234            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3235                alias.replace(exp.to_identifier(alias.output_name))
3236
3237        return self.alias_sql(expression)
3238
3239    def aliases_sql(self, expression: exp.Aliases) -> str:
3240        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3241
3242    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3243        this = self.sql(expression, "this")
3244        index = self.sql(expression, "expression")
3245        return f"{this} AT {index}"
3246
3247    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3248        this = self.sql(expression, "this")
3249        zone = self.sql(expression, "zone")
3250        return f"{this} AT TIME ZONE {zone}"
3251
3252    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3253        this = self.sql(expression, "this")
3254        zone = self.sql(expression, "zone")
3255        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3256
3257    def add_sql(self, expression: exp.Add) -> str:
3258        return self.binary(expression, "+")
3259
3260    def and_sql(
3261        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3262    ) -> str:
3263        return self.connector_sql(expression, "AND", stack)
3264
3265    def or_sql(
3266        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3267    ) -> str:
3268        return self.connector_sql(expression, "OR", stack)
3269
3270    def xor_sql(
3271        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3272    ) -> str:
3273        return self.connector_sql(expression, "XOR", stack)
3274
3275    def connector_sql(
3276        self,
3277        expression: exp.Connector,
3278        op: str,
3279        stack: t.Optional[t.List[str | exp.Expression]] = None,
3280    ) -> str:
3281        if stack is not None:
3282            if expression.expressions:
3283                stack.append(self.expressions(expression, sep=f" {op} "))
3284            else:
3285                stack.append(expression.right)
3286                if expression.comments and self.comments:
3287                    for comment in expression.comments:
3288                        if comment:
3289                            op += f" /*{self.sanitize_comment(comment)}*/"
3290                stack.extend((op, expression.left))
3291            return op
3292
3293        stack = [expression]
3294        sqls: t.List[str] = []
3295        ops = set()
3296
3297        while stack:
3298            node = stack.pop()
3299            if isinstance(node, exp.Connector):
3300                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3301            else:
3302                sql = self.sql(node)
3303                if sqls and sqls[-1] in ops:
3304                    sqls[-1] += f" {sql}"
3305                else:
3306                    sqls.append(sql)
3307
3308        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3309        return sep.join(sqls)
3310
3311    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3312        return self.binary(expression, "&")
3313
3314    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3315        return self.binary(expression, "<<")
3316
3317    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3318        return f"~{self.sql(expression, 'this')}"
3319
3320    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3321        return self.binary(expression, "|")
3322
3323    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3324        return self.binary(expression, ">>")
3325
3326    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3327        return self.binary(expression, "^")
3328
3329    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3330        format_sql = self.sql(expression, "format")
3331        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3332        to_sql = self.sql(expression, "to")
3333        to_sql = f" {to_sql}" if to_sql else ""
3334        action = self.sql(expression, "action")
3335        action = f" {action}" if action else ""
3336        default = self.sql(expression, "default")
3337        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3338        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3339
3340    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3341        zone = self.sql(expression, "this")
3342        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3343
3344    def collate_sql(self, expression: exp.Collate) -> str:
3345        if self.COLLATE_IS_FUNC:
3346            return self.function_fallback_sql(expression)
3347        return self.binary(expression, "COLLATE")
3348
3349    def command_sql(self, expression: exp.Command) -> str:
3350        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3351
3352    def comment_sql(self, expression: exp.Comment) -> str:
3353        this = self.sql(expression, "this")
3354        kind = expression.args["kind"]
3355        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3356        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3357        expression_sql = self.sql(expression, "expression")
3358        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3359
3360    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3361        this = self.sql(expression, "this")
3362        delete = " DELETE" if expression.args.get("delete") else ""
3363        recompress = self.sql(expression, "recompress")
3364        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3365        to_disk = self.sql(expression, "to_disk")
3366        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3367        to_volume = self.sql(expression, "to_volume")
3368        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3369        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3370
3371    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3372        where = self.sql(expression, "where")
3373        group = self.sql(expression, "group")
3374        aggregates = self.expressions(expression, key="aggregates")
3375        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3376
3377        if not (where or group or aggregates) and len(expression.expressions) == 1:
3378            return f"TTL {self.expressions(expression, flat=True)}"
3379
3380        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3381
3382    def transaction_sql(self, expression: exp.Transaction) -> str:
3383        return "BEGIN"
3384
3385    def commit_sql(self, expression: exp.Commit) -> str:
3386        chain = expression.args.get("chain")
3387        if chain is not None:
3388            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3389
3390        return f"COMMIT{chain or ''}"
3391
3392    def rollback_sql(self, expression: exp.Rollback) -> str:
3393        savepoint = expression.args.get("savepoint")
3394        savepoint = f" TO {savepoint}" if savepoint else ""
3395        return f"ROLLBACK{savepoint}"
3396
3397    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3398        this = self.sql(expression, "this")
3399
3400        dtype = self.sql(expression, "dtype")
3401        if dtype:
3402            collate = self.sql(expression, "collate")
3403            collate = f" COLLATE {collate}" if collate else ""
3404            using = self.sql(expression, "using")
3405            using = f" USING {using}" if using else ""
3406            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3407            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3408
3409        default = self.sql(expression, "default")
3410        if default:
3411            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3412
3413        comment = self.sql(expression, "comment")
3414        if comment:
3415            return f"ALTER COLUMN {this} COMMENT {comment}"
3416
3417        visible = expression.args.get("visible")
3418        if visible:
3419            return f"ALTER COLUMN {this} SET {visible}"
3420
3421        allow_null = expression.args.get("allow_null")
3422        drop = expression.args.get("drop")
3423
3424        if not drop and not allow_null:
3425            self.unsupported("Unsupported ALTER COLUMN syntax")
3426
3427        if allow_null is not None:
3428            keyword = "DROP" if drop else "SET"
3429            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3430
3431        return f"ALTER COLUMN {this} DROP DEFAULT"
3432
3433    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3434        this = self.sql(expression, "this")
3435
3436        visible = expression.args.get("visible")
3437        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3438
3439        return f"ALTER INDEX {this} {visible_sql}"
3440
3441    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3442        this = self.sql(expression, "this")
3443        if not isinstance(expression.this, exp.Var):
3444            this = f"KEY DISTKEY {this}"
3445        return f"ALTER DISTSTYLE {this}"
3446
3447    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3448        compound = " COMPOUND" if expression.args.get("compound") else ""
3449        this = self.sql(expression, "this")
3450        expressions = self.expressions(expression, flat=True)
3451        expressions = f"({expressions})" if expressions else ""
3452        return f"ALTER{compound} SORTKEY {this or expressions}"
3453
3454    def alterrename_sql(self, expression: exp.AlterRename) -> str:
3455        if not self.RENAME_TABLE_WITH_DB:
3456            # Remove db from tables
3457            expression = expression.transform(
3458                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3459            ).assert_is(exp.AlterRename)
3460        this = self.sql(expression, "this")
3461        return f"RENAME TO {this}"
3462
3463    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3464        exists = " IF EXISTS" if expression.args.get("exists") else ""
3465        old_column = self.sql(expression, "this")
3466        new_column = self.sql(expression, "to")
3467        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3468
3469    def alterset_sql(self, expression: exp.AlterSet) -> str:
3470        exprs = self.expressions(expression, flat=True)
3471        if self.ALTER_SET_WRAPPED:
3472            exprs = f"({exprs})"
3473
3474        return f"SET {exprs}"
3475
3476    def alter_sql(self, expression: exp.Alter) -> str:
3477        actions = expression.args["actions"]
3478
3479        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3480            actions[0], exp.ColumnDef
3481        ):
3482            actions_sql = self.expressions(expression, key="actions", flat=True)
3483            actions_sql = f"ADD {actions_sql}"
3484        else:
3485            actions_list = []
3486            for action in actions:
3487                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3488                    action_sql = self.add_column_sql(action)
3489                else:
3490                    action_sql = self.sql(action)
3491                    if isinstance(action, exp.Query):
3492                        action_sql = f"AS {action_sql}"
3493
3494                actions_list.append(action_sql)
3495
3496            actions_sql = self.format_args(*actions_list).lstrip("\n")
3497
3498        exists = " IF EXISTS" if expression.args.get("exists") else ""
3499        on_cluster = self.sql(expression, "cluster")
3500        on_cluster = f" {on_cluster}" if on_cluster else ""
3501        only = " ONLY" if expression.args.get("only") else ""
3502        options = self.expressions(expression, key="options")
3503        options = f", {options}" if options else ""
3504        kind = self.sql(expression, "kind")
3505        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3506
3507        return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
3508
3509    def add_column_sql(self, expression: exp.Expression) -> str:
3510        sql = self.sql(expression)
3511        if isinstance(expression, exp.Schema):
3512            column_text = " COLUMNS"
3513        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3514            column_text = " COLUMN"
3515        else:
3516            column_text = ""
3517
3518        return f"ADD{column_text} {sql}"
3519
3520    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3521        expressions = self.expressions(expression)
3522        exists = " IF EXISTS " if expression.args.get("exists") else " "
3523        return f"DROP{exists}{expressions}"
3524
3525    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3526        return f"ADD {self.expressions(expression, indent=False)}"
3527
3528    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3529        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3530        return f"ADD {exists}{self.sql(expression.this)}"
3531
3532    def distinct_sql(self, expression: exp.Distinct) -> str:
3533        this = self.expressions(expression, flat=True)
3534
3535        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3536            case = exp.case()
3537            for arg in expression.expressions:
3538                case = case.when(arg.is_(exp.null()), exp.null())
3539            this = self.sql(case.else_(f"({this})"))
3540
3541        this = f" {this}" if this else ""
3542
3543        on = self.sql(expression, "on")
3544        on = f" ON {on}" if on else ""
3545        return f"DISTINCT{this}{on}"
3546
3547    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3548        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
3549
3550    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3551        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
3552
3553    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3554        this_sql = self.sql(expression, "this")
3555        expression_sql = self.sql(expression, "expression")
3556        kind = "MAX" if expression.args.get("max") else "MIN"
3557        return f"{this_sql} HAVING {kind} {expression_sql}"
3558
3559    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3560        return self.sql(
3561            exp.Cast(
3562                this=exp.Div(this=expression.this, expression=expression.expression),
3563                to=exp.DataType(this=exp.DataType.Type.INT),
3564            )
3565        )
3566
3567    def dpipe_sql(self, expression: exp.DPipe) -> str:
3568        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3569            return self.func(
3570                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3571            )
3572        return self.binary(expression, "||")
3573
3574    def div_sql(self, expression: exp.Div) -> str:
3575        l, r = expression.left, expression.right
3576
3577        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3578            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3579
3580        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3581            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3582                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3583
3584        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3585            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3586                return self.sql(
3587                    exp.cast(
3588                        l / r,
3589                        to=exp.DataType.Type.BIGINT,
3590                    )
3591                )
3592
3593        return self.binary(expression, "/")
3594
3595    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3596        n = exp._wrap(expression.this, exp.Binary)
3597        d = exp._wrap(expression.expression, exp.Binary)
3598        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
3599
3600    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3601        return self.binary(expression, "OVERLAPS")
3602
3603    def distance_sql(self, expression: exp.Distance) -> str:
3604        return self.binary(expression, "<->")
3605
3606    def dot_sql(self, expression: exp.Dot) -> str:
3607        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
3608
3609    def eq_sql(self, expression: exp.EQ) -> str:
3610        return self.binary(expression, "=")
3611
3612    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3613        return self.binary(expression, ":=")
3614
3615    def escape_sql(self, expression: exp.Escape) -> str:
3616        return self.binary(expression, "ESCAPE")
3617
3618    def glob_sql(self, expression: exp.Glob) -> str:
3619        return self.binary(expression, "GLOB")
3620
3621    def gt_sql(self, expression: exp.GT) -> str:
3622        return self.binary(expression, ">")
3623
3624    def gte_sql(self, expression: exp.GTE) -> str:
3625        return self.binary(expression, ">=")
3626
3627    def ilike_sql(self, expression: exp.ILike) -> str:
3628        return self.binary(expression, "ILIKE")
3629
3630    def ilikeany_sql(self, expression: exp.ILikeAny) -> str:
3631        return self.binary(expression, "ILIKE ANY")
3632
3633    def is_sql(self, expression: exp.Is) -> str:
3634        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
3635            return self.sql(
3636                expression.this if expression.expression.this else exp.not_(expression.this)
3637            )
3638        return self.binary(expression, "IS")
3639
3640    def like_sql(self, expression: exp.Like) -> str:
3641        return self.binary(expression, "LIKE")
3642
3643    def likeany_sql(self, expression: exp.LikeAny) -> str:
3644        return self.binary(expression, "LIKE ANY")
3645
3646    def similarto_sql(self, expression: exp.SimilarTo) -> str:
3647        return self.binary(expression, "SIMILAR TO")
3648
3649    def lt_sql(self, expression: exp.LT) -> str:
3650        return self.binary(expression, "<")
3651
3652    def lte_sql(self, expression: exp.LTE) -> str:
3653        return self.binary(expression, "<=")
3654
3655    def mod_sql(self, expression: exp.Mod) -> str:
3656        return self.binary(expression, "%")
3657
3658    def mul_sql(self, expression: exp.Mul) -> str:
3659        return self.binary(expression, "*")
3660
3661    def neq_sql(self, expression: exp.NEQ) -> str:
3662        return self.binary(expression, "<>")
3663
3664    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
3665        return self.binary(expression, "IS NOT DISTINCT FROM")
3666
3667    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3668        return self.binary(expression, "IS DISTINCT FROM")
3669
3670    def slice_sql(self, expression: exp.Slice) -> str:
3671        return self.binary(expression, ":")
3672
3673    def sub_sql(self, expression: exp.Sub) -> str:
3674        return self.binary(expression, "-")
3675
3676    def trycast_sql(self, expression: exp.TryCast) -> str:
3677        return self.cast_sql(expression, safe_prefix="TRY_")
3678
3679    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
3680        return self.cast_sql(expression)
3681
3682    def try_sql(self, expression: exp.Try) -> str:
3683        if not self.TRY_SUPPORTED:
3684            self.unsupported("Unsupported TRY function")
3685            return self.sql(expression, "this")
3686
3687        return self.func("TRY", expression.this)
3688
3689    def log_sql(self, expression: exp.Log) -> str:
3690        this = expression.this
3691        expr = expression.expression
3692
3693        if self.dialect.LOG_BASE_FIRST is False:
3694            this, expr = expr, this
3695        elif self.dialect.LOG_BASE_FIRST is None and expr:
3696            if this.name in ("2", "10"):
3697                return self.func(f"LOG{this.name}", expr)
3698
3699            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
3700
3701        return self.func("LOG", this, expr)
3702
3703    def use_sql(self, expression: exp.Use) -> str:
3704        kind = self.sql(expression, "kind")
3705        kind = f" {kind}" if kind else ""
3706        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
3707        this = f" {this}" if this else ""
3708        return f"USE{kind}{this}"
3709
3710    def binary(self, expression: exp.Binary, op: str) -> str:
3711        sqls: t.List[str] = []
3712        stack: t.List[t.Union[str, exp.Expression]] = [expression]
3713        binary_type = type(expression)
3714
3715        while stack:
3716            node = stack.pop()
3717
3718            if type(node) is binary_type:
3719                op_func = node.args.get("operator")
3720                if op_func:
3721                    op = f"OPERATOR({self.sql(op_func)})"
3722
3723                stack.append(node.right)
3724                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
3725                stack.append(node.left)
3726            else:
3727                sqls.append(self.sql(node))
3728
3729        return "".join(sqls)
3730
3731    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
3732        to_clause = self.sql(expression, "to")
3733        if to_clause:
3734            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
3735
3736        return self.function_fallback_sql(expression)
3737
3738    def function_fallback_sql(self, expression: exp.Func) -> str:
3739        args = []
3740
3741        for key in expression.arg_types:
3742            arg_value = expression.args.get(key)
3743
3744            if isinstance(arg_value, list):
3745                for value in arg_value:
3746                    args.append(value)
3747            elif arg_value is not None:
3748                args.append(arg_value)
3749
3750        if self.dialect.PRESERVE_ORIGINAL_NAMES:
3751            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
3752        else:
3753            name = expression.sql_name()
3754
3755        return self.func(name, *args)
3756
3757    def func(
3758        self,
3759        name: str,
3760        *args: t.Optional[exp.Expression | str],
3761        prefix: str = "(",
3762        suffix: str = ")",
3763        normalize: bool = True,
3764    ) -> str:
3765        name = self.normalize_func(name) if normalize else name
3766        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
3767
3768    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
3769        arg_sqls = tuple(
3770            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
3771        )
3772        if self.pretty and self.too_wide(arg_sqls):
3773            return self.indent(
3774                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
3775            )
3776        return sep.join(arg_sqls)
3777
3778    def too_wide(self, args: t.Iterable) -> bool:
3779        return sum(len(arg) for arg in args) > self.max_text_width
3780
3781    def format_time(
3782        self,
3783        expression: exp.Expression,
3784        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
3785        inverse_time_trie: t.Optional[t.Dict] = None,
3786    ) -> t.Optional[str]:
3787        return format_time(
3788            self.sql(expression, "format"),
3789            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
3790            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
3791        )
3792
3793    def expressions(
3794        self,
3795        expression: t.Optional[exp.Expression] = None,
3796        key: t.Optional[str] = None,
3797        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
3798        flat: bool = False,
3799        indent: bool = True,
3800        skip_first: bool = False,
3801        skip_last: bool = False,
3802        sep: str = ", ",
3803        prefix: str = "",
3804        dynamic: bool = False,
3805        new_line: bool = False,
3806    ) -> str:
3807        expressions = expression.args.get(key or "expressions") if expression else sqls
3808
3809        if not expressions:
3810            return ""
3811
3812        if flat:
3813            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
3814
3815        num_sqls = len(expressions)
3816        result_sqls = []
3817
3818        for i, e in enumerate(expressions):
3819            sql = self.sql(e, comment=False)
3820            if not sql:
3821                continue
3822
3823            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
3824
3825            if self.pretty:
3826                if self.leading_comma:
3827                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
3828                else:
3829                    result_sqls.append(
3830                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
3831                    )
3832            else:
3833                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
3834
3835        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
3836            if new_line:
3837                result_sqls.insert(0, "")
3838                result_sqls.append("")
3839            result_sql = "\n".join(s.rstrip() for s in result_sqls)
3840        else:
3841            result_sql = "".join(result_sqls)
3842
3843        return (
3844            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
3845            if indent
3846            else result_sql
3847        )
3848
3849    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
3850        flat = flat or isinstance(expression.parent, exp.Properties)
3851        expressions_sql = self.expressions(expression, flat=flat)
3852        if flat:
3853            return f"{op} {expressions_sql}"
3854        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3855
3856    def naked_property(self, expression: exp.Property) -> str:
3857        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
3858        if not property_name:
3859            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
3860        return f"{property_name} {self.sql(expression, 'this')}"
3861
3862    def tag_sql(self, expression: exp.Tag) -> str:
3863        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
3864
3865    def token_sql(self, token_type: TokenType) -> str:
3866        return self.TOKEN_MAPPING.get(token_type, token_type.name)
3867
3868    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
3869        this = self.sql(expression, "this")
3870        expressions = self.no_identify(self.expressions, expression)
3871        expressions = (
3872            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
3873        )
3874        return f"{this}{expressions}" if expressions.strip() != "" else this
3875
3876    def joinhint_sql(self, expression: exp.JoinHint) -> str:
3877        this = self.sql(expression, "this")
3878        expressions = self.expressions(expression, flat=True)
3879        return f"{this}({expressions})"
3880
3881    def kwarg_sql(self, expression: exp.Kwarg) -> str:
3882        return self.binary(expression, "=>")
3883
3884    def when_sql(self, expression: exp.When) -> str:
3885        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
3886        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
3887        condition = self.sql(expression, "condition")
3888        condition = f" AND {condition}" if condition else ""
3889
3890        then_expression = expression.args.get("then")
3891        if isinstance(then_expression, exp.Insert):
3892            this = self.sql(then_expression, "this")
3893            this = f"INSERT {this}" if this else "INSERT"
3894            then = self.sql(then_expression, "expression")
3895            then = f"{this} VALUES {then}" if then else this
3896        elif isinstance(then_expression, exp.Update):
3897            if isinstance(then_expression.args.get("expressions"), exp.Star):
3898                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
3899            else:
3900                then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}"
3901        else:
3902            then = self.sql(then_expression)
3903        return f"WHEN {matched}{source}{condition} THEN {then}"
3904
3905    def whens_sql(self, expression: exp.Whens) -> str:
3906        return self.expressions(expression, sep=" ", indent=False)
3907
3908    def merge_sql(self, expression: exp.Merge) -> str:
3909        table = expression.this
3910        table_alias = ""
3911
3912        hints = table.args.get("hints")
3913        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
3914            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
3915            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
3916
3917        this = self.sql(table)
3918        using = f"USING {self.sql(expression, 'using')}"
3919        on = f"ON {self.sql(expression, 'on')}"
3920        whens = self.sql(expression, "whens")
3921
3922        returning = self.sql(expression, "returning")
3923        if returning:
3924            whens = f"{whens}{returning}"
3925
3926        sep = self.sep()
3927
3928        return self.prepend_ctes(
3929            expression,
3930            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
3931        )
3932
3933    @unsupported_args("format")
3934    def tochar_sql(self, expression: exp.ToChar) -> str:
3935        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
3936
3937    def tonumber_sql(self, expression: exp.ToNumber) -> str:
3938        if not self.SUPPORTS_TO_NUMBER:
3939            self.unsupported("Unsupported TO_NUMBER function")
3940            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3941
3942        fmt = expression.args.get("format")
3943        if not fmt:
3944            self.unsupported("Conversion format is required for TO_NUMBER")
3945            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3946
3947        return self.func("TO_NUMBER", expression.this, fmt)
3948
3949    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
3950        this = self.sql(expression, "this")
3951        kind = self.sql(expression, "kind")
3952        settings_sql = self.expressions(expression, key="settings", sep=" ")
3953        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
3954        return f"{this}({kind}{args})"
3955
3956    def dictrange_sql(self, expression: exp.DictRange) -> str:
3957        this = self.sql(expression, "this")
3958        max = self.sql(expression, "max")
3959        min = self.sql(expression, "min")
3960        return f"{this}(MIN {min} MAX {max})"
3961
3962    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
3963        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
3964
3965    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
3966        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
3967
3968    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
3969    def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str:
3970        return f"UNIQUE KEY ({self.expressions(expression, flat=True)})"
3971
3972    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
3973    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
3974        expressions = self.expressions(expression, flat=True)
3975        expressions = f" {self.wrap(expressions)}" if expressions else ""
3976        buckets = self.sql(expression, "buckets")
3977        kind = self.sql(expression, "kind")
3978        buckets = f" BUCKETS {buckets}" if buckets else ""
3979        order = self.sql(expression, "order")
3980        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
3981
3982    def oncluster_sql(self, expression: exp.OnCluster) -> str:
3983        return ""
3984
3985    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
3986        expressions = self.expressions(expression, key="expressions", flat=True)
3987        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
3988        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
3989        buckets = self.sql(expression, "buckets")
3990        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
3991
3992    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
3993        this = self.sql(expression, "this")
3994        having = self.sql(expression, "having")
3995
3996        if having:
3997            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
3998
3999        return self.func("ANY_VALUE", this)
4000
4001    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4002        transform = self.func("TRANSFORM", *expression.expressions)
4003        row_format_before = self.sql(expression, "row_format_before")
4004        row_format_before = f" {row_format_before}" if row_format_before else ""
4005        record_writer = self.sql(expression, "record_writer")
4006        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4007        using = f" USING {self.sql(expression, 'command_script')}"
4008        schema = self.sql(expression, "schema")
4009        schema = f" AS {schema}" if schema else ""
4010        row_format_after = self.sql(expression, "row_format_after")
4011        row_format_after = f" {row_format_after}" if row_format_after else ""
4012        record_reader = self.sql(expression, "record_reader")
4013        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4014        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4015
4016    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4017        key_block_size = self.sql(expression, "key_block_size")
4018        if key_block_size:
4019            return f"KEY_BLOCK_SIZE = {key_block_size}"
4020
4021        using = self.sql(expression, "using")
4022        if using:
4023            return f"USING {using}"
4024
4025        parser = self.sql(expression, "parser")
4026        if parser:
4027            return f"WITH PARSER {parser}"
4028
4029        comment = self.sql(expression, "comment")
4030        if comment:
4031            return f"COMMENT {comment}"
4032
4033        visible = expression.args.get("visible")
4034        if visible is not None:
4035            return "VISIBLE" if visible else "INVISIBLE"
4036
4037        engine_attr = self.sql(expression, "engine_attr")
4038        if engine_attr:
4039            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4040
4041        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4042        if secondary_engine_attr:
4043            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4044
4045        self.unsupported("Unsupported index constraint option.")
4046        return ""
4047
4048    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4049        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4050        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4051
4052    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4053        kind = self.sql(expression, "kind")
4054        kind = f"{kind} INDEX" if kind else "INDEX"
4055        this = self.sql(expression, "this")
4056        this = f" {this}" if this else ""
4057        index_type = self.sql(expression, "index_type")
4058        index_type = f" USING {index_type}" if index_type else ""
4059        expressions = self.expressions(expression, flat=True)
4060        expressions = f" ({expressions})" if expressions else ""
4061        options = self.expressions(expression, key="options", sep=" ")
4062        options = f" {options}" if options else ""
4063        return f"{kind}{this}{index_type}{expressions}{options}"
4064
4065    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4066        if self.NVL2_SUPPORTED:
4067            return self.function_fallback_sql(expression)
4068
4069        case = exp.Case().when(
4070            expression.this.is_(exp.null()).not_(copy=False),
4071            expression.args["true"],
4072            copy=False,
4073        )
4074        else_cond = expression.args.get("false")
4075        if else_cond:
4076            case.else_(else_cond, copy=False)
4077
4078        return self.sql(case)
4079
4080    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4081        this = self.sql(expression, "this")
4082        expr = self.sql(expression, "expression")
4083        iterator = self.sql(expression, "iterator")
4084        condition = self.sql(expression, "condition")
4085        condition = f" IF {condition}" if condition else ""
4086        return f"{this} FOR {expr} IN {iterator}{condition}"
4087
4088    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4089        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4090
4091    def opclass_sql(self, expression: exp.Opclass) -> str:
4092        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4093
4094    def predict_sql(self, expression: exp.Predict) -> str:
4095        model = self.sql(expression, "this")
4096        model = f"MODEL {model}"
4097        table = self.sql(expression, "expression")
4098        table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table
4099        parameters = self.sql(expression, "params_struct")
4100        return self.func("PREDICT", model, table, parameters or None)
4101
4102    def forin_sql(self, expression: exp.ForIn) -> str:
4103        this = self.sql(expression, "this")
4104        expression_sql = self.sql(expression, "expression")
4105        return f"FOR {this} DO {expression_sql}"
4106
4107    def refresh_sql(self, expression: exp.Refresh) -> str:
4108        this = self.sql(expression, "this")
4109        table = "" if isinstance(expression.this, exp.Literal) else "TABLE "
4110        return f"REFRESH {table}{this}"
4111
4112    def toarray_sql(self, expression: exp.ToArray) -> str:
4113        arg = expression.this
4114        if not arg.type:
4115            from sqlglot.optimizer.annotate_types import annotate_types
4116
4117            arg = annotate_types(arg, dialect=self.dialect)
4118
4119        if arg.is_type(exp.DataType.Type.ARRAY):
4120            return self.sql(arg)
4121
4122        cond_for_null = arg.is_(exp.null())
4123        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4124
4125    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4126        this = expression.this
4127        time_format = self.format_time(expression)
4128
4129        if time_format:
4130            return self.sql(
4131                exp.cast(
4132                    exp.StrToTime(this=this, format=expression.args["format"]),
4133                    exp.DataType.Type.TIME,
4134                )
4135            )
4136
4137        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4138            return self.sql(this)
4139
4140        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4141
4142    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4143        this = expression.this
4144        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4145            return self.sql(this)
4146
4147        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4148
4149    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4150        this = expression.this
4151        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4152            return self.sql(this)
4153
4154        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4155
4156    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4157        this = expression.this
4158        time_format = self.format_time(expression)
4159
4160        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4161            return self.sql(
4162                exp.cast(
4163                    exp.StrToTime(this=this, format=expression.args["format"]),
4164                    exp.DataType.Type.DATE,
4165                )
4166            )
4167
4168        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4169            return self.sql(this)
4170
4171        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4172
4173    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4174        return self.sql(
4175            exp.func(
4176                "DATEDIFF",
4177                expression.this,
4178                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4179                "day",
4180            )
4181        )
4182
4183    def lastday_sql(self, expression: exp.LastDay) -> str:
4184        if self.LAST_DAY_SUPPORTS_DATE_PART:
4185            return self.function_fallback_sql(expression)
4186
4187        unit = expression.text("unit")
4188        if unit and unit != "MONTH":
4189            self.unsupported("Date parts are not supported in LAST_DAY.")
4190
4191        return self.func("LAST_DAY", expression.this)
4192
4193    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4194        from sqlglot.dialects.dialect import unit_to_str
4195
4196        return self.func(
4197            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4198        )
4199
4200    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4201        if self.CAN_IMPLEMENT_ARRAY_ANY:
4202            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4203            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4204            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4205            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4206
4207        from sqlglot.dialects import Dialect
4208
4209        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4210        if self.dialect.__class__ != Dialect:
4211            self.unsupported("ARRAY_ANY is unsupported")
4212
4213        return self.function_fallback_sql(expression)
4214
4215    def struct_sql(self, expression: exp.Struct) -> str:
4216        expression.set(
4217            "expressions",
4218            [
4219                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4220                if isinstance(e, exp.PropertyEQ)
4221                else e
4222                for e in expression.expressions
4223            ],
4224        )
4225
4226        return self.function_fallback_sql(expression)
4227
4228    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4229        low = self.sql(expression, "this")
4230        high = self.sql(expression, "expression")
4231
4232        return f"{low} TO {high}"
4233
4234    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4235        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4236        tables = f" {self.expressions(expression)}"
4237
4238        exists = " IF EXISTS" if expression.args.get("exists") else ""
4239
4240        on_cluster = self.sql(expression, "cluster")
4241        on_cluster = f" {on_cluster}" if on_cluster else ""
4242
4243        identity = self.sql(expression, "identity")
4244        identity = f" {identity} IDENTITY" if identity else ""
4245
4246        option = self.sql(expression, "option")
4247        option = f" {option}" if option else ""
4248
4249        partition = self.sql(expression, "partition")
4250        partition = f" {partition}" if partition else ""
4251
4252        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4253
4254    # This transpiles T-SQL's CONVERT function
4255    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4256    def convert_sql(self, expression: exp.Convert) -> str:
4257        to = expression.this
4258        value = expression.expression
4259        style = expression.args.get("style")
4260        safe = expression.args.get("safe")
4261        strict = expression.args.get("strict")
4262
4263        if not to or not value:
4264            return ""
4265
4266        # Retrieve length of datatype and override to default if not specified
4267        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4268            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4269
4270        transformed: t.Optional[exp.Expression] = None
4271        cast = exp.Cast if strict else exp.TryCast
4272
4273        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4274        if isinstance(style, exp.Literal) and style.is_int:
4275            from sqlglot.dialects.tsql import TSQL
4276
4277            style_value = style.name
4278            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4279            if not converted_style:
4280                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4281
4282            fmt = exp.Literal.string(converted_style)
4283
4284            if to.this == exp.DataType.Type.DATE:
4285                transformed = exp.StrToDate(this=value, format=fmt)
4286            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4287                transformed = exp.StrToTime(this=value, format=fmt)
4288            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4289                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4290            elif to.this == exp.DataType.Type.TEXT:
4291                transformed = exp.TimeToStr(this=value, format=fmt)
4292
4293        if not transformed:
4294            transformed = cast(this=value, to=to, safe=safe)
4295
4296        return self.sql(transformed)
4297
4298    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4299        this = expression.this
4300        if isinstance(this, exp.JSONPathWildcard):
4301            this = self.json_path_part(this)
4302            return f".{this}" if this else ""
4303
4304        if exp.SAFE_IDENTIFIER_RE.match(this):
4305            return f".{this}"
4306
4307        this = self.json_path_part(this)
4308        return (
4309            f"[{this}]"
4310            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
4311            else f".{this}"
4312        )
4313
4314    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
4315        this = self.json_path_part(expression.this)
4316        return f"[{this}]" if this else ""
4317
4318    def _simplify_unless_literal(self, expression: E) -> E:
4319        if not isinstance(expression, exp.Literal):
4320            from sqlglot.optimizer.simplify import simplify
4321
4322            expression = simplify(expression, dialect=self.dialect)
4323
4324        return expression
4325
4326    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
4327        this = expression.this
4328        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
4329            self.unsupported(
4330                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
4331            )
4332            return self.sql(this)
4333
4334        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
4335            # The first modifier here will be the one closest to the AggFunc's arg
4336            mods = sorted(
4337                expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
4338                key=lambda x: 0
4339                if isinstance(x, exp.HavingMax)
4340                else (1 if isinstance(x, exp.Order) else 2),
4341            )
4342
4343            if mods:
4344                mod = mods[0]
4345                this = expression.__class__(this=mod.this.copy())
4346                this.meta["inline"] = True
4347                mod.this.replace(this)
4348                return self.sql(expression.this)
4349
4350            agg_func = expression.find(exp.AggFunc)
4351
4352            if agg_func:
4353                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
4354                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
4355
4356        return f"{self.sql(expression, 'this')} {text}"
4357
4358    def _replace_line_breaks(self, string: str) -> str:
4359        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
4360        if self.pretty:
4361            return string.replace("\n", self.SENTINEL_LINE_BREAK)
4362        return string
4363
4364    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4365        option = self.sql(expression, "this")
4366
4367        if expression.expressions:
4368            upper = option.upper()
4369
4370            # Snowflake FILE_FORMAT options are separated by whitespace
4371            sep = " " if upper == "FILE_FORMAT" else ", "
4372
4373            # Databricks copy/format options do not set their list of values with EQ
4374            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4375            values = self.expressions(expression, flat=True, sep=sep)
4376            return f"{option}{op}({values})"
4377
4378        value = self.sql(expression, "expression")
4379
4380        if not value:
4381            return option
4382
4383        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4384
4385        return f"{option}{op}{value}"
4386
4387    def credentials_sql(self, expression: exp.Credentials) -> str:
4388        cred_expr = expression.args.get("credentials")
4389        if isinstance(cred_expr, exp.Literal):
4390            # Redshift case: CREDENTIALS <string>
4391            credentials = self.sql(expression, "credentials")
4392            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4393        else:
4394            # Snowflake case: CREDENTIALS = (...)
4395            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4396            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4397
4398        storage = self.sql(expression, "storage")
4399        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4400
4401        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4402        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4403
4404        iam_role = self.sql(expression, "iam_role")
4405        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4406
4407        region = self.sql(expression, "region")
4408        region = f" REGION {region}" if region else ""
4409
4410        return f"{credentials}{storage}{encryption}{iam_role}{region}"
4411
4412    def copy_sql(self, expression: exp.Copy) -> str:
4413        this = self.sql(expression, "this")
4414        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4415
4416        credentials = self.sql(expression, "credentials")
4417        credentials = self.seg(credentials) if credentials else ""
4418        kind = self.seg("FROM" if expression.args.get("kind") else "TO")
4419        files = self.expressions(expression, key="files", flat=True)
4420
4421        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4422        params = self.expressions(
4423            expression,
4424            key="params",
4425            sep=sep,
4426            new_line=True,
4427            skip_last=True,
4428            skip_first=True,
4429            indent=self.COPY_PARAMS_ARE_WRAPPED,
4430        )
4431
4432        if params:
4433            if self.COPY_PARAMS_ARE_WRAPPED:
4434                params = f" WITH ({params})"
4435            elif not self.pretty:
4436                params = f" {params}"
4437
4438        return f"COPY{this}{kind} {files}{credentials}{params}"
4439
4440    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4441        return ""
4442
4443    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4444        on_sql = "ON" if expression.args.get("on") else "OFF"
4445        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4446        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4447        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4448        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4449
4450        if filter_col or retention_period:
4451            on_sql = self.func("ON", filter_col, retention_period)
4452
4453        return f"DATA_DELETION={on_sql}"
4454
4455    def maskingpolicycolumnconstraint_sql(
4456        self, expression: exp.MaskingPolicyColumnConstraint
4457    ) -> str:
4458        this = self.sql(expression, "this")
4459        expressions = self.expressions(expression, flat=True)
4460        expressions = f" USING ({expressions})" if expressions else ""
4461        return f"MASKING POLICY {this}{expressions}"
4462
4463    def gapfill_sql(self, expression: exp.GapFill) -> str:
4464        this = self.sql(expression, "this")
4465        this = f"TABLE {this}"
4466        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
4467
4468    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4469        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
4470
4471    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4472        this = self.sql(expression, "this")
4473        expr = expression.expression
4474
4475        if isinstance(expr, exp.Func):
4476            # T-SQL's CLR functions are case sensitive
4477            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4478        else:
4479            expr = self.sql(expression, "expression")
4480
4481        return self.scope_resolution(expr, this)
4482
4483    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4484        if self.PARSE_JSON_NAME is None:
4485            return self.sql(expression.this)
4486
4487        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
4488
4489    def rand_sql(self, expression: exp.Rand) -> str:
4490        lower = self.sql(expression, "lower")
4491        upper = self.sql(expression, "upper")
4492
4493        if lower and upper:
4494            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4495        return self.func("RAND", expression.this)
4496
4497    def changes_sql(self, expression: exp.Changes) -> str:
4498        information = self.sql(expression, "information")
4499        information = f"INFORMATION => {information}"
4500        at_before = self.sql(expression, "at_before")
4501        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4502        end = self.sql(expression, "end")
4503        end = f"{self.seg('')}{end}" if end else ""
4504
4505        return f"CHANGES ({information}){at_before}{end}"
4506
4507    def pad_sql(self, expression: exp.Pad) -> str:
4508        prefix = "L" if expression.args.get("is_left") else "R"
4509
4510        fill_pattern = self.sql(expression, "fill_pattern") or None
4511        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4512            fill_pattern = "' '"
4513
4514        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
4515
4516    def summarize_sql(self, expression: exp.Summarize) -> str:
4517        table = " TABLE" if expression.args.get("table") else ""
4518        return f"SUMMARIZE{table} {self.sql(expression.this)}"
4519
4520    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4521        generate_series = exp.GenerateSeries(**expression.args)
4522
4523        parent = expression.parent
4524        if isinstance(parent, (exp.Alias, exp.TableAlias)):
4525            parent = parent.parent
4526
4527        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
4528            return self.sql(exp.Unnest(expressions=[generate_series]))
4529
4530        if isinstance(parent, exp.Select):
4531            self.unsupported("GenerateSeries projection unnesting is not supported.")
4532
4533        return self.sql(generate_series)
4534
4535    def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str:
4536        exprs = expression.expressions
4537        if not self.ARRAY_CONCAT_IS_VAR_LEN:
4538            rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs)
4539        else:
4540            rhs = self.expressions(expression)
4541
4542        return self.func(name, expression.this, rhs or None)
4543
4544    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
4545        if self.SUPPORTS_CONVERT_TIMEZONE:
4546            return self.function_fallback_sql(expression)
4547
4548        source_tz = expression.args.get("source_tz")
4549        target_tz = expression.args.get("target_tz")
4550        timestamp = expression.args.get("timestamp")
4551
4552        if source_tz and timestamp:
4553            timestamp = exp.AtTimeZone(
4554                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
4555            )
4556
4557        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
4558
4559        return self.sql(expr)
4560
4561    def json_sql(self, expression: exp.JSON) -> str:
4562        this = self.sql(expression, "this")
4563        this = f" {this}" if this else ""
4564
4565        _with = expression.args.get("with")
4566
4567        if _with is None:
4568            with_sql = ""
4569        elif not _with:
4570            with_sql = " WITHOUT"
4571        else:
4572            with_sql = " WITH"
4573
4574        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
4575
4576        return f"JSON{this}{with_sql}{unique_sql}"
4577
4578    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
4579        def _generate_on_options(arg: t.Any) -> str:
4580            return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}"
4581
4582        path = self.sql(expression, "path")
4583        returning = self.sql(expression, "returning")
4584        returning = f" RETURNING {returning}" if returning else ""
4585
4586        on_condition = self.sql(expression, "on_condition")
4587        on_condition = f" {on_condition}" if on_condition else ""
4588
4589        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4590
4591    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
4592        else_ = "ELSE " if expression.args.get("else_") else ""
4593        condition = self.sql(expression, "expression")
4594        condition = f"WHEN {condition} THEN " if condition else else_
4595        insert = self.sql(expression, "this")[len("INSERT") :].strip()
4596        return f"{condition}{insert}"
4597
4598    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
4599        kind = self.sql(expression, "kind")
4600        expressions = self.seg(self.expressions(expression, sep=" "))
4601        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
4602        return res
4603
4604    def oncondition_sql(self, expression: exp.OnCondition) -> str:
4605        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
4606        empty = expression.args.get("empty")
4607        empty = (
4608            f"DEFAULT {empty} ON EMPTY"
4609            if isinstance(empty, exp.Expression)
4610            else self.sql(expression, "empty")
4611        )
4612
4613        error = expression.args.get("error")
4614        error = (
4615            f"DEFAULT {error} ON ERROR"
4616            if isinstance(error, exp.Expression)
4617            else self.sql(expression, "error")
4618        )
4619
4620        if error and empty:
4621            error = (
4622                f"{empty} {error}"
4623                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
4624                else f"{error} {empty}"
4625            )
4626            empty = ""
4627
4628        null = self.sql(expression, "null")
4629
4630        return f"{empty}{error}{null}"
4631
4632    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
4633        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
4634        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
4635
4636    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
4637        this = self.sql(expression, "this")
4638        path = self.sql(expression, "path")
4639
4640        passing = self.expressions(expression, "passing")
4641        passing = f" PASSING {passing}" if passing else ""
4642
4643        on_condition = self.sql(expression, "on_condition")
4644        on_condition = f" {on_condition}" if on_condition else ""
4645
4646        path = f"{path}{passing}{on_condition}"
4647
4648        return self.func("JSON_EXISTS", this, path)
4649
4650    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
4651        array_agg = self.function_fallback_sql(expression)
4652
4653        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
4654        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
4655        if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"):
4656            parent = expression.parent
4657            if isinstance(parent, exp.Filter):
4658                parent_cond = parent.expression.this
4659                parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_()))
4660            else:
4661                this = expression.this
4662                # Do not add the filter if the input is not a column (e.g. literal, struct etc)
4663                if this.find(exp.Column):
4664                    # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
4665                    this_sql = (
4666                        self.expressions(this)
4667                        if isinstance(this, exp.Distinct)
4668                        else self.sql(expression, "this")
4669                    )
4670
4671                    array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)"
4672
4673        return array_agg
4674
4675    def apply_sql(self, expression: exp.Apply) -> str:
4676        this = self.sql(expression, "this")
4677        expr = self.sql(expression, "expression")
4678
4679        return f"{this} APPLY({expr})"
4680
4681    def grant_sql(self, expression: exp.Grant) -> str:
4682        privileges_sql = self.expressions(expression, key="privileges", flat=True)
4683
4684        kind = self.sql(expression, "kind")
4685        kind = f" {kind}" if kind else ""
4686
4687        securable = self.sql(expression, "securable")
4688        securable = f" {securable}" if securable else ""
4689
4690        principals = self.expressions(expression, key="principals", flat=True)
4691
4692        grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else ""
4693
4694        return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4695
4696    def grantprivilege_sql(self, expression: exp.GrantPrivilege):
4697        this = self.sql(expression, "this")
4698        columns = self.expressions(expression, flat=True)
4699        columns = f"({columns})" if columns else ""
4700
4701        return f"{this}{columns}"
4702
4703    def grantprincipal_sql(self, expression: exp.GrantPrincipal):
4704        this = self.sql(expression, "this")
4705
4706        kind = self.sql(expression, "kind")
4707        kind = f"{kind} " if kind else ""
4708
4709        return f"{kind}{this}"
4710
4711    def columns_sql(self, expression: exp.Columns):
4712        func = self.function_fallback_sql(expression)
4713        if expression.args.get("unpack"):
4714            func = f"*{func}"
4715
4716        return func
4717
4718    def overlay_sql(self, expression: exp.Overlay):
4719        this = self.sql(expression, "this")
4720        expr = self.sql(expression, "expression")
4721        from_sql = self.sql(expression, "from")
4722        for_sql = self.sql(expression, "for")
4723        for_sql = f" FOR {for_sql}" if for_sql else ""
4724
4725        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
4726
4727    @unsupported_args("format")
4728    def todouble_sql(self, expression: exp.ToDouble) -> str:
4729        return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4730
4731    def string_sql(self, expression: exp.String) -> str:
4732        this = expression.this
4733        zone = expression.args.get("zone")
4734
4735        if zone:
4736            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
4737            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
4738            # set for source_tz to transpile the time conversion before the STRING cast
4739            this = exp.ConvertTimezone(
4740                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
4741            )
4742
4743        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
4744
4745    def median_sql(self, expression: exp.Median):
4746        if not self.SUPPORTS_MEDIAN:
4747            return self.sql(
4748                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
4749            )
4750
4751        return self.function_fallback_sql(expression)
4752
4753    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
4754        filler = self.sql(expression, "this")
4755        filler = f" {filler}" if filler else ""
4756        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
4757        return f"TRUNCATE{filler} {with_count}"
4758
4759    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
4760        if self.SUPPORTS_UNIX_SECONDS:
4761            return self.function_fallback_sql(expression)
4762
4763        start_ts = exp.cast(
4764            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
4765        )
4766
4767        return self.sql(
4768            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
4769        )
4770
4771    def arraysize_sql(self, expression: exp.ArraySize) -> str:
4772        dim = expression.expression
4773
4774        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
4775        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
4776            if not (dim.is_int and dim.name == "1"):
4777                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
4778            dim = None
4779
4780        # If dimension is required but not specified, default initialize it
4781        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
4782            dim = exp.Literal.number(1)
4783
4784        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4785
4786    def attach_sql(self, expression: exp.Attach) -> str:
4787        this = self.sql(expression, "this")
4788        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
4789        expressions = self.expressions(expression)
4790        expressions = f" ({expressions})" if expressions else ""
4791
4792        return f"ATTACH{exists_sql} {this}{expressions}"
4793
4794    def detach_sql(self, expression: exp.Detach) -> str:
4795        this = self.sql(expression, "this")
4796        # the DATABASE keyword is required if IF EXISTS is set
4797        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
4798        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
4799        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
4800
4801        return f"DETACH{exists_sql} {this}"
4802
4803    def attachoption_sql(self, expression: exp.AttachOption) -> str:
4804        this = self.sql(expression, "this")
4805        value = self.sql(expression, "expression")
4806        value = f" {value}" if value else ""
4807        return f"{this}{value}"
4808
4809    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4810        this_sql = self.sql(expression, "this")
4811        if isinstance(expression.this, exp.Table):
4812            this_sql = f"TABLE {this_sql}"
4813
4814        return self.func(
4815            "FEATURES_AT_TIME",
4816            this_sql,
4817            expression.args.get("time"),
4818            expression.args.get("num_rows"),
4819            expression.args.get("ignore_feature_nulls"),
4820        )
4821
4822    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
4823        return (
4824            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
4825        )
4826
4827    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
4828        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
4829        encode = f"{encode} {self.sql(expression, 'this')}"
4830
4831        properties = expression.args.get("properties")
4832        if properties:
4833            encode = f"{encode} {self.properties(properties)}"
4834
4835        return encode
4836
4837    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
4838        this = self.sql(expression, "this")
4839        include = f"INCLUDE {this}"
4840
4841        column_def = self.sql(expression, "column_def")
4842        if column_def:
4843            include = f"{include} {column_def}"
4844
4845        alias = self.sql(expression, "alias")
4846        if alias:
4847            include = f"{include} AS {alias}"
4848
4849        return include
4850
4851    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
4852        name = f"NAME {self.sql(expression, 'this')}"
4853        return self.func("XMLELEMENT", name, *expression.expressions)
4854
4855    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
4856        this = self.sql(expression, "this")
4857        expr = self.sql(expression, "expression")
4858        expr = f"({expr})" if expr else ""
4859        return f"{this}{expr}"
4860
4861    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
4862        partitions = self.expressions(expression, "partition_expressions")
4863        create = self.expressions(expression, "create_expressions")
4864        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
4865
4866    def partitionbyrangepropertydynamic_sql(
4867        self, expression: exp.PartitionByRangePropertyDynamic
4868    ) -> str:
4869        start = self.sql(expression, "start")
4870        end = self.sql(expression, "end")
4871
4872        every = expression.args["every"]
4873        if isinstance(every, exp.Interval) and every.this.is_string:
4874            every.this.replace(exp.Literal.number(every.name))
4875
4876        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
4877
4878    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
4879        name = self.sql(expression, "this")
4880        values = self.expressions(expression, flat=True)
4881
4882        return f"NAME {name} VALUE {values}"
4883
4884    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
4885        kind = self.sql(expression, "kind")
4886        sample = self.sql(expression, "sample")
4887        return f"SAMPLE {sample} {kind}"
4888
4889    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
4890        kind = self.sql(expression, "kind")
4891        option = self.sql(expression, "option")
4892        option = f" {option}" if option else ""
4893        this = self.sql(expression, "this")
4894        this = f" {this}" if this else ""
4895        columns = self.expressions(expression)
4896        columns = f" {columns}" if columns else ""
4897        return f"{kind}{option} STATISTICS{this}{columns}"
4898
4899    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
4900        this = self.sql(expression, "this")
4901        columns = self.expressions(expression)
4902        inner_expression = self.sql(expression, "expression")
4903        inner_expression = f" {inner_expression}" if inner_expression else ""
4904        update_options = self.sql(expression, "update_options")
4905        update_options = f" {update_options} UPDATE" if update_options else ""
4906        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
4907
4908    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
4909        kind = self.sql(expression, "kind")
4910        kind = f" {kind}" if kind else ""
4911        return f"DELETE{kind} STATISTICS"
4912
4913    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
4914        inner_expression = self.sql(expression, "expression")
4915        return f"LIST CHAINED ROWS{inner_expression}"
4916
4917    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
4918        kind = self.sql(expression, "kind")
4919        this = self.sql(expression, "this")
4920        this = f" {this}" if this else ""
4921        inner_expression = self.sql(expression, "expression")
4922        return f"VALIDATE {kind}{this}{inner_expression}"
4923
4924    def analyze_sql(self, expression: exp.Analyze) -> str:
4925        options = self.expressions(expression, key="options", sep=" ")
4926        options = f" {options}" if options else ""
4927        kind = self.sql(expression, "kind")
4928        kind = f" {kind}" if kind else ""
4929        this = self.sql(expression, "this")
4930        this = f" {this}" if this else ""
4931        mode = self.sql(expression, "mode")
4932        mode = f" {mode}" if mode else ""
4933        properties = self.sql(expression, "properties")
4934        properties = f" {properties}" if properties else ""
4935        partition = self.sql(expression, "partition")
4936        partition = f" {partition}" if partition else ""
4937        inner_expression = self.sql(expression, "expression")
4938        inner_expression = f" {inner_expression}" if inner_expression else ""
4939        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
4940
4941    def xmltable_sql(self, expression: exp.XMLTable) -> str:
4942        this = self.sql(expression, "this")
4943        namespaces = self.expressions(expression, key="namespaces")
4944        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
4945        passing = self.expressions(expression, key="passing")
4946        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
4947        columns = self.expressions(expression, key="columns")
4948        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
4949        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
4950        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
4951
4952    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
4953        this = self.sql(expression, "this")
4954        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
4955
4956    def export_sql(self, expression: exp.Export) -> str:
4957        this = self.sql(expression, "this")
4958        connection = self.sql(expression, "connection")
4959        connection = f"WITH CONNECTION {connection} " if connection else ""
4960        options = self.sql(expression, "options")
4961        return f"EXPORT DATA {connection}{options} AS {this}"
4962
4963    def declare_sql(self, expression: exp.Declare) -> str:
4964        return f"DECLARE {self.expressions(expression, flat=True)}"
4965
4966    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
4967        variable = self.sql(expression, "this")
4968        default = self.sql(expression, "default")
4969        default = f" = {default}" if default else ""
4970
4971        kind = self.sql(expression, "kind")
4972        if isinstance(expression.args.get("kind"), exp.Schema):
4973            kind = f"TABLE {kind}"
4974
4975        return f"{variable} AS {kind}{default}"
4976
4977    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
4978        kind = self.sql(expression, "kind")
4979        this = self.sql(expression, "this")
4980        set = self.sql(expression, "expression")
4981        using = self.sql(expression, "using")
4982        using = f" USING {using}" if using else ""
4983
4984        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
4985
4986        return f"{kind_sql} {this} SET {set}{using}"
4987
4988    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
4989        params = self.expressions(expression, key="params", flat=True)
4990        return self.func(expression.name, *expression.expressions) + f"({params})"
4991
4992    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
4993        return self.func(expression.name, *expression.expressions)
4994
4995    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
4996        return self.anonymousaggfunc_sql(expression)
4997
4998    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
4999        return self.parameterizedagg_sql(expression)
5000
5001    def show_sql(self, expression: exp.Show) -> str:
5002        self.unsupported("Unsupported SHOW statement")
5003        return ""
5004
5005    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5006        # Snowflake GET/PUT statements:
5007        #   PUT <file> <internalStage> <properties>
5008        #   GET <internalStage> <file> <properties>
5009        props = expression.args.get("properties")
5010        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5011        this = self.sql(expression, "this")
5012        target = self.sql(expression, "target")
5013
5014        if isinstance(expression, exp.Put):
5015            return f"PUT {this} {target}{props_sql}"
5016        else:
5017            return f"GET {target} {this}{props_sql}"
5018
5019    def translatecharacters_sql(self, expression: exp.TranslateCharacters):
5020        this = self.sql(expression, "this")
5021        expr = self.sql(expression, "expression")
5022        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5023        return f"TRANSLATE({this} USING {expr}{with_error})"
5024
5025    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5026        if self.SUPPORTS_DECODE_CASE:
5027            return self.func("DECODE", *expression.expressions)
5028
5029        expression, *expressions = expression.expressions
5030
5031        ifs = []
5032        for search, result in zip(expressions[::2], expressions[1::2]):
5033            if isinstance(search, exp.Literal):
5034                ifs.append(exp.If(this=expression.eq(search), true=result))
5035            elif isinstance(search, exp.Null):
5036                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5037            else:
5038                if isinstance(search, exp.Binary):
5039                    search = exp.paren(search)
5040
5041                cond = exp.or_(
5042                    expression.eq(search),
5043                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5044                    copy=False,
5045                )
5046                ifs.append(exp.If(this=cond, true=result))
5047
5048        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5049        return self.sql(case)
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.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.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 223        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 224        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 225        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 226        exp.ForceProperty: lambda *_: "FORCE",
 227    }
 228
 229    # Whether null ordering is supported in order by
 230    # True: Full Support, None: No support, False: No support for certain cases
 231    # such as window specifications, aggregate functions etc
 232    NULL_ORDERING_SUPPORTED: t.Optional[bool] = True
 233
 234    # Whether ignore nulls is inside the agg or outside.
 235    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 236    IGNORE_NULLS_IN_FUNC = False
 237
 238    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 239    LOCKING_READS_SUPPORTED = False
 240
 241    # Whether the EXCEPT and INTERSECT operations can return duplicates
 242    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 243
 244    # Wrap derived values in parens, usually standard but spark doesn't support it
 245    WRAP_DERIVED_VALUES = True
 246
 247    # Whether create function uses an AS before the RETURN
 248    CREATE_FUNCTION_RETURN_AS = True
 249
 250    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 251    MATCHED_BY_SOURCE = True
 252
 253    # Whether the INTERVAL expression works only with values like '1 day'
 254    SINGLE_STRING_INTERVAL = False
 255
 256    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 257    INTERVAL_ALLOWS_PLURAL_FORM = True
 258
 259    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 260    LIMIT_FETCH = "ALL"
 261
 262    # Whether limit and fetch allows expresions or just limits
 263    LIMIT_ONLY_LITERALS = False
 264
 265    # Whether a table is allowed to be renamed with a db
 266    RENAME_TABLE_WITH_DB = True
 267
 268    # The separator for grouping sets and rollups
 269    GROUPINGS_SEP = ","
 270
 271    # The string used for creating an index on a table
 272    INDEX_ON = "ON"
 273
 274    # Whether join hints should be generated
 275    JOIN_HINTS = True
 276
 277    # Whether table hints should be generated
 278    TABLE_HINTS = True
 279
 280    # Whether query hints should be generated
 281    QUERY_HINTS = True
 282
 283    # What kind of separator to use for query hints
 284    QUERY_HINT_SEP = ", "
 285
 286    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 287    IS_BOOL_ALLOWED = True
 288
 289    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 290    DUPLICATE_KEY_UPDATE_WITH_SET = True
 291
 292    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 293    LIMIT_IS_TOP = False
 294
 295    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 296    RETURNING_END = True
 297
 298    # Whether to generate an unquoted value for EXTRACT's date part argument
 299    EXTRACT_ALLOWS_QUOTES = True
 300
 301    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 302    TZ_TO_WITH_TIME_ZONE = False
 303
 304    # Whether the NVL2 function is supported
 305    NVL2_SUPPORTED = True
 306
 307    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 308    SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE")
 309
 310    # Whether VALUES statements can be used as derived tables.
 311    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 312    # SELECT * VALUES into SELECT UNION
 313    VALUES_AS_TABLE = True
 314
 315    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 316    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 317
 318    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 319    UNNEST_WITH_ORDINALITY = True
 320
 321    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 322    AGGREGATE_FILTER_SUPPORTED = True
 323
 324    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 325    SEMI_ANTI_JOIN_WITH_SIDE = True
 326
 327    # Whether to include the type of a computed column in the CREATE DDL
 328    COMPUTED_COLUMN_WITH_TYPE = True
 329
 330    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 331    SUPPORTS_TABLE_COPY = True
 332
 333    # Whether parentheses are required around the table sample's expression
 334    TABLESAMPLE_REQUIRES_PARENS = True
 335
 336    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 337    TABLESAMPLE_SIZE_IS_ROWS = True
 338
 339    # The keyword(s) to use when generating a sample clause
 340    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 341
 342    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 343    TABLESAMPLE_WITH_METHOD = True
 344
 345    # The keyword to use when specifying the seed of a sample clause
 346    TABLESAMPLE_SEED_KEYWORD = "SEED"
 347
 348    # Whether COLLATE is a function instead of a binary operator
 349    COLLATE_IS_FUNC = False
 350
 351    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 352    DATA_TYPE_SPECIFIERS_ALLOWED = False
 353
 354    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 355    ENSURE_BOOLS = False
 356
 357    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 358    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 359
 360    # Whether CONCAT requires >1 arguments
 361    SUPPORTS_SINGLE_ARG_CONCAT = True
 362
 363    # Whether LAST_DAY function supports a date part argument
 364    LAST_DAY_SUPPORTS_DATE_PART = True
 365
 366    # Whether named columns are allowed in table aliases
 367    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 368
 369    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 370    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 371
 372    # What delimiter to use for separating JSON key/value pairs
 373    JSON_KEY_VALUE_PAIR_SEP = ":"
 374
 375    # INSERT OVERWRITE TABLE x override
 376    INSERT_OVERWRITE = " OVERWRITE TABLE"
 377
 378    # Whether the SELECT .. INTO syntax is used instead of CTAS
 379    SUPPORTS_SELECT_INTO = False
 380
 381    # Whether UNLOGGED tables can be created
 382    SUPPORTS_UNLOGGED_TABLES = False
 383
 384    # Whether the CREATE TABLE LIKE statement is supported
 385    SUPPORTS_CREATE_TABLE_LIKE = True
 386
 387    # Whether the LikeProperty needs to be specified inside of the schema clause
 388    LIKE_PROPERTY_INSIDE_SCHEMA = False
 389
 390    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 391    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 392    MULTI_ARG_DISTINCT = True
 393
 394    # Whether the JSON extraction operators expect a value of type JSON
 395    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 396
 397    # Whether bracketed keys like ["foo"] are supported in JSON paths
 398    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 399
 400    # Whether to escape keys using single quotes in JSON paths
 401    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 402
 403    # The JSONPathPart expressions supported by this dialect
 404    SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy()
 405
 406    # Whether any(f(x) for x in array) can be implemented by this dialect
 407    CAN_IMPLEMENT_ARRAY_ANY = False
 408
 409    # Whether the function TO_NUMBER is supported
 410    SUPPORTS_TO_NUMBER = True
 411
 412    # Whether EXCLUDE in window specification is supported
 413    SUPPORTS_WINDOW_EXCLUDE = False
 414
 415    # Whether or not set op modifiers apply to the outer set op or select.
 416    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 417    # True means limit 1 happens after the set op, False means it it happens on y.
 418    SET_OP_MODIFIERS = True
 419
 420    # Whether parameters from COPY statement are wrapped in parentheses
 421    COPY_PARAMS_ARE_WRAPPED = True
 422
 423    # Whether values of params are set with "=" token or empty space
 424    COPY_PARAMS_EQ_REQUIRED = False
 425
 426    # Whether COPY statement has INTO keyword
 427    COPY_HAS_INTO_KEYWORD = True
 428
 429    # Whether the conditional TRY(expression) function is supported
 430    TRY_SUPPORTED = True
 431
 432    # Whether the UESCAPE syntax in unicode strings is supported
 433    SUPPORTS_UESCAPE = True
 434
 435    # The keyword to use when generating a star projection with excluded columns
 436    STAR_EXCEPT = "EXCEPT"
 437
 438    # The HEX function name
 439    HEX_FUNC = "HEX"
 440
 441    # The keywords to use when prefixing & separating WITH based properties
 442    WITH_PROPERTIES_PREFIX = "WITH"
 443
 444    # Whether to quote the generated expression of exp.JsonPath
 445    QUOTE_JSON_PATH = True
 446
 447    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 448    PAD_FILL_PATTERN_IS_REQUIRED = False
 449
 450    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 451    SUPPORTS_EXPLODING_PROJECTIONS = True
 452
 453    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 454    ARRAY_CONCAT_IS_VAR_LEN = True
 455
 456    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 457    SUPPORTS_CONVERT_TIMEZONE = False
 458
 459    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 460    SUPPORTS_MEDIAN = True
 461
 462    # Whether UNIX_SECONDS(timestamp) is supported
 463    SUPPORTS_UNIX_SECONDS = False
 464
 465    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 466    ALTER_SET_WRAPPED = False
 467
 468    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 469    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 470    # TODO: The normalization should be done by default once we've tested it across all dialects.
 471    NORMALIZE_EXTRACT_DATE_PARTS = False
 472
 473    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 474    PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
 475
 476    # The function name of the exp.ArraySize expression
 477    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 478
 479    # The syntax to use when altering the type of a column
 480    ALTER_SET_TYPE = "SET DATA TYPE"
 481
 482    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 483    # None -> Doesn't support it at all
 484    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 485    # True (Postgres) -> Explicitly requires it
 486    ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None
 487
 488    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 489    SUPPORTS_DECODE_CASE = True
 490
 491    TYPE_MAPPING = {
 492        exp.DataType.Type.DATETIME2: "TIMESTAMP",
 493        exp.DataType.Type.NCHAR: "CHAR",
 494        exp.DataType.Type.NVARCHAR: "VARCHAR",
 495        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 496        exp.DataType.Type.LONGTEXT: "TEXT",
 497        exp.DataType.Type.TINYTEXT: "TEXT",
 498        exp.DataType.Type.BLOB: "VARBINARY",
 499        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 500        exp.DataType.Type.LONGBLOB: "BLOB",
 501        exp.DataType.Type.TINYBLOB: "BLOB",
 502        exp.DataType.Type.INET: "INET",
 503        exp.DataType.Type.ROWVERSION: "VARBINARY",
 504        exp.DataType.Type.SMALLDATETIME: "TIMESTAMP",
 505    }
 506
 507    TIME_PART_SINGULARS = {
 508        "MICROSECONDS": "MICROSECOND",
 509        "SECONDS": "SECOND",
 510        "MINUTES": "MINUTE",
 511        "HOURS": "HOUR",
 512        "DAYS": "DAY",
 513        "WEEKS": "WEEK",
 514        "MONTHS": "MONTH",
 515        "QUARTERS": "QUARTER",
 516        "YEARS": "YEAR",
 517    }
 518
 519    AFTER_HAVING_MODIFIER_TRANSFORMS = {
 520        "cluster": lambda self, e: self.sql(e, "cluster"),
 521        "distribute": lambda self, e: self.sql(e, "distribute"),
 522        "sort": lambda self, e: self.sql(e, "sort"),
 523        "windows": lambda self, e: (
 524            self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
 525            if e.args.get("windows")
 526            else ""
 527        ),
 528        "qualify": lambda self, e: self.sql(e, "qualify"),
 529    }
 530
 531    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 532
 533    STRUCT_DELIMITER = ("<", ">")
 534
 535    PARAMETER_TOKEN = "@"
 536    NAMED_PLACEHOLDER_TOKEN = ":"
 537
 538    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set()
 539
 540    PROPERTIES_LOCATION = {
 541        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 542        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 543        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 544        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 545        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 546        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 547        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 548        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 549        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 550        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 551        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 552        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 553        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 554        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 555        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 556        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 557        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 558        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 559        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 560        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 561        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 562        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 563        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 564        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 565        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 566        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 567        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 568        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 569        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 570        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 571        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 572        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 573        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 574        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 575        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 576        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 577        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 578        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 579        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 580        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 581        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 582        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 583        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 584        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 585        exp.LogProperty: exp.Properties.Location.POST_NAME,
 586        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 587        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 588        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 589        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 590        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 591        exp.Order: exp.Properties.Location.POST_SCHEMA,
 592        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 593        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 594        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 595        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 596        exp.Property: exp.Properties.Location.POST_WITH,
 597        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 598        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 599        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 600        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 601        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 602        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 603        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 604        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 605        exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA,
 606        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 607        exp.Set: exp.Properties.Location.POST_SCHEMA,
 608        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 609        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 610        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 611        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 612        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 613        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 614        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 615        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 616        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 617        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 618        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 619        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 620        exp.Tags: exp.Properties.Location.POST_WITH,
 621        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 622        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 623        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 624        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 625        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 626        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 627        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 628        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 629        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 630        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 631        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 632        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 633        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 634        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 635        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 636    }
 637
 638    # Keywords that can't be used as unquoted identifier names
 639    RESERVED_KEYWORDS: t.Set[str] = set()
 640
 641    # Expressions whose comments are separated from them for better formatting
 642    WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 643        exp.Command,
 644        exp.Create,
 645        exp.Describe,
 646        exp.Delete,
 647        exp.Drop,
 648        exp.From,
 649        exp.Insert,
 650        exp.Join,
 651        exp.MultitableInserts,
 652        exp.Order,
 653        exp.Group,
 654        exp.Having,
 655        exp.Select,
 656        exp.SetOperation,
 657        exp.Update,
 658        exp.Where,
 659        exp.With,
 660    )
 661
 662    # Expressions that should not have their comments generated in maybe_comment
 663    EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 664        exp.Binary,
 665        exp.SetOperation,
 666    )
 667
 668    # Expressions that can remain unwrapped when appearing in the context of an INTERVAL
 669    UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = (
 670        exp.Column,
 671        exp.Literal,
 672        exp.Neg,
 673        exp.Paren,
 674    )
 675
 676    PARAMETERIZABLE_TEXT_TYPES = {
 677        exp.DataType.Type.NVARCHAR,
 678        exp.DataType.Type.VARCHAR,
 679        exp.DataType.Type.CHAR,
 680        exp.DataType.Type.NCHAR,
 681    }
 682
 683    # Expressions that need to have all CTEs under them bubbled up to them
 684    EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set()
 685
 686    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = ()
 687
 688    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 689
 690    __slots__ = (
 691        "pretty",
 692        "identify",
 693        "normalize",
 694        "pad",
 695        "_indent",
 696        "normalize_functions",
 697        "unsupported_level",
 698        "max_unsupported",
 699        "leading_comma",
 700        "max_text_width",
 701        "comments",
 702        "dialect",
 703        "unsupported_messages",
 704        "_escaped_quote_end",
 705        "_escaped_identifier_end",
 706        "_next_name",
 707        "_identifier_start",
 708        "_identifier_end",
 709        "_quote_json_path_key_using_brackets",
 710    )
 711
 712    def __init__(
 713        self,
 714        pretty: t.Optional[bool] = None,
 715        identify: str | bool = False,
 716        normalize: bool = False,
 717        pad: int = 2,
 718        indent: int = 2,
 719        normalize_functions: t.Optional[str | bool] = None,
 720        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 721        max_unsupported: int = 3,
 722        leading_comma: bool = False,
 723        max_text_width: int = 80,
 724        comments: bool = True,
 725        dialect: DialectType = None,
 726    ):
 727        import sqlglot
 728        from sqlglot.dialects import Dialect
 729
 730        self.pretty = pretty if pretty is not None else sqlglot.pretty
 731        self.identify = identify
 732        self.normalize = normalize
 733        self.pad = pad
 734        self._indent = indent
 735        self.unsupported_level = unsupported_level
 736        self.max_unsupported = max_unsupported
 737        self.leading_comma = leading_comma
 738        self.max_text_width = max_text_width
 739        self.comments = comments
 740        self.dialect = Dialect.get_or_raise(dialect)
 741
 742        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 743        self.normalize_functions = (
 744            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 745        )
 746
 747        self.unsupported_messages: t.List[str] = []
 748        self._escaped_quote_end: str = (
 749            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 750        )
 751        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 752
 753        self._next_name = name_sequence("_t")
 754
 755        self._identifier_start = self.dialect.IDENTIFIER_START
 756        self._identifier_end = self.dialect.IDENTIFIER_END
 757
 758        self._quote_json_path_key_using_brackets = True
 759
 760    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
 761        """
 762        Generates the SQL string corresponding to the given syntax tree.
 763
 764        Args:
 765            expression: The syntax tree.
 766            copy: Whether to copy the expression. The generator performs mutations so
 767                it is safer to copy.
 768
 769        Returns:
 770            The SQL string corresponding to `expression`.
 771        """
 772        if copy:
 773            expression = expression.copy()
 774
 775        expression = self.preprocess(expression)
 776
 777        self.unsupported_messages = []
 778        sql = self.sql(expression).strip()
 779
 780        if self.pretty:
 781            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 782
 783        if self.unsupported_level == ErrorLevel.IGNORE:
 784            return sql
 785
 786        if self.unsupported_level == ErrorLevel.WARN:
 787            for msg in self.unsupported_messages:
 788                logger.warning(msg)
 789        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 790            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 791
 792        return sql
 793
 794    def preprocess(self, expression: exp.Expression) -> exp.Expression:
 795        """Apply generic preprocessing transformations to a given expression."""
 796        expression = self._move_ctes_to_top_level(expression)
 797
 798        if self.ENSURE_BOOLS:
 799            from sqlglot.transforms import ensure_bools
 800
 801            expression = ensure_bools(expression)
 802
 803        return expression
 804
 805    def _move_ctes_to_top_level(self, expression: E) -> E:
 806        if (
 807            not expression.parent
 808            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 809            and any(node.parent is not expression for node in expression.find_all(exp.With))
 810        ):
 811            from sqlglot.transforms import move_ctes_to_top_level
 812
 813            expression = move_ctes_to_top_level(expression)
 814        return expression
 815
 816    def unsupported(self, message: str) -> None:
 817        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 818            raise UnsupportedError(message)
 819        self.unsupported_messages.append(message)
 820
 821    def sep(self, sep: str = " ") -> str:
 822        return f"{sep.strip()}\n" if self.pretty else sep
 823
 824    def seg(self, sql: str, sep: str = " ") -> str:
 825        return f"{self.sep(sep)}{sql}"
 826
 827    def sanitize_comment(self, comment: str) -> str:
 828        comment = " " + comment if comment[0].strip() else comment
 829        comment = comment + " " if comment[-1].strip() else comment
 830
 831        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
 832            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
 833            comment = comment.replace("*/", "* /")
 834
 835        return comment
 836
 837    def maybe_comment(
 838        self,
 839        sql: str,
 840        expression: t.Optional[exp.Expression] = None,
 841        comments: t.Optional[t.List[str]] = None,
 842        separated: bool = False,
 843    ) -> str:
 844        comments = (
 845            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 846            if self.comments
 847            else None
 848        )
 849
 850        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 851            return sql
 852
 853        comments_sql = " ".join(
 854            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
 855        )
 856
 857        if not comments_sql:
 858            return sql
 859
 860        comments_sql = self._replace_line_breaks(comments_sql)
 861
 862        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 863            return (
 864                f"{self.sep()}{comments_sql}{sql}"
 865                if not sql or sql[0].isspace()
 866                else f"{comments_sql}{self.sep()}{sql}"
 867            )
 868
 869        return f"{sql} {comments_sql}"
 870
 871    def wrap(self, expression: exp.Expression | str) -> str:
 872        this_sql = (
 873            self.sql(expression)
 874            if isinstance(expression, exp.UNWRAPPED_QUERIES)
 875            else self.sql(expression, "this")
 876        )
 877        if not this_sql:
 878            return "()"
 879
 880        this_sql = self.indent(this_sql, level=1, pad=0)
 881        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 882
 883    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 884        original = self.identify
 885        self.identify = False
 886        result = func(*args, **kwargs)
 887        self.identify = original
 888        return result
 889
 890    def normalize_func(self, name: str) -> str:
 891        if self.normalize_functions == "upper" or self.normalize_functions is True:
 892            return name.upper()
 893        if self.normalize_functions == "lower":
 894            return name.lower()
 895        return name
 896
 897    def indent(
 898        self,
 899        sql: str,
 900        level: int = 0,
 901        pad: t.Optional[int] = None,
 902        skip_first: bool = False,
 903        skip_last: bool = False,
 904    ) -> str:
 905        if not self.pretty or not sql:
 906            return sql
 907
 908        pad = self.pad if pad is None else pad
 909        lines = sql.split("\n")
 910
 911        return "\n".join(
 912            (
 913                line
 914                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 915                else f"{' ' * (level * self._indent + pad)}{line}"
 916            )
 917            for i, line in enumerate(lines)
 918        )
 919
 920    def sql(
 921        self,
 922        expression: t.Optional[str | exp.Expression],
 923        key: t.Optional[str] = None,
 924        comment: bool = True,
 925    ) -> str:
 926        if not expression:
 927            return ""
 928
 929        if isinstance(expression, str):
 930            return expression
 931
 932        if key:
 933            value = expression.args.get(key)
 934            if value:
 935                return self.sql(value)
 936            return ""
 937
 938        transform = self.TRANSFORMS.get(expression.__class__)
 939
 940        if callable(transform):
 941            sql = transform(self, expression)
 942        elif isinstance(expression, exp.Expression):
 943            exp_handler_name = f"{expression.key}_sql"
 944
 945            if hasattr(self, exp_handler_name):
 946                sql = getattr(self, exp_handler_name)(expression)
 947            elif isinstance(expression, exp.Func):
 948                sql = self.function_fallback_sql(expression)
 949            elif isinstance(expression, exp.Property):
 950                sql = self.property_sql(expression)
 951            else:
 952                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
 953        else:
 954            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
 955
 956        return self.maybe_comment(sql, expression) if self.comments and comment else sql
 957
 958    def uncache_sql(self, expression: exp.Uncache) -> str:
 959        table = self.sql(expression, "this")
 960        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
 961        return f"UNCACHE TABLE{exists_sql} {table}"
 962
 963    def cache_sql(self, expression: exp.Cache) -> str:
 964        lazy = " LAZY" if expression.args.get("lazy") else ""
 965        table = self.sql(expression, "this")
 966        options = expression.args.get("options")
 967        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
 968        sql = self.sql(expression, "expression")
 969        sql = f" AS{self.sep()}{sql}" if sql else ""
 970        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
 971        return self.prepend_ctes(expression, sql)
 972
 973    def characterset_sql(self, expression: exp.CharacterSet) -> str:
 974        if isinstance(expression.parent, exp.Cast):
 975            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
 976        default = "DEFAULT " if expression.args.get("default") else ""
 977        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
 978
 979    def column_parts(self, expression: exp.Column) -> str:
 980        return ".".join(
 981            self.sql(part)
 982            for part in (
 983                expression.args.get("catalog"),
 984                expression.args.get("db"),
 985                expression.args.get("table"),
 986                expression.args.get("this"),
 987            )
 988            if part
 989        )
 990
 991    def column_sql(self, expression: exp.Column) -> str:
 992        join_mark = " (+)" if expression.args.get("join_mark") else ""
 993
 994        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
 995            join_mark = ""
 996            self.unsupported("Outer join syntax using the (+) operator is not supported.")
 997
 998        return f"{self.column_parts(expression)}{join_mark}"
 999
1000    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1001        this = self.sql(expression, "this")
1002        this = f" {this}" if this else ""
1003        position = self.sql(expression, "position")
1004        return f"{position}{this}"
1005
1006    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1007        column = self.sql(expression, "this")
1008        kind = self.sql(expression, "kind")
1009        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1010        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1011        kind = f"{sep}{kind}" if kind else ""
1012        constraints = f" {constraints}" if constraints else ""
1013        position = self.sql(expression, "position")
1014        position = f" {position}" if position else ""
1015
1016        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1017            kind = ""
1018
1019        return f"{exists}{column}{kind}{constraints}{position}"
1020
1021    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1022        this = self.sql(expression, "this")
1023        kind_sql = self.sql(expression, "kind").strip()
1024        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1025
1026    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1027        this = self.sql(expression, "this")
1028        if expression.args.get("not_null"):
1029            persisted = " PERSISTED NOT NULL"
1030        elif expression.args.get("persisted"):
1031            persisted = " PERSISTED"
1032        else:
1033            persisted = ""
1034
1035        return f"AS {this}{persisted}"
1036
1037    def autoincrementcolumnconstraint_sql(self, _) -> str:
1038        return self.token_sql(TokenType.AUTO_INCREMENT)
1039
1040    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1041        if isinstance(expression.this, list):
1042            this = self.wrap(self.expressions(expression, key="this", flat=True))
1043        else:
1044            this = self.sql(expression, "this")
1045
1046        return f"COMPRESS {this}"
1047
1048    def generatedasidentitycolumnconstraint_sql(
1049        self, expression: exp.GeneratedAsIdentityColumnConstraint
1050    ) -> str:
1051        this = ""
1052        if expression.this is not None:
1053            on_null = " ON NULL" if expression.args.get("on_null") else ""
1054            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1055
1056        start = expression.args.get("start")
1057        start = f"START WITH {start}" if start else ""
1058        increment = expression.args.get("increment")
1059        increment = f" INCREMENT BY {increment}" if increment else ""
1060        minvalue = expression.args.get("minvalue")
1061        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1062        maxvalue = expression.args.get("maxvalue")
1063        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1064        cycle = expression.args.get("cycle")
1065        cycle_sql = ""
1066
1067        if cycle is not None:
1068            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1069            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1070
1071        sequence_opts = ""
1072        if start or increment or cycle_sql:
1073            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1074            sequence_opts = f" ({sequence_opts.strip()})"
1075
1076        expr = self.sql(expression, "expression")
1077        expr = f"({expr})" if expr else "IDENTITY"
1078
1079        return f"GENERATED{this} AS {expr}{sequence_opts}"
1080
1081    def generatedasrowcolumnconstraint_sql(
1082        self, expression: exp.GeneratedAsRowColumnConstraint
1083    ) -> str:
1084        start = "START" if expression.args.get("start") else "END"
1085        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1086        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1087
1088    def periodforsystemtimeconstraint_sql(
1089        self, expression: exp.PeriodForSystemTimeConstraint
1090    ) -> str:
1091        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1092
1093    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1094        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1095
1096    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1097        desc = expression.args.get("desc")
1098        if desc is not None:
1099            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1100        options = self.expressions(expression, key="options", flat=True, sep=" ")
1101        options = f" {options}" if options else ""
1102        return f"PRIMARY KEY{options}"
1103
1104    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1105        this = self.sql(expression, "this")
1106        this = f" {this}" if this else ""
1107        index_type = expression.args.get("index_type")
1108        index_type = f" USING {index_type}" if index_type else ""
1109        on_conflict = self.sql(expression, "on_conflict")
1110        on_conflict = f" {on_conflict}" if on_conflict else ""
1111        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1112        options = self.expressions(expression, key="options", flat=True, sep=" ")
1113        options = f" {options}" if options else ""
1114        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1115
1116    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1117        return self.sql(expression, "this")
1118
1119    def create_sql(self, expression: exp.Create) -> str:
1120        kind = self.sql(expression, "kind")
1121        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1122        properties = expression.args.get("properties")
1123        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1124
1125        this = self.createable_sql(expression, properties_locs)
1126
1127        properties_sql = ""
1128        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1129            exp.Properties.Location.POST_WITH
1130        ):
1131            properties_sql = self.sql(
1132                exp.Properties(
1133                    expressions=[
1134                        *properties_locs[exp.Properties.Location.POST_SCHEMA],
1135                        *properties_locs[exp.Properties.Location.POST_WITH],
1136                    ]
1137                )
1138            )
1139
1140            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1141                properties_sql = self.sep() + properties_sql
1142            elif not self.pretty:
1143                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1144                properties_sql = f" {properties_sql}"
1145
1146        begin = " BEGIN" if expression.args.get("begin") else ""
1147        end = " END" if expression.args.get("end") else ""
1148
1149        expression_sql = self.sql(expression, "expression")
1150        if expression_sql:
1151            expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
1152
1153            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1154                postalias_props_sql = ""
1155                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1156                    postalias_props_sql = self.properties(
1157                        exp.Properties(
1158                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1159                        ),
1160                        wrapped=False,
1161                    )
1162                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1163                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1164
1165        postindex_props_sql = ""
1166        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1167            postindex_props_sql = self.properties(
1168                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1169                wrapped=False,
1170                prefix=" ",
1171            )
1172
1173        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1174        indexes = f" {indexes}" if indexes else ""
1175        index_sql = indexes + postindex_props_sql
1176
1177        replace = " OR REPLACE" if expression.args.get("replace") else ""
1178        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1179        unique = " UNIQUE" if expression.args.get("unique") else ""
1180
1181        clustered = expression.args.get("clustered")
1182        if clustered is None:
1183            clustered_sql = ""
1184        elif clustered:
1185            clustered_sql = " CLUSTERED COLUMNSTORE"
1186        else:
1187            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1188
1189        postcreate_props_sql = ""
1190        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1191            postcreate_props_sql = self.properties(
1192                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1193                sep=" ",
1194                prefix=" ",
1195                wrapped=False,
1196            )
1197
1198        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1199
1200        postexpression_props_sql = ""
1201        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1202            postexpression_props_sql = self.properties(
1203                exp.Properties(
1204                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1205                ),
1206                sep=" ",
1207                prefix=" ",
1208                wrapped=False,
1209            )
1210
1211        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1212        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1213        no_schema_binding = (
1214            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1215        )
1216
1217        clone = self.sql(expression, "clone")
1218        clone = f" {clone}" if clone else ""
1219
1220        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1221            properties_expression = f"{expression_sql}{properties_sql}"
1222        else:
1223            properties_expression = f"{properties_sql}{expression_sql}"
1224
1225        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1226        return self.prepend_ctes(expression, expression_sql)
1227
1228    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1229        start = self.sql(expression, "start")
1230        start = f"START WITH {start}" if start else ""
1231        increment = self.sql(expression, "increment")
1232        increment = f" INCREMENT BY {increment}" if increment else ""
1233        minvalue = self.sql(expression, "minvalue")
1234        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1235        maxvalue = self.sql(expression, "maxvalue")
1236        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1237        owned = self.sql(expression, "owned")
1238        owned = f" OWNED BY {owned}" if owned else ""
1239
1240        cache = expression.args.get("cache")
1241        if cache is None:
1242            cache_str = ""
1243        elif cache is True:
1244            cache_str = " CACHE"
1245        else:
1246            cache_str = f" CACHE {cache}"
1247
1248        options = self.expressions(expression, key="options", flat=True, sep=" ")
1249        options = f" {options}" if options else ""
1250
1251        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1252
1253    def clone_sql(self, expression: exp.Clone) -> str:
1254        this = self.sql(expression, "this")
1255        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1256        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1257        return f"{shallow}{keyword} {this}"
1258
1259    def describe_sql(self, expression: exp.Describe) -> str:
1260        style = expression.args.get("style")
1261        style = f" {style}" if style else ""
1262        partition = self.sql(expression, "partition")
1263        partition = f" {partition}" if partition else ""
1264        format = self.sql(expression, "format")
1265        format = f" {format}" if format else ""
1266
1267        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1268
1269    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1270        tag = self.sql(expression, "tag")
1271        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1272
1273    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1274        with_ = self.sql(expression, "with")
1275        if with_:
1276            sql = f"{with_}{self.sep()}{sql}"
1277        return sql
1278
1279    def with_sql(self, expression: exp.With) -> str:
1280        sql = self.expressions(expression, flat=True)
1281        recursive = (
1282            "RECURSIVE "
1283            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1284            else ""
1285        )
1286        search = self.sql(expression, "search")
1287        search = f" {search}" if search else ""
1288
1289        return f"WITH {recursive}{sql}{search}"
1290
1291    def cte_sql(self, expression: exp.CTE) -> str:
1292        alias = expression.args.get("alias")
1293        if alias:
1294            alias.add_comments(expression.pop_comments())
1295
1296        alias_sql = self.sql(expression, "alias")
1297
1298        materialized = expression.args.get("materialized")
1299        if materialized is False:
1300            materialized = "NOT MATERIALIZED "
1301        elif materialized:
1302            materialized = "MATERIALIZED "
1303
1304        return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1305
1306    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1307        alias = self.sql(expression, "this")
1308        columns = self.expressions(expression, key="columns", flat=True)
1309        columns = f"({columns})" if columns else ""
1310
1311        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1312            columns = ""
1313            self.unsupported("Named columns are not supported in table alias.")
1314
1315        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1316            alias = self._next_name()
1317
1318        return f"{alias}{columns}"
1319
1320    def bitstring_sql(self, expression: exp.BitString) -> str:
1321        this = self.sql(expression, "this")
1322        if self.dialect.BIT_START:
1323            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1324        return f"{int(this, 2)}"
1325
1326    def hexstring_sql(
1327        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1328    ) -> str:
1329        this = self.sql(expression, "this")
1330        is_integer_type = expression.args.get("is_integer")
1331
1332        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1333            not self.dialect.HEX_START and not binary_function_repr
1334        ):
1335            # Integer representation will be returned if:
1336            # - The read dialect treats the hex value as integer literal but not the write
1337            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1338            return f"{int(this, 16)}"
1339
1340        if not is_integer_type:
1341            # Read dialect treats the hex value as BINARY/BLOB
1342            if binary_function_repr:
1343                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1344                return self.func(binary_function_repr, exp.Literal.string(this))
1345            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1346                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1347                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1348
1349        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1350
1351    def bytestring_sql(self, expression: exp.ByteString) -> str:
1352        this = self.sql(expression, "this")
1353        if self.dialect.BYTE_START:
1354            return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}"
1355        return this
1356
1357    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1358        this = self.sql(expression, "this")
1359        escape = expression.args.get("escape")
1360
1361        if self.dialect.UNICODE_START:
1362            escape_substitute = r"\\\1"
1363            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1364        else:
1365            escape_substitute = r"\\u\1"
1366            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1367
1368        if escape:
1369            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1370            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1371        else:
1372            escape_pattern = ESCAPED_UNICODE_RE
1373            escape_sql = ""
1374
1375        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1376            this = escape_pattern.sub(escape_substitute, this)
1377
1378        return f"{left_quote}{this}{right_quote}{escape_sql}"
1379
1380    def rawstring_sql(self, expression: exp.RawString) -> str:
1381        string = expression.this
1382        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1383            string = string.replace("\\", "\\\\")
1384
1385        string = self.escape_str(string, escape_backslash=False)
1386        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1387
1388    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1389        this = self.sql(expression, "this")
1390        specifier = self.sql(expression, "expression")
1391        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1392        return f"{this}{specifier}"
1393
1394    def datatype_sql(self, expression: exp.DataType) -> str:
1395        nested = ""
1396        values = ""
1397        interior = self.expressions(expression, flat=True)
1398
1399        type_value = expression.this
1400        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1401            type_sql = self.sql(expression, "kind")
1402        else:
1403            type_sql = (
1404                self.TYPE_MAPPING.get(type_value, type_value.value)
1405                if isinstance(type_value, exp.DataType.Type)
1406                else type_value
1407            )
1408
1409        if interior:
1410            if expression.args.get("nested"):
1411                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1412                if expression.args.get("values") is not None:
1413                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1414                    values = self.expressions(expression, key="values", flat=True)
1415                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1416            elif type_value == exp.DataType.Type.INTERVAL:
1417                nested = f" {interior}"
1418            else:
1419                nested = f"({interior})"
1420
1421        type_sql = f"{type_sql}{nested}{values}"
1422        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1423            exp.DataType.Type.TIMETZ,
1424            exp.DataType.Type.TIMESTAMPTZ,
1425        ):
1426            type_sql = f"{type_sql} WITH TIME ZONE"
1427
1428        return type_sql
1429
1430    def directory_sql(self, expression: exp.Directory) -> str:
1431        local = "LOCAL " if expression.args.get("local") else ""
1432        row_format = self.sql(expression, "row_format")
1433        row_format = f" {row_format}" if row_format else ""
1434        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1435
1436    def delete_sql(self, expression: exp.Delete) -> str:
1437        this = self.sql(expression, "this")
1438        this = f" FROM {this}" if this else ""
1439        using = self.sql(expression, "using")
1440        using = f" USING {using}" if using else ""
1441        cluster = self.sql(expression, "cluster")
1442        cluster = f" {cluster}" if cluster else ""
1443        where = self.sql(expression, "where")
1444        returning = self.sql(expression, "returning")
1445        limit = self.sql(expression, "limit")
1446        tables = self.expressions(expression, key="tables")
1447        tables = f" {tables}" if tables else ""
1448        if self.RETURNING_END:
1449            expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}"
1450        else:
1451            expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}"
1452        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1453
1454    def drop_sql(self, expression: exp.Drop) -> str:
1455        this = self.sql(expression, "this")
1456        expressions = self.expressions(expression, flat=True)
1457        expressions = f" ({expressions})" if expressions else ""
1458        kind = expression.args["kind"]
1459        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1460        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1461        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1462        on_cluster = self.sql(expression, "cluster")
1463        on_cluster = f" {on_cluster}" if on_cluster else ""
1464        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1465        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1466        cascade = " CASCADE" if expression.args.get("cascade") else ""
1467        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1468        purge = " PURGE" if expression.args.get("purge") else ""
1469        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1470
1471    def set_operation(self, expression: exp.SetOperation) -> str:
1472        op_type = type(expression)
1473        op_name = op_type.key.upper()
1474
1475        distinct = expression.args.get("distinct")
1476        if (
1477            distinct is False
1478            and op_type in (exp.Except, exp.Intersect)
1479            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1480        ):
1481            self.unsupported(f"{op_name} ALL is not supported")
1482
1483        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1484
1485        if distinct is None:
1486            distinct = default_distinct
1487            if distinct is None:
1488                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1489
1490        if distinct is default_distinct:
1491            distinct_or_all = ""
1492        else:
1493            distinct_or_all = " DISTINCT" if distinct else " ALL"
1494
1495        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1496        side_kind = f"{side_kind} " if side_kind else ""
1497
1498        by_name = " BY NAME" if expression.args.get("by_name") else ""
1499        on = self.expressions(expression, key="on", flat=True)
1500        on = f" ON ({on})" if on else ""
1501
1502        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1503
1504    def set_operations(self, expression: exp.SetOperation) -> str:
1505        if not self.SET_OP_MODIFIERS:
1506            limit = expression.args.get("limit")
1507            order = expression.args.get("order")
1508
1509            if limit or order:
1510                select = self._move_ctes_to_top_level(
1511                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1512                )
1513
1514                if limit:
1515                    select = select.limit(limit.pop(), copy=False)
1516                if order:
1517                    select = select.order_by(order.pop(), copy=False)
1518                return self.sql(select)
1519
1520        sqls: t.List[str] = []
1521        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1522
1523        while stack:
1524            node = stack.pop()
1525
1526            if isinstance(node, exp.SetOperation):
1527                stack.append(node.expression)
1528                stack.append(
1529                    self.maybe_comment(
1530                        self.set_operation(node), comments=node.comments, separated=True
1531                    )
1532                )
1533                stack.append(node.this)
1534            else:
1535                sqls.append(self.sql(node))
1536
1537        this = self.sep().join(sqls)
1538        this = self.query_modifiers(expression, this)
1539        return self.prepend_ctes(expression, this)
1540
1541    def fetch_sql(self, expression: exp.Fetch) -> str:
1542        direction = expression.args.get("direction")
1543        direction = f" {direction}" if direction else ""
1544        count = self.sql(expression, "count")
1545        count = f" {count}" if count else ""
1546        limit_options = self.sql(expression, "limit_options")
1547        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1548        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1549
1550    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1551        percent = " PERCENT" if expression.args.get("percent") else ""
1552        rows = " ROWS" if expression.args.get("rows") else ""
1553        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1554        if not with_ties and rows:
1555            with_ties = " ONLY"
1556        return f"{percent}{rows}{with_ties}"
1557
1558    def filter_sql(self, expression: exp.Filter) -> str:
1559        if self.AGGREGATE_FILTER_SUPPORTED:
1560            this = self.sql(expression, "this")
1561            where = self.sql(expression, "expression").strip()
1562            return f"{this} FILTER({where})"
1563
1564        agg = expression.this
1565        agg_arg = agg.this
1566        cond = expression.expression.this
1567        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1568        return self.sql(agg)
1569
1570    def hint_sql(self, expression: exp.Hint) -> str:
1571        if not self.QUERY_HINTS:
1572            self.unsupported("Hints are not supported")
1573            return ""
1574
1575        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1576
1577    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1578        using = self.sql(expression, "using")
1579        using = f" USING {using}" if using else ""
1580        columns = self.expressions(expression, key="columns", flat=True)
1581        columns = f"({columns})" if columns else ""
1582        partition_by = self.expressions(expression, key="partition_by", flat=True)
1583        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1584        where = self.sql(expression, "where")
1585        include = self.expressions(expression, key="include", flat=True)
1586        if include:
1587            include = f" INCLUDE ({include})"
1588        with_storage = self.expressions(expression, key="with_storage", flat=True)
1589        with_storage = f" WITH ({with_storage})" if with_storage else ""
1590        tablespace = self.sql(expression, "tablespace")
1591        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1592        on = self.sql(expression, "on")
1593        on = f" ON {on}" if on else ""
1594
1595        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1596
1597    def index_sql(self, expression: exp.Index) -> str:
1598        unique = "UNIQUE " if expression.args.get("unique") else ""
1599        primary = "PRIMARY " if expression.args.get("primary") else ""
1600        amp = "AMP " if expression.args.get("amp") else ""
1601        name = self.sql(expression, "this")
1602        name = f"{name} " if name else ""
1603        table = self.sql(expression, "table")
1604        table = f"{self.INDEX_ON} {table}" if table else ""
1605
1606        index = "INDEX " if not table else ""
1607
1608        params = self.sql(expression, "params")
1609        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1610
1611    def identifier_sql(self, expression: exp.Identifier) -> str:
1612        text = expression.name
1613        lower = text.lower()
1614        text = lower if self.normalize and not expression.quoted else text
1615        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1616        if (
1617            expression.quoted
1618            or self.dialect.can_identify(text, self.identify)
1619            or lower in self.RESERVED_KEYWORDS
1620            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1621        ):
1622            text = f"{self._identifier_start}{text}{self._identifier_end}"
1623        return text
1624
1625    def hex_sql(self, expression: exp.Hex) -> str:
1626        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1627        if self.dialect.HEX_LOWERCASE:
1628            text = self.func("LOWER", text)
1629
1630        return text
1631
1632    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1633        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1634        if not self.dialect.HEX_LOWERCASE:
1635            text = self.func("LOWER", text)
1636        return text
1637
1638    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1639        input_format = self.sql(expression, "input_format")
1640        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1641        output_format = self.sql(expression, "output_format")
1642        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1643        return self.sep().join((input_format, output_format))
1644
1645    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1646        string = self.sql(exp.Literal.string(expression.name))
1647        return f"{prefix}{string}"
1648
1649    def partition_sql(self, expression: exp.Partition) -> str:
1650        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1651        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1652
1653    def properties_sql(self, expression: exp.Properties) -> str:
1654        root_properties = []
1655        with_properties = []
1656
1657        for p in expression.expressions:
1658            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1659            if p_loc == exp.Properties.Location.POST_WITH:
1660                with_properties.append(p)
1661            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1662                root_properties.append(p)
1663
1664        root_props = self.root_properties(exp.Properties(expressions=root_properties))
1665        with_props = self.with_properties(exp.Properties(expressions=with_properties))
1666
1667        if root_props and with_props and not self.pretty:
1668            with_props = " " + with_props
1669
1670        return root_props + with_props
1671
1672    def root_properties(self, properties: exp.Properties) -> str:
1673        if properties.expressions:
1674            return self.expressions(properties, indent=False, sep=" ")
1675        return ""
1676
1677    def properties(
1678        self,
1679        properties: exp.Properties,
1680        prefix: str = "",
1681        sep: str = ", ",
1682        suffix: str = "",
1683        wrapped: bool = True,
1684    ) -> str:
1685        if properties.expressions:
1686            expressions = self.expressions(properties, sep=sep, indent=False)
1687            if expressions:
1688                expressions = self.wrap(expressions) if wrapped else expressions
1689                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1690        return ""
1691
1692    def with_properties(self, properties: exp.Properties) -> str:
1693        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1694
1695    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1696        properties_locs = defaultdict(list)
1697        for p in properties.expressions:
1698            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1699            if p_loc != exp.Properties.Location.UNSUPPORTED:
1700                properties_locs[p_loc].append(p)
1701            else:
1702                self.unsupported(f"Unsupported property {p.key}")
1703
1704        return properties_locs
1705
1706    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1707        if isinstance(expression.this, exp.Dot):
1708            return self.sql(expression, "this")
1709        return f"'{expression.name}'" if string_key else expression.name
1710
1711    def property_sql(self, expression: exp.Property) -> str:
1712        property_cls = expression.__class__
1713        if property_cls == exp.Property:
1714            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1715
1716        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1717        if not property_name:
1718            self.unsupported(f"Unsupported property {expression.key}")
1719
1720        return f"{property_name}={self.sql(expression, 'this')}"
1721
1722    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1723        if self.SUPPORTS_CREATE_TABLE_LIKE:
1724            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1725            options = f" {options}" if options else ""
1726
1727            like = f"LIKE {self.sql(expression, 'this')}{options}"
1728            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1729                like = f"({like})"
1730
1731            return like
1732
1733        if expression.expressions:
1734            self.unsupported("Transpilation of LIKE property options is unsupported")
1735
1736        select = exp.select("*").from_(expression.this).limit(0)
1737        return f"AS {self.sql(select)}"
1738
1739    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1740        no = "NO " if expression.args.get("no") else ""
1741        protection = " PROTECTION" if expression.args.get("protection") else ""
1742        return f"{no}FALLBACK{protection}"
1743
1744    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1745        no = "NO " if expression.args.get("no") else ""
1746        local = expression.args.get("local")
1747        local = f"{local} " if local else ""
1748        dual = "DUAL " if expression.args.get("dual") else ""
1749        before = "BEFORE " if expression.args.get("before") else ""
1750        after = "AFTER " if expression.args.get("after") else ""
1751        return f"{no}{local}{dual}{before}{after}JOURNAL"
1752
1753    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1754        freespace = self.sql(expression, "this")
1755        percent = " PERCENT" if expression.args.get("percent") else ""
1756        return f"FREESPACE={freespace}{percent}"
1757
1758    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1759        if expression.args.get("default"):
1760            property = "DEFAULT"
1761        elif expression.args.get("on"):
1762            property = "ON"
1763        else:
1764            property = "OFF"
1765        return f"CHECKSUM={property}"
1766
1767    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1768        if expression.args.get("no"):
1769            return "NO MERGEBLOCKRATIO"
1770        if expression.args.get("default"):
1771            return "DEFAULT MERGEBLOCKRATIO"
1772
1773        percent = " PERCENT" if expression.args.get("percent") else ""
1774        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1775
1776    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1777        default = expression.args.get("default")
1778        minimum = expression.args.get("minimum")
1779        maximum = expression.args.get("maximum")
1780        if default or minimum or maximum:
1781            if default:
1782                prop = "DEFAULT"
1783            elif minimum:
1784                prop = "MINIMUM"
1785            else:
1786                prop = "MAXIMUM"
1787            return f"{prop} DATABLOCKSIZE"
1788        units = expression.args.get("units")
1789        units = f" {units}" if units else ""
1790        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
1791
1792    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1793        autotemp = expression.args.get("autotemp")
1794        always = expression.args.get("always")
1795        default = expression.args.get("default")
1796        manual = expression.args.get("manual")
1797        never = expression.args.get("never")
1798
1799        if autotemp is not None:
1800            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1801        elif always:
1802            prop = "ALWAYS"
1803        elif default:
1804            prop = "DEFAULT"
1805        elif manual:
1806            prop = "MANUAL"
1807        elif never:
1808            prop = "NEVER"
1809        return f"BLOCKCOMPRESSION={prop}"
1810
1811    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1812        no = expression.args.get("no")
1813        no = " NO" if no else ""
1814        concurrent = expression.args.get("concurrent")
1815        concurrent = " CONCURRENT" if concurrent else ""
1816        target = self.sql(expression, "target")
1817        target = f" {target}" if target else ""
1818        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1819
1820    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1821        if isinstance(expression.this, list):
1822            return f"IN ({self.expressions(expression, key='this', flat=True)})"
1823        if expression.this:
1824            modulus = self.sql(expression, "this")
1825            remainder = self.sql(expression, "expression")
1826            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1827
1828        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1829        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1830        return f"FROM ({from_expressions}) TO ({to_expressions})"
1831
1832    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1833        this = self.sql(expression, "this")
1834
1835        for_values_or_default = expression.expression
1836        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1837            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1838        else:
1839            for_values_or_default = " DEFAULT"
1840
1841        return f"PARTITION OF {this}{for_values_or_default}"
1842
1843    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1844        kind = expression.args.get("kind")
1845        this = f" {self.sql(expression, 'this')}" if expression.this else ""
1846        for_or_in = expression.args.get("for_or_in")
1847        for_or_in = f" {for_or_in}" if for_or_in else ""
1848        lock_type = expression.args.get("lock_type")
1849        override = " OVERRIDE" if expression.args.get("override") else ""
1850        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1851
1852    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1853        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1854        statistics = expression.args.get("statistics")
1855        statistics_sql = ""
1856        if statistics is not None:
1857            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1858        return f"{data_sql}{statistics_sql}"
1859
1860    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1861        this = self.sql(expression, "this")
1862        this = f"HISTORY_TABLE={this}" if this else ""
1863        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
1864        data_consistency = (
1865            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
1866        )
1867        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
1868        retention_period = (
1869            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
1870        )
1871
1872        if this:
1873            on_sql = self.func("ON", this, data_consistency, retention_period)
1874        else:
1875            on_sql = "ON" if expression.args.get("on") else "OFF"
1876
1877        sql = f"SYSTEM_VERSIONING={on_sql}"
1878
1879        return f"WITH({sql})" if expression.args.get("with") else sql
1880
1881    def insert_sql(self, expression: exp.Insert) -> str:
1882        hint = self.sql(expression, "hint")
1883        overwrite = expression.args.get("overwrite")
1884
1885        if isinstance(expression.this, exp.Directory):
1886            this = " OVERWRITE" if overwrite else " INTO"
1887        else:
1888            this = self.INSERT_OVERWRITE if overwrite else " INTO"
1889
1890        stored = self.sql(expression, "stored")
1891        stored = f" {stored}" if stored else ""
1892        alternative = expression.args.get("alternative")
1893        alternative = f" OR {alternative}" if alternative else ""
1894        ignore = " IGNORE" if expression.args.get("ignore") else ""
1895        is_function = expression.args.get("is_function")
1896        if is_function:
1897            this = f"{this} FUNCTION"
1898        this = f"{this} {self.sql(expression, 'this')}"
1899
1900        exists = " IF EXISTS" if expression.args.get("exists") else ""
1901        where = self.sql(expression, "where")
1902        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
1903        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
1904        on_conflict = self.sql(expression, "conflict")
1905        on_conflict = f" {on_conflict}" if on_conflict else ""
1906        by_name = " BY NAME" if expression.args.get("by_name") else ""
1907        returning = self.sql(expression, "returning")
1908
1909        if self.RETURNING_END:
1910            expression_sql = f"{expression_sql}{on_conflict}{returning}"
1911        else:
1912            expression_sql = f"{returning}{expression_sql}{on_conflict}"
1913
1914        partition_by = self.sql(expression, "partition")
1915        partition_by = f" {partition_by}" if partition_by else ""
1916        settings = self.sql(expression, "settings")
1917        settings = f" {settings}" if settings else ""
1918
1919        source = self.sql(expression, "source")
1920        source = f"TABLE {source}" if source else ""
1921
1922        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
1923        return self.prepend_ctes(expression, sql)
1924
1925    def introducer_sql(self, expression: exp.Introducer) -> str:
1926        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
1927
1928    def kill_sql(self, expression: exp.Kill) -> str:
1929        kind = self.sql(expression, "kind")
1930        kind = f" {kind}" if kind else ""
1931        this = self.sql(expression, "this")
1932        this = f" {this}" if this else ""
1933        return f"KILL{kind}{this}"
1934
1935    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
1936        return expression.name
1937
1938    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
1939        return expression.name
1940
1941    def onconflict_sql(self, expression: exp.OnConflict) -> str:
1942        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
1943
1944        constraint = self.sql(expression, "constraint")
1945        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
1946
1947        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
1948        conflict_keys = f"({conflict_keys}) " if conflict_keys else " "
1949        action = self.sql(expression, "action")
1950
1951        expressions = self.expressions(expression, flat=True)
1952        if expressions:
1953            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
1954            expressions = f" {set_keyword}{expressions}"
1955
1956        where = self.sql(expression, "where")
1957        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
1958
1959    def returning_sql(self, expression: exp.Returning) -> str:
1960        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
1961
1962    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
1963        fields = self.sql(expression, "fields")
1964        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
1965        escaped = self.sql(expression, "escaped")
1966        escaped = f" ESCAPED BY {escaped}" if escaped else ""
1967        items = self.sql(expression, "collection_items")
1968        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
1969        keys = self.sql(expression, "map_keys")
1970        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
1971        lines = self.sql(expression, "lines")
1972        lines = f" LINES TERMINATED BY {lines}" if lines else ""
1973        null = self.sql(expression, "null")
1974        null = f" NULL DEFINED AS {null}" if null else ""
1975        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
1976
1977    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
1978        return f"WITH ({self.expressions(expression, flat=True)})"
1979
1980    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
1981        this = f"{self.sql(expression, 'this')} INDEX"
1982        target = self.sql(expression, "target")
1983        target = f" FOR {target}" if target else ""
1984        return f"{this}{target} ({self.expressions(expression, flat=True)})"
1985
1986    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
1987        this = self.sql(expression, "this")
1988        kind = self.sql(expression, "kind")
1989        expr = self.sql(expression, "expression")
1990        return f"{this} ({kind} => {expr})"
1991
1992    def table_parts(self, expression: exp.Table) -> str:
1993        return ".".join(
1994            self.sql(part)
1995            for part in (
1996                expression.args.get("catalog"),
1997                expression.args.get("db"),
1998                expression.args.get("this"),
1999            )
2000            if part is not None
2001        )
2002
2003    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2004        table = self.table_parts(expression)
2005        only = "ONLY " if expression.args.get("only") else ""
2006        partition = self.sql(expression, "partition")
2007        partition = f" {partition}" if partition else ""
2008        version = self.sql(expression, "version")
2009        version = f" {version}" if version else ""
2010        alias = self.sql(expression, "alias")
2011        alias = f"{sep}{alias}" if alias else ""
2012
2013        sample = self.sql(expression, "sample")
2014        if self.dialect.ALIAS_POST_TABLESAMPLE:
2015            sample_pre_alias = sample
2016            sample_post_alias = ""
2017        else:
2018            sample_pre_alias = ""
2019            sample_post_alias = sample
2020
2021        hints = self.expressions(expression, key="hints", sep=" ")
2022        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2023        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2024        joins = self.indent(
2025            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2026        )
2027        laterals = self.expressions(expression, key="laterals", sep="")
2028
2029        file_format = self.sql(expression, "format")
2030        if file_format:
2031            pattern = self.sql(expression, "pattern")
2032            pattern = f", PATTERN => {pattern}" if pattern else ""
2033            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2034
2035        ordinality = expression.args.get("ordinality") or ""
2036        if ordinality:
2037            ordinality = f" WITH ORDINALITY{alias}"
2038            alias = ""
2039
2040        when = self.sql(expression, "when")
2041        if when:
2042            table = f"{table} {when}"
2043
2044        changes = self.sql(expression, "changes")
2045        changes = f" {changes}" if changes else ""
2046
2047        rows_from = self.expressions(expression, key="rows_from")
2048        if rows_from:
2049            table = f"ROWS FROM {self.wrap(rows_from)}"
2050
2051        return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2052
2053    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2054        table = self.func("TABLE", expression.this)
2055        alias = self.sql(expression, "alias")
2056        alias = f" AS {alias}" if alias else ""
2057        sample = self.sql(expression, "sample")
2058        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2059        joins = self.indent(
2060            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2061        )
2062        return f"{table}{alias}{pivots}{sample}{joins}"
2063
2064    def tablesample_sql(
2065        self,
2066        expression: exp.TableSample,
2067        tablesample_keyword: t.Optional[str] = None,
2068    ) -> str:
2069        method = self.sql(expression, "method")
2070        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2071        numerator = self.sql(expression, "bucket_numerator")
2072        denominator = self.sql(expression, "bucket_denominator")
2073        field = self.sql(expression, "bucket_field")
2074        field = f" ON {field}" if field else ""
2075        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2076        seed = self.sql(expression, "seed")
2077        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2078
2079        size = self.sql(expression, "size")
2080        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2081            size = f"{size} ROWS"
2082
2083        percent = self.sql(expression, "percent")
2084        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2085            percent = f"{percent} PERCENT"
2086
2087        expr = f"{bucket}{percent}{size}"
2088        if self.TABLESAMPLE_REQUIRES_PARENS:
2089            expr = f"({expr})"
2090
2091        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2092
2093    def pivot_sql(self, expression: exp.Pivot) -> str:
2094        expressions = self.expressions(expression, flat=True)
2095        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2096
2097        group = self.sql(expression, "group")
2098
2099        if expression.this:
2100            this = self.sql(expression, "this")
2101            if not expressions:
2102                return f"UNPIVOT {this}"
2103
2104            on = f"{self.seg('ON')} {expressions}"
2105            into = self.sql(expression, "into")
2106            into = f"{self.seg('INTO')} {into}" if into else ""
2107            using = self.expressions(expression, key="using", flat=True)
2108            using = f"{self.seg('USING')} {using}" if using else ""
2109            return f"{direction} {this}{on}{into}{using}{group}"
2110
2111        alias = self.sql(expression, "alias")
2112        alias = f" AS {alias}" if alias else ""
2113
2114        fields = self.expressions(
2115            expression,
2116            "fields",
2117            sep=" ",
2118            dynamic=True,
2119            new_line=True,
2120            skip_first=True,
2121            skip_last=True,
2122        )
2123
2124        include_nulls = expression.args.get("include_nulls")
2125        if include_nulls is not None:
2126            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2127        else:
2128            nulls = ""
2129
2130        default_on_null = self.sql(expression, "default_on_null")
2131        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2132        return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2133
2134    def version_sql(self, expression: exp.Version) -> str:
2135        this = f"FOR {expression.name}"
2136        kind = expression.text("kind")
2137        expr = self.sql(expression, "expression")
2138        return f"{this} {kind} {expr}"
2139
2140    def tuple_sql(self, expression: exp.Tuple) -> str:
2141        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2142
2143    def update_sql(self, expression: exp.Update) -> str:
2144        this = self.sql(expression, "this")
2145        set_sql = self.expressions(expression, flat=True)
2146        from_sql = self.sql(expression, "from")
2147        where_sql = self.sql(expression, "where")
2148        returning = self.sql(expression, "returning")
2149        order = self.sql(expression, "order")
2150        limit = self.sql(expression, "limit")
2151        if self.RETURNING_END:
2152            expression_sql = f"{from_sql}{where_sql}{returning}"
2153        else:
2154            expression_sql = f"{returning}{from_sql}{where_sql}"
2155        sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}"
2156        return self.prepend_ctes(expression, sql)
2157
2158    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2159        values_as_table = values_as_table and self.VALUES_AS_TABLE
2160
2161        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2162        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2163            args = self.expressions(expression)
2164            alias = self.sql(expression, "alias")
2165            values = f"VALUES{self.seg('')}{args}"
2166            values = (
2167                f"({values})"
2168                if self.WRAP_DERIVED_VALUES
2169                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2170                else values
2171            )
2172            return f"{values} AS {alias}" if alias else values
2173
2174        # Converts `VALUES...` expression into a series of select unions.
2175        alias_node = expression.args.get("alias")
2176        column_names = alias_node and alias_node.columns
2177
2178        selects: t.List[exp.Query] = []
2179
2180        for i, tup in enumerate(expression.expressions):
2181            row = tup.expressions
2182
2183            if i == 0 and column_names:
2184                row = [
2185                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2186                ]
2187
2188            selects.append(exp.Select(expressions=row))
2189
2190        if self.pretty:
2191            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2192            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2193            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2194            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2195            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2196
2197        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2198        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2199        return f"({unions}){alias}"
2200
2201    def var_sql(self, expression: exp.Var) -> str:
2202        return self.sql(expression, "this")
2203
2204    @unsupported_args("expressions")
2205    def into_sql(self, expression: exp.Into) -> str:
2206        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2207        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2208        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2209
2210    def from_sql(self, expression: exp.From) -> str:
2211        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2212
2213    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2214        grouping_sets = self.expressions(expression, indent=False)
2215        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2216
2217    def rollup_sql(self, expression: exp.Rollup) -> str:
2218        expressions = self.expressions(expression, indent=False)
2219        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2220
2221    def cube_sql(self, expression: exp.Cube) -> str:
2222        expressions = self.expressions(expression, indent=False)
2223        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2224
2225    def group_sql(self, expression: exp.Group) -> str:
2226        group_by_all = expression.args.get("all")
2227        if group_by_all is True:
2228            modifier = " ALL"
2229        elif group_by_all is False:
2230            modifier = " DISTINCT"
2231        else:
2232            modifier = ""
2233
2234        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2235
2236        grouping_sets = self.expressions(expression, key="grouping_sets")
2237        cube = self.expressions(expression, key="cube")
2238        rollup = self.expressions(expression, key="rollup")
2239
2240        groupings = csv(
2241            self.seg(grouping_sets) if grouping_sets else "",
2242            self.seg(cube) if cube else "",
2243            self.seg(rollup) if rollup else "",
2244            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2245            sep=self.GROUPINGS_SEP,
2246        )
2247
2248        if (
2249            expression.expressions
2250            and groupings
2251            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2252        ):
2253            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2254
2255        return f"{group_by}{groupings}"
2256
2257    def having_sql(self, expression: exp.Having) -> str:
2258        this = self.indent(self.sql(expression, "this"))
2259        return f"{self.seg('HAVING')}{self.sep()}{this}"
2260
2261    def connect_sql(self, expression: exp.Connect) -> str:
2262        start = self.sql(expression, "start")
2263        start = self.seg(f"START WITH {start}") if start else ""
2264        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2265        connect = self.sql(expression, "connect")
2266        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2267        return start + connect
2268
2269    def prior_sql(self, expression: exp.Prior) -> str:
2270        return f"PRIOR {self.sql(expression, 'this')}"
2271
2272    def join_sql(self, expression: exp.Join) -> str:
2273        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2274            side = None
2275        else:
2276            side = expression.side
2277
2278        op_sql = " ".join(
2279            op
2280            for op in (
2281                expression.method,
2282                "GLOBAL" if expression.args.get("global") else None,
2283                side,
2284                expression.kind,
2285                expression.hint if self.JOIN_HINTS else None,
2286            )
2287            if op
2288        )
2289        match_cond = self.sql(expression, "match_condition")
2290        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2291        on_sql = self.sql(expression, "on")
2292        using = expression.args.get("using")
2293
2294        if not on_sql and using:
2295            on_sql = csv(*(self.sql(column) for column in using))
2296
2297        this = expression.this
2298        this_sql = self.sql(this)
2299
2300        exprs = self.expressions(expression)
2301        if exprs:
2302            this_sql = f"{this_sql},{self.seg(exprs)}"
2303
2304        if on_sql:
2305            on_sql = self.indent(on_sql, skip_first=True)
2306            space = self.seg(" " * self.pad) if self.pretty else " "
2307            if using:
2308                on_sql = f"{space}USING ({on_sql})"
2309            else:
2310                on_sql = f"{space}ON {on_sql}"
2311        elif not op_sql:
2312            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2313                return f" {this_sql}"
2314
2315            return f", {this_sql}"
2316
2317        if op_sql != "STRAIGHT_JOIN":
2318            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2319
2320        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2321        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2322
2323    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2324        args = self.expressions(expression, flat=True)
2325        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2326        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2327
2328    def lateral_op(self, expression: exp.Lateral) -> str:
2329        cross_apply = expression.args.get("cross_apply")
2330
2331        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2332        if cross_apply is True:
2333            op = "INNER JOIN "
2334        elif cross_apply is False:
2335            op = "LEFT JOIN "
2336        else:
2337            op = ""
2338
2339        return f"{op}LATERAL"
2340
2341    def lateral_sql(self, expression: exp.Lateral) -> str:
2342        this = self.sql(expression, "this")
2343
2344        if expression.args.get("view"):
2345            alias = expression.args["alias"]
2346            columns = self.expressions(alias, key="columns", flat=True)
2347            table = f" {alias.name}" if alias.name else ""
2348            columns = f" AS {columns}" if columns else ""
2349            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2350            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2351
2352        alias = self.sql(expression, "alias")
2353        alias = f" AS {alias}" if alias else ""
2354
2355        ordinality = expression.args.get("ordinality") or ""
2356        if ordinality:
2357            ordinality = f" WITH ORDINALITY{alias}"
2358            alias = ""
2359
2360        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2361
2362    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2363        this = self.sql(expression, "this")
2364
2365        args = [
2366            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2367            for e in (expression.args.get(k) for k in ("offset", "expression"))
2368            if e
2369        ]
2370
2371        args_sql = ", ".join(self.sql(e) for e in args)
2372        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2373        expressions = self.expressions(expression, flat=True)
2374        limit_options = self.sql(expression, "limit_options")
2375        expressions = f" BY {expressions}" if expressions else ""
2376
2377        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2378
2379    def offset_sql(self, expression: exp.Offset) -> str:
2380        this = self.sql(expression, "this")
2381        value = expression.expression
2382        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2383        expressions = self.expressions(expression, flat=True)
2384        expressions = f" BY {expressions}" if expressions else ""
2385        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2386
2387    def setitem_sql(self, expression: exp.SetItem) -> str:
2388        kind = self.sql(expression, "kind")
2389        kind = f"{kind} " if kind else ""
2390        this = self.sql(expression, "this")
2391        expressions = self.expressions(expression)
2392        collate = self.sql(expression, "collate")
2393        collate = f" COLLATE {collate}" if collate else ""
2394        global_ = "GLOBAL " if expression.args.get("global") else ""
2395        return f"{global_}{kind}{this}{expressions}{collate}"
2396
2397    def set_sql(self, expression: exp.Set) -> str:
2398        expressions = f" {self.expressions(expression, flat=True)}"
2399        tag = " TAG" if expression.args.get("tag") else ""
2400        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2401
2402    def pragma_sql(self, expression: exp.Pragma) -> str:
2403        return f"PRAGMA {self.sql(expression, 'this')}"
2404
2405    def lock_sql(self, expression: exp.Lock) -> str:
2406        if not self.LOCKING_READS_SUPPORTED:
2407            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2408            return ""
2409
2410        lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE"
2411        expressions = self.expressions(expression, flat=True)
2412        expressions = f" OF {expressions}" if expressions else ""
2413        wait = expression.args.get("wait")
2414
2415        if wait is not None:
2416            if isinstance(wait, exp.Literal):
2417                wait = f" WAIT {self.sql(wait)}"
2418            else:
2419                wait = " NOWAIT" if wait else " SKIP LOCKED"
2420
2421        return f"{lock_type}{expressions}{wait or ''}"
2422
2423    def literal_sql(self, expression: exp.Literal) -> str:
2424        text = expression.this or ""
2425        if expression.is_string:
2426            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2427        return text
2428
2429    def escape_str(self, text: str, escape_backslash: bool = True) -> str:
2430        if self.dialect.ESCAPED_SEQUENCES:
2431            to_escaped = self.dialect.ESCAPED_SEQUENCES
2432            text = "".join(
2433                to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text
2434            )
2435
2436        return self._replace_line_breaks(text).replace(
2437            self.dialect.QUOTE_END, self._escaped_quote_end
2438        )
2439
2440    def loaddata_sql(self, expression: exp.LoadData) -> str:
2441        local = " LOCAL" if expression.args.get("local") else ""
2442        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2443        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2444        this = f" INTO TABLE {self.sql(expression, 'this')}"
2445        partition = self.sql(expression, "partition")
2446        partition = f" {partition}" if partition else ""
2447        input_format = self.sql(expression, "input_format")
2448        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2449        serde = self.sql(expression, "serde")
2450        serde = f" SERDE {serde}" if serde else ""
2451        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2452
2453    def null_sql(self, *_) -> str:
2454        return "NULL"
2455
2456    def boolean_sql(self, expression: exp.Boolean) -> str:
2457        return "TRUE" if expression.this else "FALSE"
2458
2459    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2460        this = self.sql(expression, "this")
2461        this = f"{this} " if this else this
2462        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2463        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
2464
2465    def withfill_sql(self, expression: exp.WithFill) -> str:
2466        from_sql = self.sql(expression, "from")
2467        from_sql = f" FROM {from_sql}" if from_sql else ""
2468        to_sql = self.sql(expression, "to")
2469        to_sql = f" TO {to_sql}" if to_sql else ""
2470        step_sql = self.sql(expression, "step")
2471        step_sql = f" STEP {step_sql}" if step_sql else ""
2472        interpolated_values = [
2473            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2474            if isinstance(e, exp.Alias)
2475            else self.sql(e, "this")
2476            for e in expression.args.get("interpolate") or []
2477        ]
2478        interpolate = (
2479            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2480        )
2481        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2482
2483    def cluster_sql(self, expression: exp.Cluster) -> str:
2484        return self.op_expressions("CLUSTER BY", expression)
2485
2486    def distribute_sql(self, expression: exp.Distribute) -> str:
2487        return self.op_expressions("DISTRIBUTE BY", expression)
2488
2489    def sort_sql(self, expression: exp.Sort) -> str:
2490        return self.op_expressions("SORT BY", expression)
2491
2492    def ordered_sql(self, expression: exp.Ordered) -> str:
2493        desc = expression.args.get("desc")
2494        asc = not desc
2495
2496        nulls_first = expression.args.get("nulls_first")
2497        nulls_last = not nulls_first
2498        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2499        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2500        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2501
2502        this = self.sql(expression, "this")
2503
2504        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2505        nulls_sort_change = ""
2506        if nulls_first and (
2507            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2508        ):
2509            nulls_sort_change = " NULLS FIRST"
2510        elif (
2511            nulls_last
2512            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2513            and not nulls_are_last
2514        ):
2515            nulls_sort_change = " NULLS LAST"
2516
2517        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2518        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2519            window = expression.find_ancestor(exp.Window, exp.Select)
2520            if isinstance(window, exp.Window) and window.args.get("spec"):
2521                self.unsupported(
2522                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2523                )
2524                nulls_sort_change = ""
2525            elif self.NULL_ORDERING_SUPPORTED is False and (
2526                (asc and nulls_sort_change == " NULLS LAST")
2527                or (desc and nulls_sort_change == " NULLS FIRST")
2528            ):
2529                # BigQuery does not allow these ordering/nulls combinations when used under
2530                # an aggregation func or under a window containing one
2531                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2532
2533                if isinstance(ancestor, exp.Window):
2534                    ancestor = ancestor.this
2535                if isinstance(ancestor, exp.AggFunc):
2536                    self.unsupported(
2537                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2538                    )
2539                    nulls_sort_change = ""
2540            elif self.NULL_ORDERING_SUPPORTED is None:
2541                if expression.this.is_int:
2542                    self.unsupported(
2543                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2544                    )
2545                elif not isinstance(expression.this, exp.Rand):
2546                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2547                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2548                nulls_sort_change = ""
2549
2550        with_fill = self.sql(expression, "with_fill")
2551        with_fill = f" {with_fill}" if with_fill else ""
2552
2553        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2554
2555    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2556        window_frame = self.sql(expression, "window_frame")
2557        window_frame = f"{window_frame} " if window_frame else ""
2558
2559        this = self.sql(expression, "this")
2560
2561        return f"{window_frame}{this}"
2562
2563    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2564        partition = self.partition_by_sql(expression)
2565        order = self.sql(expression, "order")
2566        measures = self.expressions(expression, key="measures")
2567        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2568        rows = self.sql(expression, "rows")
2569        rows = self.seg(rows) if rows else ""
2570        after = self.sql(expression, "after")
2571        after = self.seg(after) if after else ""
2572        pattern = self.sql(expression, "pattern")
2573        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2574        definition_sqls = [
2575            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2576            for definition in expression.args.get("define", [])
2577        ]
2578        definitions = self.expressions(sqls=definition_sqls)
2579        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2580        body = "".join(
2581            (
2582                partition,
2583                order,
2584                measures,
2585                rows,
2586                after,
2587                pattern,
2588                define,
2589            )
2590        )
2591        alias = self.sql(expression, "alias")
2592        alias = f" {alias}" if alias else ""
2593        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2594
2595    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2596        limit = expression.args.get("limit")
2597
2598        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2599            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2600        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2601            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2602
2603        return csv(
2604            *sqls,
2605            *[self.sql(join) for join in expression.args.get("joins") or []],
2606            self.sql(expression, "match"),
2607            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2608            self.sql(expression, "prewhere"),
2609            self.sql(expression, "where"),
2610            self.sql(expression, "connect"),
2611            self.sql(expression, "group"),
2612            self.sql(expression, "having"),
2613            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2614            self.sql(expression, "order"),
2615            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2616            *self.after_limit_modifiers(expression),
2617            self.options_modifier(expression),
2618            self.for_modifiers(expression),
2619            sep="",
2620        )
2621
2622    def options_modifier(self, expression: exp.Expression) -> str:
2623        options = self.expressions(expression, key="options")
2624        return f" {options}" if options else ""
2625
2626    def for_modifiers(self, expression: exp.Expression) -> str:
2627        for_modifiers = self.expressions(expression, key="for")
2628        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
2629
2630    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2631        self.unsupported("Unsupported query option.")
2632        return ""
2633
2634    def offset_limit_modifiers(
2635        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2636    ) -> t.List[str]:
2637        return [
2638            self.sql(expression, "offset") if fetch else self.sql(limit),
2639            self.sql(limit) if fetch else self.sql(expression, "offset"),
2640        ]
2641
2642    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2643        locks = self.expressions(expression, key="locks", sep=" ")
2644        locks = f" {locks}" if locks else ""
2645        return [locks, self.sql(expression, "sample")]
2646
2647    def select_sql(self, expression: exp.Select) -> str:
2648        into = expression.args.get("into")
2649        if not self.SUPPORTS_SELECT_INTO and into:
2650            into.pop()
2651
2652        hint = self.sql(expression, "hint")
2653        distinct = self.sql(expression, "distinct")
2654        distinct = f" {distinct}" if distinct else ""
2655        kind = self.sql(expression, "kind")
2656
2657        limit = expression.args.get("limit")
2658        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2659            top = self.limit_sql(limit, top=True)
2660            limit.pop()
2661        else:
2662            top = ""
2663
2664        expressions = self.expressions(expression)
2665
2666        if kind:
2667            if kind in self.SELECT_KINDS:
2668                kind = f" AS {kind}"
2669            else:
2670                if kind == "STRUCT":
2671                    expressions = self.expressions(
2672                        sqls=[
2673                            self.sql(
2674                                exp.Struct(
2675                                    expressions=[
2676                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2677                                        if isinstance(e, exp.Alias)
2678                                        else e
2679                                        for e in expression.expressions
2680                                    ]
2681                                )
2682                            )
2683                        ]
2684                    )
2685                kind = ""
2686
2687        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2688        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2689
2690        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2691        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2692        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2693        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2694        sql = self.query_modifiers(
2695            expression,
2696            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2697            self.sql(expression, "into", comment=False),
2698            self.sql(expression, "from", comment=False),
2699        )
2700
2701        # If both the CTE and SELECT clauses have comments, generate the latter earlier
2702        if expression.args.get("with"):
2703            sql = self.maybe_comment(sql, expression)
2704            expression.pop_comments()
2705
2706        sql = self.prepend_ctes(expression, sql)
2707
2708        if not self.SUPPORTS_SELECT_INTO and into:
2709            if into.args.get("temporary"):
2710                table_kind = " TEMPORARY"
2711            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
2712                table_kind = " UNLOGGED"
2713            else:
2714                table_kind = ""
2715            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
2716
2717        return sql
2718
2719    def schema_sql(self, expression: exp.Schema) -> str:
2720        this = self.sql(expression, "this")
2721        sql = self.schema_columns_sql(expression)
2722        return f"{this} {sql}" if this and sql else this or sql
2723
2724    def schema_columns_sql(self, expression: exp.Schema) -> str:
2725        if expression.expressions:
2726            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2727        return ""
2728
2729    def star_sql(self, expression: exp.Star) -> str:
2730        except_ = self.expressions(expression, key="except", flat=True)
2731        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2732        replace = self.expressions(expression, key="replace", flat=True)
2733        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
2734        rename = self.expressions(expression, key="rename", flat=True)
2735        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
2736        return f"*{except_}{replace}{rename}"
2737
2738    def parameter_sql(self, expression: exp.Parameter) -> str:
2739        this = self.sql(expression, "this")
2740        return f"{self.PARAMETER_TOKEN}{this}"
2741
2742    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2743        this = self.sql(expression, "this")
2744        kind = expression.text("kind")
2745        if kind:
2746            kind = f"{kind}."
2747        return f"@@{kind}{this}"
2748
2749    def placeholder_sql(self, expression: exp.Placeholder) -> str:
2750        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
2751
2752    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2753        alias = self.sql(expression, "alias")
2754        alias = f"{sep}{alias}" if alias else ""
2755        sample = self.sql(expression, "sample")
2756        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
2757            alias = f"{sample}{alias}"
2758
2759            # Set to None so it's not generated again by self.query_modifiers()
2760            expression.set("sample", None)
2761
2762        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2763        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2764        return self.prepend_ctes(expression, sql)
2765
2766    def qualify_sql(self, expression: exp.Qualify) -> str:
2767        this = self.indent(self.sql(expression, "this"))
2768        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
2769
2770    def unnest_sql(self, expression: exp.Unnest) -> str:
2771        args = self.expressions(expression, flat=True)
2772
2773        alias = expression.args.get("alias")
2774        offset = expression.args.get("offset")
2775
2776        if self.UNNEST_WITH_ORDINALITY:
2777            if alias and isinstance(offset, exp.Expression):
2778                alias.append("columns", offset)
2779
2780        if alias and self.dialect.UNNEST_COLUMN_ONLY:
2781            columns = alias.columns
2782            alias = self.sql(columns[0]) if columns else ""
2783        else:
2784            alias = self.sql(alias)
2785
2786        alias = f" AS {alias}" if alias else alias
2787        if self.UNNEST_WITH_ORDINALITY:
2788            suffix = f" WITH ORDINALITY{alias}" if offset else alias
2789        else:
2790            if isinstance(offset, exp.Expression):
2791                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
2792            elif offset:
2793                suffix = f"{alias} WITH OFFSET"
2794            else:
2795                suffix = alias
2796
2797        return f"UNNEST({args}){suffix}"
2798
2799    def prewhere_sql(self, expression: exp.PreWhere) -> str:
2800        return ""
2801
2802    def where_sql(self, expression: exp.Where) -> str:
2803        this = self.indent(self.sql(expression, "this"))
2804        return f"{self.seg('WHERE')}{self.sep()}{this}"
2805
2806    def window_sql(self, expression: exp.Window) -> str:
2807        this = self.sql(expression, "this")
2808        partition = self.partition_by_sql(expression)
2809        order = expression.args.get("order")
2810        order = self.order_sql(order, flat=True) if order else ""
2811        spec = self.sql(expression, "spec")
2812        alias = self.sql(expression, "alias")
2813        over = self.sql(expression, "over") or "OVER"
2814
2815        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
2816
2817        first = expression.args.get("first")
2818        if first is None:
2819            first = ""
2820        else:
2821            first = "FIRST" if first else "LAST"
2822
2823        if not partition and not order and not spec and alias:
2824            return f"{this} {alias}"
2825
2826        args = self.format_args(
2827            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
2828        )
2829        return f"{this} ({args})"
2830
2831    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
2832        partition = self.expressions(expression, key="partition_by", flat=True)
2833        return f"PARTITION BY {partition}" if partition else ""
2834
2835    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
2836        kind = self.sql(expression, "kind")
2837        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
2838        end = (
2839            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
2840            or "CURRENT ROW"
2841        )
2842
2843        window_spec = f"{kind} BETWEEN {start} AND {end}"
2844
2845        exclude = self.sql(expression, "exclude")
2846        if exclude:
2847            if self.SUPPORTS_WINDOW_EXCLUDE:
2848                window_spec += f" EXCLUDE {exclude}"
2849            else:
2850                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
2851
2852        return window_spec
2853
2854    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
2855        this = self.sql(expression, "this")
2856        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
2857        return f"{this} WITHIN GROUP ({expression_sql})"
2858
2859    def between_sql(self, expression: exp.Between) -> str:
2860        this = self.sql(expression, "this")
2861        low = self.sql(expression, "low")
2862        high = self.sql(expression, "high")
2863        return f"{this} BETWEEN {low} AND {high}"
2864
2865    def bracket_offset_expressions(
2866        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
2867    ) -> t.List[exp.Expression]:
2868        return apply_index_offset(
2869            expression.this,
2870            expression.expressions,
2871            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
2872            dialect=self.dialect,
2873        )
2874
2875    def bracket_sql(self, expression: exp.Bracket) -> str:
2876        expressions = self.bracket_offset_expressions(expression)
2877        expressions_sql = ", ".join(self.sql(e) for e in expressions)
2878        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
2879
2880    def all_sql(self, expression: exp.All) -> str:
2881        return f"ALL {self.wrap(expression)}"
2882
2883    def any_sql(self, expression: exp.Any) -> str:
2884        this = self.sql(expression, "this")
2885        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
2886            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
2887                this = self.wrap(this)
2888            return f"ANY{this}"
2889        return f"ANY {this}"
2890
2891    def exists_sql(self, expression: exp.Exists) -> str:
2892        return f"EXISTS{self.wrap(expression)}"
2893
2894    def case_sql(self, expression: exp.Case) -> str:
2895        this = self.sql(expression, "this")
2896        statements = [f"CASE {this}" if this else "CASE"]
2897
2898        for e in expression.args["ifs"]:
2899            statements.append(f"WHEN {self.sql(e, 'this')}")
2900            statements.append(f"THEN {self.sql(e, 'true')}")
2901
2902        default = self.sql(expression, "default")
2903
2904        if default:
2905            statements.append(f"ELSE {default}")
2906
2907        statements.append("END")
2908
2909        if self.pretty and self.too_wide(statements):
2910            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
2911
2912        return " ".join(statements)
2913
2914    def constraint_sql(self, expression: exp.Constraint) -> str:
2915        this = self.sql(expression, "this")
2916        expressions = self.expressions(expression, flat=True)
2917        return f"CONSTRAINT {this} {expressions}"
2918
2919    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
2920        order = expression.args.get("order")
2921        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
2922        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
2923
2924    def extract_sql(self, expression: exp.Extract) -> str:
2925        from sqlglot.dialects.dialect import map_date_part
2926
2927        this = (
2928            map_date_part(expression.this, self.dialect)
2929            if self.NORMALIZE_EXTRACT_DATE_PARTS
2930            else expression.this
2931        )
2932        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
2933        expression_sql = self.sql(expression, "expression")
2934
2935        return f"EXTRACT({this_sql} FROM {expression_sql})"
2936
2937    def trim_sql(self, expression: exp.Trim) -> str:
2938        trim_type = self.sql(expression, "position")
2939
2940        if trim_type == "LEADING":
2941            func_name = "LTRIM"
2942        elif trim_type == "TRAILING":
2943            func_name = "RTRIM"
2944        else:
2945            func_name = "TRIM"
2946
2947        return self.func(func_name, expression.this, expression.expression)
2948
2949    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
2950        args = expression.expressions
2951        if isinstance(expression, exp.ConcatWs):
2952            args = args[1:]  # Skip the delimiter
2953
2954        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
2955            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
2956
2957        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
2958            args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args]
2959
2960        return args
2961
2962    def concat_sql(self, expression: exp.Concat) -> str:
2963        expressions = self.convert_concat_args(expression)
2964
2965        # Some dialects don't allow a single-argument CONCAT call
2966        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
2967            return self.sql(expressions[0])
2968
2969        return self.func("CONCAT", *expressions)
2970
2971    def concatws_sql(self, expression: exp.ConcatWs) -> str:
2972        return self.func(
2973            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
2974        )
2975
2976    def check_sql(self, expression: exp.Check) -> str:
2977        this = self.sql(expression, key="this")
2978        return f"CHECK ({this})"
2979
2980    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
2981        expressions = self.expressions(expression, flat=True)
2982        expressions = f" ({expressions})" if expressions else ""
2983        reference = self.sql(expression, "reference")
2984        reference = f" {reference}" if reference else ""
2985        delete = self.sql(expression, "delete")
2986        delete = f" ON DELETE {delete}" if delete else ""
2987        update = self.sql(expression, "update")
2988        update = f" ON UPDATE {update}" if update else ""
2989        options = self.expressions(expression, key="options", flat=True, sep=" ")
2990        options = f" {options}" if options else ""
2991        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
2992
2993    def primarykey_sql(self, expression: exp.ForeignKey) -> str:
2994        expressions = self.expressions(expression, flat=True)
2995        options = self.expressions(expression, key="options", flat=True, sep=" ")
2996        options = f" {options}" if options else ""
2997        return f"PRIMARY KEY ({expressions}){options}"
2998
2999    def if_sql(self, expression: exp.If) -> str:
3000        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3001
3002    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3003        modifier = expression.args.get("modifier")
3004        modifier = f" {modifier}" if modifier else ""
3005        return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3006
3007    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3008        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3009
3010    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3011        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3012
3013        if expression.args.get("escape"):
3014            path = self.escape_str(path)
3015
3016        if self.QUOTE_JSON_PATH:
3017            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3018
3019        return path
3020
3021    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3022        if isinstance(expression, exp.JSONPathPart):
3023            transform = self.TRANSFORMS.get(expression.__class__)
3024            if not callable(transform):
3025                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3026                return ""
3027
3028            return transform(self, expression)
3029
3030        if isinstance(expression, int):
3031            return str(expression)
3032
3033        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3034            escaped = expression.replace("'", "\\'")
3035            escaped = f"\\'{expression}\\'"
3036        else:
3037            escaped = expression.replace('"', '\\"')
3038            escaped = f'"{escaped}"'
3039
3040        return escaped
3041
3042    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3043        return f"{self.sql(expression, 'this')} FORMAT JSON"
3044
3045    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3046        # Output the Teradata column FORMAT override.
3047        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3048        this = self.sql(expression, "this")
3049        fmt = self.sql(expression, "format")
3050        return f"{this} (FORMAT {fmt})"
3051
3052    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3053        null_handling = expression.args.get("null_handling")
3054        null_handling = f" {null_handling}" if null_handling else ""
3055
3056        unique_keys = expression.args.get("unique_keys")
3057        if unique_keys is not None:
3058            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3059        else:
3060            unique_keys = ""
3061
3062        return_type = self.sql(expression, "return_type")
3063        return_type = f" RETURNING {return_type}" if return_type else ""
3064        encoding = self.sql(expression, "encoding")
3065        encoding = f" ENCODING {encoding}" if encoding else ""
3066
3067        return self.func(
3068            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3069            *expression.expressions,
3070            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3071        )
3072
3073    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3074        return self.jsonobject_sql(expression)
3075
3076    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3077        null_handling = expression.args.get("null_handling")
3078        null_handling = f" {null_handling}" if null_handling else ""
3079        return_type = self.sql(expression, "return_type")
3080        return_type = f" RETURNING {return_type}" if return_type else ""
3081        strict = " STRICT" if expression.args.get("strict") else ""
3082        return self.func(
3083            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3084        )
3085
3086    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3087        this = self.sql(expression, "this")
3088        order = self.sql(expression, "order")
3089        null_handling = expression.args.get("null_handling")
3090        null_handling = f" {null_handling}" if null_handling else ""
3091        return_type = self.sql(expression, "return_type")
3092        return_type = f" RETURNING {return_type}" if return_type else ""
3093        strict = " STRICT" if expression.args.get("strict") else ""
3094        return self.func(
3095            "JSON_ARRAYAGG",
3096            this,
3097            suffix=f"{order}{null_handling}{return_type}{strict})",
3098        )
3099
3100    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3101        path = self.sql(expression, "path")
3102        path = f" PATH {path}" if path else ""
3103        nested_schema = self.sql(expression, "nested_schema")
3104
3105        if nested_schema:
3106            return f"NESTED{path} {nested_schema}"
3107
3108        this = self.sql(expression, "this")
3109        kind = self.sql(expression, "kind")
3110        kind = f" {kind}" if kind else ""
3111        return f"{this}{kind}{path}"
3112
3113    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3114        return self.func("COLUMNS", *expression.expressions)
3115
3116    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3117        this = self.sql(expression, "this")
3118        path = self.sql(expression, "path")
3119        path = f", {path}" if path else ""
3120        error_handling = expression.args.get("error_handling")
3121        error_handling = f" {error_handling}" if error_handling else ""
3122        empty_handling = expression.args.get("empty_handling")
3123        empty_handling = f" {empty_handling}" if empty_handling else ""
3124        schema = self.sql(expression, "schema")
3125        return self.func(
3126            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3127        )
3128
3129    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3130        this = self.sql(expression, "this")
3131        kind = self.sql(expression, "kind")
3132        path = self.sql(expression, "path")
3133        path = f" {path}" if path else ""
3134        as_json = " AS JSON" if expression.args.get("as_json") else ""
3135        return f"{this} {kind}{path}{as_json}"
3136
3137    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3138        this = self.sql(expression, "this")
3139        path = self.sql(expression, "path")
3140        path = f", {path}" if path else ""
3141        expressions = self.expressions(expression)
3142        with_ = (
3143            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3144            if expressions
3145            else ""
3146        )
3147        return f"OPENJSON({this}{path}){with_}"
3148
3149    def in_sql(self, expression: exp.In) -> str:
3150        query = expression.args.get("query")
3151        unnest = expression.args.get("unnest")
3152        field = expression.args.get("field")
3153        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3154
3155        if query:
3156            in_sql = self.sql(query)
3157        elif unnest:
3158            in_sql = self.in_unnest_op(unnest)
3159        elif field:
3160            in_sql = self.sql(field)
3161        else:
3162            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3163
3164        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3165
3166    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3167        return f"(SELECT {self.sql(unnest)})"
3168
3169    def interval_sql(self, expression: exp.Interval) -> str:
3170        unit = self.sql(expression, "unit")
3171        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3172            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3173        unit = f" {unit}" if unit else ""
3174
3175        if self.SINGLE_STRING_INTERVAL:
3176            this = expression.this.name if expression.this else ""
3177            return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}"
3178
3179        this = self.sql(expression, "this")
3180        if this:
3181            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3182            this = f" {this}" if unwrapped else f" ({this})"
3183
3184        return f"INTERVAL{this}{unit}"
3185
3186    def return_sql(self, expression: exp.Return) -> str:
3187        return f"RETURN {self.sql(expression, 'this')}"
3188
3189    def reference_sql(self, expression: exp.Reference) -> str:
3190        this = self.sql(expression, "this")
3191        expressions = self.expressions(expression, flat=True)
3192        expressions = f"({expressions})" if expressions else ""
3193        options = self.expressions(expression, key="options", flat=True, sep=" ")
3194        options = f" {options}" if options else ""
3195        return f"REFERENCES {this}{expressions}{options}"
3196
3197    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3198        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3199        parent = expression.parent
3200        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3201        return self.func(
3202            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3203        )
3204
3205    def paren_sql(self, expression: exp.Paren) -> str:
3206        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3207        return f"({sql}{self.seg(')', sep='')}"
3208
3209    def neg_sql(self, expression: exp.Neg) -> str:
3210        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3211        this_sql = self.sql(expression, "this")
3212        sep = " " if this_sql[0] == "-" else ""
3213        return f"-{sep}{this_sql}"
3214
3215    def not_sql(self, expression: exp.Not) -> str:
3216        return f"NOT {self.sql(expression, 'this')}"
3217
3218    def alias_sql(self, expression: exp.Alias) -> str:
3219        alias = self.sql(expression, "alias")
3220        alias = f" AS {alias}" if alias else ""
3221        return f"{self.sql(expression, 'this')}{alias}"
3222
3223    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3224        alias = expression.args["alias"]
3225
3226        parent = expression.parent
3227        pivot = parent and parent.parent
3228
3229        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3230            identifier_alias = isinstance(alias, exp.Identifier)
3231            literal_alias = isinstance(alias, exp.Literal)
3232
3233            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3234                alias.replace(exp.Literal.string(alias.output_name))
3235            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3236                alias.replace(exp.to_identifier(alias.output_name))
3237
3238        return self.alias_sql(expression)
3239
3240    def aliases_sql(self, expression: exp.Aliases) -> str:
3241        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3242
3243    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3244        this = self.sql(expression, "this")
3245        index = self.sql(expression, "expression")
3246        return f"{this} AT {index}"
3247
3248    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3249        this = self.sql(expression, "this")
3250        zone = self.sql(expression, "zone")
3251        return f"{this} AT TIME ZONE {zone}"
3252
3253    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3254        this = self.sql(expression, "this")
3255        zone = self.sql(expression, "zone")
3256        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3257
3258    def add_sql(self, expression: exp.Add) -> str:
3259        return self.binary(expression, "+")
3260
3261    def and_sql(
3262        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3263    ) -> str:
3264        return self.connector_sql(expression, "AND", stack)
3265
3266    def or_sql(
3267        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3268    ) -> str:
3269        return self.connector_sql(expression, "OR", stack)
3270
3271    def xor_sql(
3272        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3273    ) -> str:
3274        return self.connector_sql(expression, "XOR", stack)
3275
3276    def connector_sql(
3277        self,
3278        expression: exp.Connector,
3279        op: str,
3280        stack: t.Optional[t.List[str | exp.Expression]] = None,
3281    ) -> str:
3282        if stack is not None:
3283            if expression.expressions:
3284                stack.append(self.expressions(expression, sep=f" {op} "))
3285            else:
3286                stack.append(expression.right)
3287                if expression.comments and self.comments:
3288                    for comment in expression.comments:
3289                        if comment:
3290                            op += f" /*{self.sanitize_comment(comment)}*/"
3291                stack.extend((op, expression.left))
3292            return op
3293
3294        stack = [expression]
3295        sqls: t.List[str] = []
3296        ops = set()
3297
3298        while stack:
3299            node = stack.pop()
3300            if isinstance(node, exp.Connector):
3301                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3302            else:
3303                sql = self.sql(node)
3304                if sqls and sqls[-1] in ops:
3305                    sqls[-1] += f" {sql}"
3306                else:
3307                    sqls.append(sql)
3308
3309        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3310        return sep.join(sqls)
3311
3312    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3313        return self.binary(expression, "&")
3314
3315    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3316        return self.binary(expression, "<<")
3317
3318    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3319        return f"~{self.sql(expression, 'this')}"
3320
3321    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3322        return self.binary(expression, "|")
3323
3324    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3325        return self.binary(expression, ">>")
3326
3327    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3328        return self.binary(expression, "^")
3329
3330    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3331        format_sql = self.sql(expression, "format")
3332        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3333        to_sql = self.sql(expression, "to")
3334        to_sql = f" {to_sql}" if to_sql else ""
3335        action = self.sql(expression, "action")
3336        action = f" {action}" if action else ""
3337        default = self.sql(expression, "default")
3338        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3339        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3340
3341    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3342        zone = self.sql(expression, "this")
3343        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3344
3345    def collate_sql(self, expression: exp.Collate) -> str:
3346        if self.COLLATE_IS_FUNC:
3347            return self.function_fallback_sql(expression)
3348        return self.binary(expression, "COLLATE")
3349
3350    def command_sql(self, expression: exp.Command) -> str:
3351        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3352
3353    def comment_sql(self, expression: exp.Comment) -> str:
3354        this = self.sql(expression, "this")
3355        kind = expression.args["kind"]
3356        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3357        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3358        expression_sql = self.sql(expression, "expression")
3359        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3360
3361    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3362        this = self.sql(expression, "this")
3363        delete = " DELETE" if expression.args.get("delete") else ""
3364        recompress = self.sql(expression, "recompress")
3365        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3366        to_disk = self.sql(expression, "to_disk")
3367        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3368        to_volume = self.sql(expression, "to_volume")
3369        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3370        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3371
3372    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3373        where = self.sql(expression, "where")
3374        group = self.sql(expression, "group")
3375        aggregates = self.expressions(expression, key="aggregates")
3376        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3377
3378        if not (where or group or aggregates) and len(expression.expressions) == 1:
3379            return f"TTL {self.expressions(expression, flat=True)}"
3380
3381        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3382
3383    def transaction_sql(self, expression: exp.Transaction) -> str:
3384        return "BEGIN"
3385
3386    def commit_sql(self, expression: exp.Commit) -> str:
3387        chain = expression.args.get("chain")
3388        if chain is not None:
3389            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3390
3391        return f"COMMIT{chain or ''}"
3392
3393    def rollback_sql(self, expression: exp.Rollback) -> str:
3394        savepoint = expression.args.get("savepoint")
3395        savepoint = f" TO {savepoint}" if savepoint else ""
3396        return f"ROLLBACK{savepoint}"
3397
3398    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3399        this = self.sql(expression, "this")
3400
3401        dtype = self.sql(expression, "dtype")
3402        if dtype:
3403            collate = self.sql(expression, "collate")
3404            collate = f" COLLATE {collate}" if collate else ""
3405            using = self.sql(expression, "using")
3406            using = f" USING {using}" if using else ""
3407            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3408            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3409
3410        default = self.sql(expression, "default")
3411        if default:
3412            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3413
3414        comment = self.sql(expression, "comment")
3415        if comment:
3416            return f"ALTER COLUMN {this} COMMENT {comment}"
3417
3418        visible = expression.args.get("visible")
3419        if visible:
3420            return f"ALTER COLUMN {this} SET {visible}"
3421
3422        allow_null = expression.args.get("allow_null")
3423        drop = expression.args.get("drop")
3424
3425        if not drop and not allow_null:
3426            self.unsupported("Unsupported ALTER COLUMN syntax")
3427
3428        if allow_null is not None:
3429            keyword = "DROP" if drop else "SET"
3430            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3431
3432        return f"ALTER COLUMN {this} DROP DEFAULT"
3433
3434    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3435        this = self.sql(expression, "this")
3436
3437        visible = expression.args.get("visible")
3438        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3439
3440        return f"ALTER INDEX {this} {visible_sql}"
3441
3442    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3443        this = self.sql(expression, "this")
3444        if not isinstance(expression.this, exp.Var):
3445            this = f"KEY DISTKEY {this}"
3446        return f"ALTER DISTSTYLE {this}"
3447
3448    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3449        compound = " COMPOUND" if expression.args.get("compound") else ""
3450        this = self.sql(expression, "this")
3451        expressions = self.expressions(expression, flat=True)
3452        expressions = f"({expressions})" if expressions else ""
3453        return f"ALTER{compound} SORTKEY {this or expressions}"
3454
3455    def alterrename_sql(self, expression: exp.AlterRename) -> str:
3456        if not self.RENAME_TABLE_WITH_DB:
3457            # Remove db from tables
3458            expression = expression.transform(
3459                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3460            ).assert_is(exp.AlterRename)
3461        this = self.sql(expression, "this")
3462        return f"RENAME TO {this}"
3463
3464    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3465        exists = " IF EXISTS" if expression.args.get("exists") else ""
3466        old_column = self.sql(expression, "this")
3467        new_column = self.sql(expression, "to")
3468        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3469
3470    def alterset_sql(self, expression: exp.AlterSet) -> str:
3471        exprs = self.expressions(expression, flat=True)
3472        if self.ALTER_SET_WRAPPED:
3473            exprs = f"({exprs})"
3474
3475        return f"SET {exprs}"
3476
3477    def alter_sql(self, expression: exp.Alter) -> str:
3478        actions = expression.args["actions"]
3479
3480        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3481            actions[0], exp.ColumnDef
3482        ):
3483            actions_sql = self.expressions(expression, key="actions", flat=True)
3484            actions_sql = f"ADD {actions_sql}"
3485        else:
3486            actions_list = []
3487            for action in actions:
3488                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3489                    action_sql = self.add_column_sql(action)
3490                else:
3491                    action_sql = self.sql(action)
3492                    if isinstance(action, exp.Query):
3493                        action_sql = f"AS {action_sql}"
3494
3495                actions_list.append(action_sql)
3496
3497            actions_sql = self.format_args(*actions_list).lstrip("\n")
3498
3499        exists = " IF EXISTS" if expression.args.get("exists") else ""
3500        on_cluster = self.sql(expression, "cluster")
3501        on_cluster = f" {on_cluster}" if on_cluster else ""
3502        only = " ONLY" if expression.args.get("only") else ""
3503        options = self.expressions(expression, key="options")
3504        options = f", {options}" if options else ""
3505        kind = self.sql(expression, "kind")
3506        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3507
3508        return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
3509
3510    def add_column_sql(self, expression: exp.Expression) -> str:
3511        sql = self.sql(expression)
3512        if isinstance(expression, exp.Schema):
3513            column_text = " COLUMNS"
3514        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3515            column_text = " COLUMN"
3516        else:
3517            column_text = ""
3518
3519        return f"ADD{column_text} {sql}"
3520
3521    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3522        expressions = self.expressions(expression)
3523        exists = " IF EXISTS " if expression.args.get("exists") else " "
3524        return f"DROP{exists}{expressions}"
3525
3526    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3527        return f"ADD {self.expressions(expression, indent=False)}"
3528
3529    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3530        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3531        return f"ADD {exists}{self.sql(expression.this)}"
3532
3533    def distinct_sql(self, expression: exp.Distinct) -> str:
3534        this = self.expressions(expression, flat=True)
3535
3536        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3537            case = exp.case()
3538            for arg in expression.expressions:
3539                case = case.when(arg.is_(exp.null()), exp.null())
3540            this = self.sql(case.else_(f"({this})"))
3541
3542        this = f" {this}" if this else ""
3543
3544        on = self.sql(expression, "on")
3545        on = f" ON {on}" if on else ""
3546        return f"DISTINCT{this}{on}"
3547
3548    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3549        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
3550
3551    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3552        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
3553
3554    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3555        this_sql = self.sql(expression, "this")
3556        expression_sql = self.sql(expression, "expression")
3557        kind = "MAX" if expression.args.get("max") else "MIN"
3558        return f"{this_sql} HAVING {kind} {expression_sql}"
3559
3560    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3561        return self.sql(
3562            exp.Cast(
3563                this=exp.Div(this=expression.this, expression=expression.expression),
3564                to=exp.DataType(this=exp.DataType.Type.INT),
3565            )
3566        )
3567
3568    def dpipe_sql(self, expression: exp.DPipe) -> str:
3569        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3570            return self.func(
3571                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3572            )
3573        return self.binary(expression, "||")
3574
3575    def div_sql(self, expression: exp.Div) -> str:
3576        l, r = expression.left, expression.right
3577
3578        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3579            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3580
3581        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3582            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3583                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3584
3585        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3586            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3587                return self.sql(
3588                    exp.cast(
3589                        l / r,
3590                        to=exp.DataType.Type.BIGINT,
3591                    )
3592                )
3593
3594        return self.binary(expression, "/")
3595
3596    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3597        n = exp._wrap(expression.this, exp.Binary)
3598        d = exp._wrap(expression.expression, exp.Binary)
3599        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
3600
3601    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3602        return self.binary(expression, "OVERLAPS")
3603
3604    def distance_sql(self, expression: exp.Distance) -> str:
3605        return self.binary(expression, "<->")
3606
3607    def dot_sql(self, expression: exp.Dot) -> str:
3608        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
3609
3610    def eq_sql(self, expression: exp.EQ) -> str:
3611        return self.binary(expression, "=")
3612
3613    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3614        return self.binary(expression, ":=")
3615
3616    def escape_sql(self, expression: exp.Escape) -> str:
3617        return self.binary(expression, "ESCAPE")
3618
3619    def glob_sql(self, expression: exp.Glob) -> str:
3620        return self.binary(expression, "GLOB")
3621
3622    def gt_sql(self, expression: exp.GT) -> str:
3623        return self.binary(expression, ">")
3624
3625    def gte_sql(self, expression: exp.GTE) -> str:
3626        return self.binary(expression, ">=")
3627
3628    def ilike_sql(self, expression: exp.ILike) -> str:
3629        return self.binary(expression, "ILIKE")
3630
3631    def ilikeany_sql(self, expression: exp.ILikeAny) -> str:
3632        return self.binary(expression, "ILIKE ANY")
3633
3634    def is_sql(self, expression: exp.Is) -> str:
3635        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
3636            return self.sql(
3637                expression.this if expression.expression.this else exp.not_(expression.this)
3638            )
3639        return self.binary(expression, "IS")
3640
3641    def like_sql(self, expression: exp.Like) -> str:
3642        return self.binary(expression, "LIKE")
3643
3644    def likeany_sql(self, expression: exp.LikeAny) -> str:
3645        return self.binary(expression, "LIKE ANY")
3646
3647    def similarto_sql(self, expression: exp.SimilarTo) -> str:
3648        return self.binary(expression, "SIMILAR TO")
3649
3650    def lt_sql(self, expression: exp.LT) -> str:
3651        return self.binary(expression, "<")
3652
3653    def lte_sql(self, expression: exp.LTE) -> str:
3654        return self.binary(expression, "<=")
3655
3656    def mod_sql(self, expression: exp.Mod) -> str:
3657        return self.binary(expression, "%")
3658
3659    def mul_sql(self, expression: exp.Mul) -> str:
3660        return self.binary(expression, "*")
3661
3662    def neq_sql(self, expression: exp.NEQ) -> str:
3663        return self.binary(expression, "<>")
3664
3665    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
3666        return self.binary(expression, "IS NOT DISTINCT FROM")
3667
3668    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3669        return self.binary(expression, "IS DISTINCT FROM")
3670
3671    def slice_sql(self, expression: exp.Slice) -> str:
3672        return self.binary(expression, ":")
3673
3674    def sub_sql(self, expression: exp.Sub) -> str:
3675        return self.binary(expression, "-")
3676
3677    def trycast_sql(self, expression: exp.TryCast) -> str:
3678        return self.cast_sql(expression, safe_prefix="TRY_")
3679
3680    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
3681        return self.cast_sql(expression)
3682
3683    def try_sql(self, expression: exp.Try) -> str:
3684        if not self.TRY_SUPPORTED:
3685            self.unsupported("Unsupported TRY function")
3686            return self.sql(expression, "this")
3687
3688        return self.func("TRY", expression.this)
3689
3690    def log_sql(self, expression: exp.Log) -> str:
3691        this = expression.this
3692        expr = expression.expression
3693
3694        if self.dialect.LOG_BASE_FIRST is False:
3695            this, expr = expr, this
3696        elif self.dialect.LOG_BASE_FIRST is None and expr:
3697            if this.name in ("2", "10"):
3698                return self.func(f"LOG{this.name}", expr)
3699
3700            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
3701
3702        return self.func("LOG", this, expr)
3703
3704    def use_sql(self, expression: exp.Use) -> str:
3705        kind = self.sql(expression, "kind")
3706        kind = f" {kind}" if kind else ""
3707        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
3708        this = f" {this}" if this else ""
3709        return f"USE{kind}{this}"
3710
3711    def binary(self, expression: exp.Binary, op: str) -> str:
3712        sqls: t.List[str] = []
3713        stack: t.List[t.Union[str, exp.Expression]] = [expression]
3714        binary_type = type(expression)
3715
3716        while stack:
3717            node = stack.pop()
3718
3719            if type(node) is binary_type:
3720                op_func = node.args.get("operator")
3721                if op_func:
3722                    op = f"OPERATOR({self.sql(op_func)})"
3723
3724                stack.append(node.right)
3725                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
3726                stack.append(node.left)
3727            else:
3728                sqls.append(self.sql(node))
3729
3730        return "".join(sqls)
3731
3732    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
3733        to_clause = self.sql(expression, "to")
3734        if to_clause:
3735            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
3736
3737        return self.function_fallback_sql(expression)
3738
3739    def function_fallback_sql(self, expression: exp.Func) -> str:
3740        args = []
3741
3742        for key in expression.arg_types:
3743            arg_value = expression.args.get(key)
3744
3745            if isinstance(arg_value, list):
3746                for value in arg_value:
3747                    args.append(value)
3748            elif arg_value is not None:
3749                args.append(arg_value)
3750
3751        if self.dialect.PRESERVE_ORIGINAL_NAMES:
3752            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
3753        else:
3754            name = expression.sql_name()
3755
3756        return self.func(name, *args)
3757
3758    def func(
3759        self,
3760        name: str,
3761        *args: t.Optional[exp.Expression | str],
3762        prefix: str = "(",
3763        suffix: str = ")",
3764        normalize: bool = True,
3765    ) -> str:
3766        name = self.normalize_func(name) if normalize else name
3767        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
3768
3769    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
3770        arg_sqls = tuple(
3771            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
3772        )
3773        if self.pretty and self.too_wide(arg_sqls):
3774            return self.indent(
3775                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
3776            )
3777        return sep.join(arg_sqls)
3778
3779    def too_wide(self, args: t.Iterable) -> bool:
3780        return sum(len(arg) for arg in args) > self.max_text_width
3781
3782    def format_time(
3783        self,
3784        expression: exp.Expression,
3785        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
3786        inverse_time_trie: t.Optional[t.Dict] = None,
3787    ) -> t.Optional[str]:
3788        return format_time(
3789            self.sql(expression, "format"),
3790            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
3791            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
3792        )
3793
3794    def expressions(
3795        self,
3796        expression: t.Optional[exp.Expression] = None,
3797        key: t.Optional[str] = None,
3798        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
3799        flat: bool = False,
3800        indent: bool = True,
3801        skip_first: bool = False,
3802        skip_last: bool = False,
3803        sep: str = ", ",
3804        prefix: str = "",
3805        dynamic: bool = False,
3806        new_line: bool = False,
3807    ) -> str:
3808        expressions = expression.args.get(key or "expressions") if expression else sqls
3809
3810        if not expressions:
3811            return ""
3812
3813        if flat:
3814            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
3815
3816        num_sqls = len(expressions)
3817        result_sqls = []
3818
3819        for i, e in enumerate(expressions):
3820            sql = self.sql(e, comment=False)
3821            if not sql:
3822                continue
3823
3824            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
3825
3826            if self.pretty:
3827                if self.leading_comma:
3828                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
3829                else:
3830                    result_sqls.append(
3831                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
3832                    )
3833            else:
3834                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
3835
3836        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
3837            if new_line:
3838                result_sqls.insert(0, "")
3839                result_sqls.append("")
3840            result_sql = "\n".join(s.rstrip() for s in result_sqls)
3841        else:
3842            result_sql = "".join(result_sqls)
3843
3844        return (
3845            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
3846            if indent
3847            else result_sql
3848        )
3849
3850    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
3851        flat = flat or isinstance(expression.parent, exp.Properties)
3852        expressions_sql = self.expressions(expression, flat=flat)
3853        if flat:
3854            return f"{op} {expressions_sql}"
3855        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3856
3857    def naked_property(self, expression: exp.Property) -> str:
3858        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
3859        if not property_name:
3860            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
3861        return f"{property_name} {self.sql(expression, 'this')}"
3862
3863    def tag_sql(self, expression: exp.Tag) -> str:
3864        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
3865
3866    def token_sql(self, token_type: TokenType) -> str:
3867        return self.TOKEN_MAPPING.get(token_type, token_type.name)
3868
3869    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
3870        this = self.sql(expression, "this")
3871        expressions = self.no_identify(self.expressions, expression)
3872        expressions = (
3873            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
3874        )
3875        return f"{this}{expressions}" if expressions.strip() != "" else this
3876
3877    def joinhint_sql(self, expression: exp.JoinHint) -> str:
3878        this = self.sql(expression, "this")
3879        expressions = self.expressions(expression, flat=True)
3880        return f"{this}({expressions})"
3881
3882    def kwarg_sql(self, expression: exp.Kwarg) -> str:
3883        return self.binary(expression, "=>")
3884
3885    def when_sql(self, expression: exp.When) -> str:
3886        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
3887        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
3888        condition = self.sql(expression, "condition")
3889        condition = f" AND {condition}" if condition else ""
3890
3891        then_expression = expression.args.get("then")
3892        if isinstance(then_expression, exp.Insert):
3893            this = self.sql(then_expression, "this")
3894            this = f"INSERT {this}" if this else "INSERT"
3895            then = self.sql(then_expression, "expression")
3896            then = f"{this} VALUES {then}" if then else this
3897        elif isinstance(then_expression, exp.Update):
3898            if isinstance(then_expression.args.get("expressions"), exp.Star):
3899                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
3900            else:
3901                then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}"
3902        else:
3903            then = self.sql(then_expression)
3904        return f"WHEN {matched}{source}{condition} THEN {then}"
3905
3906    def whens_sql(self, expression: exp.Whens) -> str:
3907        return self.expressions(expression, sep=" ", indent=False)
3908
3909    def merge_sql(self, expression: exp.Merge) -> str:
3910        table = expression.this
3911        table_alias = ""
3912
3913        hints = table.args.get("hints")
3914        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
3915            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
3916            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
3917
3918        this = self.sql(table)
3919        using = f"USING {self.sql(expression, 'using')}"
3920        on = f"ON {self.sql(expression, 'on')}"
3921        whens = self.sql(expression, "whens")
3922
3923        returning = self.sql(expression, "returning")
3924        if returning:
3925            whens = f"{whens}{returning}"
3926
3927        sep = self.sep()
3928
3929        return self.prepend_ctes(
3930            expression,
3931            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
3932        )
3933
3934    @unsupported_args("format")
3935    def tochar_sql(self, expression: exp.ToChar) -> str:
3936        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
3937
3938    def tonumber_sql(self, expression: exp.ToNumber) -> str:
3939        if not self.SUPPORTS_TO_NUMBER:
3940            self.unsupported("Unsupported TO_NUMBER function")
3941            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3942
3943        fmt = expression.args.get("format")
3944        if not fmt:
3945            self.unsupported("Conversion format is required for TO_NUMBER")
3946            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
3947
3948        return self.func("TO_NUMBER", expression.this, fmt)
3949
3950    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
3951        this = self.sql(expression, "this")
3952        kind = self.sql(expression, "kind")
3953        settings_sql = self.expressions(expression, key="settings", sep=" ")
3954        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
3955        return f"{this}({kind}{args})"
3956
3957    def dictrange_sql(self, expression: exp.DictRange) -> str:
3958        this = self.sql(expression, "this")
3959        max = self.sql(expression, "max")
3960        min = self.sql(expression, "min")
3961        return f"{this}(MIN {min} MAX {max})"
3962
3963    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
3964        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
3965
3966    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
3967        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
3968
3969    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
3970    def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str:
3971        return f"UNIQUE KEY ({self.expressions(expression, flat=True)})"
3972
3973    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
3974    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
3975        expressions = self.expressions(expression, flat=True)
3976        expressions = f" {self.wrap(expressions)}" if expressions else ""
3977        buckets = self.sql(expression, "buckets")
3978        kind = self.sql(expression, "kind")
3979        buckets = f" BUCKETS {buckets}" if buckets else ""
3980        order = self.sql(expression, "order")
3981        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
3982
3983    def oncluster_sql(self, expression: exp.OnCluster) -> str:
3984        return ""
3985
3986    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
3987        expressions = self.expressions(expression, key="expressions", flat=True)
3988        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
3989        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
3990        buckets = self.sql(expression, "buckets")
3991        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
3992
3993    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
3994        this = self.sql(expression, "this")
3995        having = self.sql(expression, "having")
3996
3997        if having:
3998            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
3999
4000        return self.func("ANY_VALUE", this)
4001
4002    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4003        transform = self.func("TRANSFORM", *expression.expressions)
4004        row_format_before = self.sql(expression, "row_format_before")
4005        row_format_before = f" {row_format_before}" if row_format_before else ""
4006        record_writer = self.sql(expression, "record_writer")
4007        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4008        using = f" USING {self.sql(expression, 'command_script')}"
4009        schema = self.sql(expression, "schema")
4010        schema = f" AS {schema}" if schema else ""
4011        row_format_after = self.sql(expression, "row_format_after")
4012        row_format_after = f" {row_format_after}" if row_format_after else ""
4013        record_reader = self.sql(expression, "record_reader")
4014        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4015        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4016
4017    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4018        key_block_size = self.sql(expression, "key_block_size")
4019        if key_block_size:
4020            return f"KEY_BLOCK_SIZE = {key_block_size}"
4021
4022        using = self.sql(expression, "using")
4023        if using:
4024            return f"USING {using}"
4025
4026        parser = self.sql(expression, "parser")
4027        if parser:
4028            return f"WITH PARSER {parser}"
4029
4030        comment = self.sql(expression, "comment")
4031        if comment:
4032            return f"COMMENT {comment}"
4033
4034        visible = expression.args.get("visible")
4035        if visible is not None:
4036            return "VISIBLE" if visible else "INVISIBLE"
4037
4038        engine_attr = self.sql(expression, "engine_attr")
4039        if engine_attr:
4040            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4041
4042        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4043        if secondary_engine_attr:
4044            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4045
4046        self.unsupported("Unsupported index constraint option.")
4047        return ""
4048
4049    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4050        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4051        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4052
4053    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4054        kind = self.sql(expression, "kind")
4055        kind = f"{kind} INDEX" if kind else "INDEX"
4056        this = self.sql(expression, "this")
4057        this = f" {this}" if this else ""
4058        index_type = self.sql(expression, "index_type")
4059        index_type = f" USING {index_type}" if index_type else ""
4060        expressions = self.expressions(expression, flat=True)
4061        expressions = f" ({expressions})" if expressions else ""
4062        options = self.expressions(expression, key="options", sep=" ")
4063        options = f" {options}" if options else ""
4064        return f"{kind}{this}{index_type}{expressions}{options}"
4065
4066    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4067        if self.NVL2_SUPPORTED:
4068            return self.function_fallback_sql(expression)
4069
4070        case = exp.Case().when(
4071            expression.this.is_(exp.null()).not_(copy=False),
4072            expression.args["true"],
4073            copy=False,
4074        )
4075        else_cond = expression.args.get("false")
4076        if else_cond:
4077            case.else_(else_cond, copy=False)
4078
4079        return self.sql(case)
4080
4081    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4082        this = self.sql(expression, "this")
4083        expr = self.sql(expression, "expression")
4084        iterator = self.sql(expression, "iterator")
4085        condition = self.sql(expression, "condition")
4086        condition = f" IF {condition}" if condition else ""
4087        return f"{this} FOR {expr} IN {iterator}{condition}"
4088
4089    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4090        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4091
4092    def opclass_sql(self, expression: exp.Opclass) -> str:
4093        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4094
4095    def predict_sql(self, expression: exp.Predict) -> str:
4096        model = self.sql(expression, "this")
4097        model = f"MODEL {model}"
4098        table = self.sql(expression, "expression")
4099        table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table
4100        parameters = self.sql(expression, "params_struct")
4101        return self.func("PREDICT", model, table, parameters or None)
4102
4103    def forin_sql(self, expression: exp.ForIn) -> str:
4104        this = self.sql(expression, "this")
4105        expression_sql = self.sql(expression, "expression")
4106        return f"FOR {this} DO {expression_sql}"
4107
4108    def refresh_sql(self, expression: exp.Refresh) -> str:
4109        this = self.sql(expression, "this")
4110        table = "" if isinstance(expression.this, exp.Literal) else "TABLE "
4111        return f"REFRESH {table}{this}"
4112
4113    def toarray_sql(self, expression: exp.ToArray) -> str:
4114        arg = expression.this
4115        if not arg.type:
4116            from sqlglot.optimizer.annotate_types import annotate_types
4117
4118            arg = annotate_types(arg, dialect=self.dialect)
4119
4120        if arg.is_type(exp.DataType.Type.ARRAY):
4121            return self.sql(arg)
4122
4123        cond_for_null = arg.is_(exp.null())
4124        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4125
4126    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4127        this = expression.this
4128        time_format = self.format_time(expression)
4129
4130        if time_format:
4131            return self.sql(
4132                exp.cast(
4133                    exp.StrToTime(this=this, format=expression.args["format"]),
4134                    exp.DataType.Type.TIME,
4135                )
4136            )
4137
4138        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4139            return self.sql(this)
4140
4141        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4142
4143    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4144        this = expression.this
4145        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4146            return self.sql(this)
4147
4148        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4149
4150    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4151        this = expression.this
4152        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4153            return self.sql(this)
4154
4155        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4156
4157    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4158        this = expression.this
4159        time_format = self.format_time(expression)
4160
4161        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4162            return self.sql(
4163                exp.cast(
4164                    exp.StrToTime(this=this, format=expression.args["format"]),
4165                    exp.DataType.Type.DATE,
4166                )
4167            )
4168
4169        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4170            return self.sql(this)
4171
4172        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4173
4174    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4175        return self.sql(
4176            exp.func(
4177                "DATEDIFF",
4178                expression.this,
4179                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4180                "day",
4181            )
4182        )
4183
4184    def lastday_sql(self, expression: exp.LastDay) -> str:
4185        if self.LAST_DAY_SUPPORTS_DATE_PART:
4186            return self.function_fallback_sql(expression)
4187
4188        unit = expression.text("unit")
4189        if unit and unit != "MONTH":
4190            self.unsupported("Date parts are not supported in LAST_DAY.")
4191
4192        return self.func("LAST_DAY", expression.this)
4193
4194    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4195        from sqlglot.dialects.dialect import unit_to_str
4196
4197        return self.func(
4198            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4199        )
4200
4201    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4202        if self.CAN_IMPLEMENT_ARRAY_ANY:
4203            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4204            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4205            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4206            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4207
4208        from sqlglot.dialects import Dialect
4209
4210        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4211        if self.dialect.__class__ != Dialect:
4212            self.unsupported("ARRAY_ANY is unsupported")
4213
4214        return self.function_fallback_sql(expression)
4215
4216    def struct_sql(self, expression: exp.Struct) -> str:
4217        expression.set(
4218            "expressions",
4219            [
4220                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4221                if isinstance(e, exp.PropertyEQ)
4222                else e
4223                for e in expression.expressions
4224            ],
4225        )
4226
4227        return self.function_fallback_sql(expression)
4228
4229    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4230        low = self.sql(expression, "this")
4231        high = self.sql(expression, "expression")
4232
4233        return f"{low} TO {high}"
4234
4235    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4236        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4237        tables = f" {self.expressions(expression)}"
4238
4239        exists = " IF EXISTS" if expression.args.get("exists") else ""
4240
4241        on_cluster = self.sql(expression, "cluster")
4242        on_cluster = f" {on_cluster}" if on_cluster else ""
4243
4244        identity = self.sql(expression, "identity")
4245        identity = f" {identity} IDENTITY" if identity else ""
4246
4247        option = self.sql(expression, "option")
4248        option = f" {option}" if option else ""
4249
4250        partition = self.sql(expression, "partition")
4251        partition = f" {partition}" if partition else ""
4252
4253        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4254
4255    # This transpiles T-SQL's CONVERT function
4256    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4257    def convert_sql(self, expression: exp.Convert) -> str:
4258        to = expression.this
4259        value = expression.expression
4260        style = expression.args.get("style")
4261        safe = expression.args.get("safe")
4262        strict = expression.args.get("strict")
4263
4264        if not to or not value:
4265            return ""
4266
4267        # Retrieve length of datatype and override to default if not specified
4268        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4269            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4270
4271        transformed: t.Optional[exp.Expression] = None
4272        cast = exp.Cast if strict else exp.TryCast
4273
4274        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4275        if isinstance(style, exp.Literal) and style.is_int:
4276            from sqlglot.dialects.tsql import TSQL
4277
4278            style_value = style.name
4279            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4280            if not converted_style:
4281                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4282
4283            fmt = exp.Literal.string(converted_style)
4284
4285            if to.this == exp.DataType.Type.DATE:
4286                transformed = exp.StrToDate(this=value, format=fmt)
4287            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4288                transformed = exp.StrToTime(this=value, format=fmt)
4289            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4290                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4291            elif to.this == exp.DataType.Type.TEXT:
4292                transformed = exp.TimeToStr(this=value, format=fmt)
4293
4294        if not transformed:
4295            transformed = cast(this=value, to=to, safe=safe)
4296
4297        return self.sql(transformed)
4298
4299    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4300        this = expression.this
4301        if isinstance(this, exp.JSONPathWildcard):
4302            this = self.json_path_part(this)
4303            return f".{this}" if this else ""
4304
4305        if exp.SAFE_IDENTIFIER_RE.match(this):
4306            return f".{this}"
4307
4308        this = self.json_path_part(this)
4309        return (
4310            f"[{this}]"
4311            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
4312            else f".{this}"
4313        )
4314
4315    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
4316        this = self.json_path_part(expression.this)
4317        return f"[{this}]" if this else ""
4318
4319    def _simplify_unless_literal(self, expression: E) -> E:
4320        if not isinstance(expression, exp.Literal):
4321            from sqlglot.optimizer.simplify import simplify
4322
4323            expression = simplify(expression, dialect=self.dialect)
4324
4325        return expression
4326
4327    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
4328        this = expression.this
4329        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
4330            self.unsupported(
4331                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
4332            )
4333            return self.sql(this)
4334
4335        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
4336            # The first modifier here will be the one closest to the AggFunc's arg
4337            mods = sorted(
4338                expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
4339                key=lambda x: 0
4340                if isinstance(x, exp.HavingMax)
4341                else (1 if isinstance(x, exp.Order) else 2),
4342            )
4343
4344            if mods:
4345                mod = mods[0]
4346                this = expression.__class__(this=mod.this.copy())
4347                this.meta["inline"] = True
4348                mod.this.replace(this)
4349                return self.sql(expression.this)
4350
4351            agg_func = expression.find(exp.AggFunc)
4352
4353            if agg_func:
4354                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
4355                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
4356
4357        return f"{self.sql(expression, 'this')} {text}"
4358
4359    def _replace_line_breaks(self, string: str) -> str:
4360        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
4361        if self.pretty:
4362            return string.replace("\n", self.SENTINEL_LINE_BREAK)
4363        return string
4364
4365    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4366        option = self.sql(expression, "this")
4367
4368        if expression.expressions:
4369            upper = option.upper()
4370
4371            # Snowflake FILE_FORMAT options are separated by whitespace
4372            sep = " " if upper == "FILE_FORMAT" else ", "
4373
4374            # Databricks copy/format options do not set their list of values with EQ
4375            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4376            values = self.expressions(expression, flat=True, sep=sep)
4377            return f"{option}{op}({values})"
4378
4379        value = self.sql(expression, "expression")
4380
4381        if not value:
4382            return option
4383
4384        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4385
4386        return f"{option}{op}{value}"
4387
4388    def credentials_sql(self, expression: exp.Credentials) -> str:
4389        cred_expr = expression.args.get("credentials")
4390        if isinstance(cred_expr, exp.Literal):
4391            # Redshift case: CREDENTIALS <string>
4392            credentials = self.sql(expression, "credentials")
4393            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4394        else:
4395            # Snowflake case: CREDENTIALS = (...)
4396            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4397            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4398
4399        storage = self.sql(expression, "storage")
4400        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4401
4402        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4403        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4404
4405        iam_role = self.sql(expression, "iam_role")
4406        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4407
4408        region = self.sql(expression, "region")
4409        region = f" REGION {region}" if region else ""
4410
4411        return f"{credentials}{storage}{encryption}{iam_role}{region}"
4412
4413    def copy_sql(self, expression: exp.Copy) -> str:
4414        this = self.sql(expression, "this")
4415        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4416
4417        credentials = self.sql(expression, "credentials")
4418        credentials = self.seg(credentials) if credentials else ""
4419        kind = self.seg("FROM" if expression.args.get("kind") else "TO")
4420        files = self.expressions(expression, key="files", flat=True)
4421
4422        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4423        params = self.expressions(
4424            expression,
4425            key="params",
4426            sep=sep,
4427            new_line=True,
4428            skip_last=True,
4429            skip_first=True,
4430            indent=self.COPY_PARAMS_ARE_WRAPPED,
4431        )
4432
4433        if params:
4434            if self.COPY_PARAMS_ARE_WRAPPED:
4435                params = f" WITH ({params})"
4436            elif not self.pretty:
4437                params = f" {params}"
4438
4439        return f"COPY{this}{kind} {files}{credentials}{params}"
4440
4441    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4442        return ""
4443
4444    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4445        on_sql = "ON" if expression.args.get("on") else "OFF"
4446        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4447        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4448        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4449        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4450
4451        if filter_col or retention_period:
4452            on_sql = self.func("ON", filter_col, retention_period)
4453
4454        return f"DATA_DELETION={on_sql}"
4455
4456    def maskingpolicycolumnconstraint_sql(
4457        self, expression: exp.MaskingPolicyColumnConstraint
4458    ) -> str:
4459        this = self.sql(expression, "this")
4460        expressions = self.expressions(expression, flat=True)
4461        expressions = f" USING ({expressions})" if expressions else ""
4462        return f"MASKING POLICY {this}{expressions}"
4463
4464    def gapfill_sql(self, expression: exp.GapFill) -> str:
4465        this = self.sql(expression, "this")
4466        this = f"TABLE {this}"
4467        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
4468
4469    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4470        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
4471
4472    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4473        this = self.sql(expression, "this")
4474        expr = expression.expression
4475
4476        if isinstance(expr, exp.Func):
4477            # T-SQL's CLR functions are case sensitive
4478            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4479        else:
4480            expr = self.sql(expression, "expression")
4481
4482        return self.scope_resolution(expr, this)
4483
4484    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4485        if self.PARSE_JSON_NAME is None:
4486            return self.sql(expression.this)
4487
4488        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
4489
4490    def rand_sql(self, expression: exp.Rand) -> str:
4491        lower = self.sql(expression, "lower")
4492        upper = self.sql(expression, "upper")
4493
4494        if lower and upper:
4495            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4496        return self.func("RAND", expression.this)
4497
4498    def changes_sql(self, expression: exp.Changes) -> str:
4499        information = self.sql(expression, "information")
4500        information = f"INFORMATION => {information}"
4501        at_before = self.sql(expression, "at_before")
4502        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4503        end = self.sql(expression, "end")
4504        end = f"{self.seg('')}{end}" if end else ""
4505
4506        return f"CHANGES ({information}){at_before}{end}"
4507
4508    def pad_sql(self, expression: exp.Pad) -> str:
4509        prefix = "L" if expression.args.get("is_left") else "R"
4510
4511        fill_pattern = self.sql(expression, "fill_pattern") or None
4512        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4513            fill_pattern = "' '"
4514
4515        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
4516
4517    def summarize_sql(self, expression: exp.Summarize) -> str:
4518        table = " TABLE" if expression.args.get("table") else ""
4519        return f"SUMMARIZE{table} {self.sql(expression.this)}"
4520
4521    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4522        generate_series = exp.GenerateSeries(**expression.args)
4523
4524        parent = expression.parent
4525        if isinstance(parent, (exp.Alias, exp.TableAlias)):
4526            parent = parent.parent
4527
4528        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
4529            return self.sql(exp.Unnest(expressions=[generate_series]))
4530
4531        if isinstance(parent, exp.Select):
4532            self.unsupported("GenerateSeries projection unnesting is not supported.")
4533
4534        return self.sql(generate_series)
4535
4536    def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str:
4537        exprs = expression.expressions
4538        if not self.ARRAY_CONCAT_IS_VAR_LEN:
4539            rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs)
4540        else:
4541            rhs = self.expressions(expression)
4542
4543        return self.func(name, expression.this, rhs or None)
4544
4545    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
4546        if self.SUPPORTS_CONVERT_TIMEZONE:
4547            return self.function_fallback_sql(expression)
4548
4549        source_tz = expression.args.get("source_tz")
4550        target_tz = expression.args.get("target_tz")
4551        timestamp = expression.args.get("timestamp")
4552
4553        if source_tz and timestamp:
4554            timestamp = exp.AtTimeZone(
4555                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
4556            )
4557
4558        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
4559
4560        return self.sql(expr)
4561
4562    def json_sql(self, expression: exp.JSON) -> str:
4563        this = self.sql(expression, "this")
4564        this = f" {this}" if this else ""
4565
4566        _with = expression.args.get("with")
4567
4568        if _with is None:
4569            with_sql = ""
4570        elif not _with:
4571            with_sql = " WITHOUT"
4572        else:
4573            with_sql = " WITH"
4574
4575        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
4576
4577        return f"JSON{this}{with_sql}{unique_sql}"
4578
4579    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
4580        def _generate_on_options(arg: t.Any) -> str:
4581            return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}"
4582
4583        path = self.sql(expression, "path")
4584        returning = self.sql(expression, "returning")
4585        returning = f" RETURNING {returning}" if returning else ""
4586
4587        on_condition = self.sql(expression, "on_condition")
4588        on_condition = f" {on_condition}" if on_condition else ""
4589
4590        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4591
4592    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
4593        else_ = "ELSE " if expression.args.get("else_") else ""
4594        condition = self.sql(expression, "expression")
4595        condition = f"WHEN {condition} THEN " if condition else else_
4596        insert = self.sql(expression, "this")[len("INSERT") :].strip()
4597        return f"{condition}{insert}"
4598
4599    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
4600        kind = self.sql(expression, "kind")
4601        expressions = self.seg(self.expressions(expression, sep=" "))
4602        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
4603        return res
4604
4605    def oncondition_sql(self, expression: exp.OnCondition) -> str:
4606        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
4607        empty = expression.args.get("empty")
4608        empty = (
4609            f"DEFAULT {empty} ON EMPTY"
4610            if isinstance(empty, exp.Expression)
4611            else self.sql(expression, "empty")
4612        )
4613
4614        error = expression.args.get("error")
4615        error = (
4616            f"DEFAULT {error} ON ERROR"
4617            if isinstance(error, exp.Expression)
4618            else self.sql(expression, "error")
4619        )
4620
4621        if error and empty:
4622            error = (
4623                f"{empty} {error}"
4624                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
4625                else f"{error} {empty}"
4626            )
4627            empty = ""
4628
4629        null = self.sql(expression, "null")
4630
4631        return f"{empty}{error}{null}"
4632
4633    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
4634        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
4635        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
4636
4637    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
4638        this = self.sql(expression, "this")
4639        path = self.sql(expression, "path")
4640
4641        passing = self.expressions(expression, "passing")
4642        passing = f" PASSING {passing}" if passing else ""
4643
4644        on_condition = self.sql(expression, "on_condition")
4645        on_condition = f" {on_condition}" if on_condition else ""
4646
4647        path = f"{path}{passing}{on_condition}"
4648
4649        return self.func("JSON_EXISTS", this, path)
4650
4651    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
4652        array_agg = self.function_fallback_sql(expression)
4653
4654        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
4655        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
4656        if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"):
4657            parent = expression.parent
4658            if isinstance(parent, exp.Filter):
4659                parent_cond = parent.expression.this
4660                parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_()))
4661            else:
4662                this = expression.this
4663                # Do not add the filter if the input is not a column (e.g. literal, struct etc)
4664                if this.find(exp.Column):
4665                    # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
4666                    this_sql = (
4667                        self.expressions(this)
4668                        if isinstance(this, exp.Distinct)
4669                        else self.sql(expression, "this")
4670                    )
4671
4672                    array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)"
4673
4674        return array_agg
4675
4676    def apply_sql(self, expression: exp.Apply) -> str:
4677        this = self.sql(expression, "this")
4678        expr = self.sql(expression, "expression")
4679
4680        return f"{this} APPLY({expr})"
4681
4682    def grant_sql(self, expression: exp.Grant) -> str:
4683        privileges_sql = self.expressions(expression, key="privileges", flat=True)
4684
4685        kind = self.sql(expression, "kind")
4686        kind = f" {kind}" if kind else ""
4687
4688        securable = self.sql(expression, "securable")
4689        securable = f" {securable}" if securable else ""
4690
4691        principals = self.expressions(expression, key="principals", flat=True)
4692
4693        grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else ""
4694
4695        return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4696
4697    def grantprivilege_sql(self, expression: exp.GrantPrivilege):
4698        this = self.sql(expression, "this")
4699        columns = self.expressions(expression, flat=True)
4700        columns = f"({columns})" if columns else ""
4701
4702        return f"{this}{columns}"
4703
4704    def grantprincipal_sql(self, expression: exp.GrantPrincipal):
4705        this = self.sql(expression, "this")
4706
4707        kind = self.sql(expression, "kind")
4708        kind = f"{kind} " if kind else ""
4709
4710        return f"{kind}{this}"
4711
4712    def columns_sql(self, expression: exp.Columns):
4713        func = self.function_fallback_sql(expression)
4714        if expression.args.get("unpack"):
4715            func = f"*{func}"
4716
4717        return func
4718
4719    def overlay_sql(self, expression: exp.Overlay):
4720        this = self.sql(expression, "this")
4721        expr = self.sql(expression, "expression")
4722        from_sql = self.sql(expression, "from")
4723        for_sql = self.sql(expression, "for")
4724        for_sql = f" FOR {for_sql}" if for_sql else ""
4725
4726        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
4727
4728    @unsupported_args("format")
4729    def todouble_sql(self, expression: exp.ToDouble) -> str:
4730        return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4731
4732    def string_sql(self, expression: exp.String) -> str:
4733        this = expression.this
4734        zone = expression.args.get("zone")
4735
4736        if zone:
4737            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
4738            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
4739            # set for source_tz to transpile the time conversion before the STRING cast
4740            this = exp.ConvertTimezone(
4741                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
4742            )
4743
4744        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
4745
4746    def median_sql(self, expression: exp.Median):
4747        if not self.SUPPORTS_MEDIAN:
4748            return self.sql(
4749                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
4750            )
4751
4752        return self.function_fallback_sql(expression)
4753
4754    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
4755        filler = self.sql(expression, "this")
4756        filler = f" {filler}" if filler else ""
4757        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
4758        return f"TRUNCATE{filler} {with_count}"
4759
4760    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
4761        if self.SUPPORTS_UNIX_SECONDS:
4762            return self.function_fallback_sql(expression)
4763
4764        start_ts = exp.cast(
4765            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
4766        )
4767
4768        return self.sql(
4769            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
4770        )
4771
4772    def arraysize_sql(self, expression: exp.ArraySize) -> str:
4773        dim = expression.expression
4774
4775        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
4776        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
4777            if not (dim.is_int and dim.name == "1"):
4778                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
4779            dim = None
4780
4781        # If dimension is required but not specified, default initialize it
4782        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
4783            dim = exp.Literal.number(1)
4784
4785        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4786
4787    def attach_sql(self, expression: exp.Attach) -> str:
4788        this = self.sql(expression, "this")
4789        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
4790        expressions = self.expressions(expression)
4791        expressions = f" ({expressions})" if expressions else ""
4792
4793        return f"ATTACH{exists_sql} {this}{expressions}"
4794
4795    def detach_sql(self, expression: exp.Detach) -> str:
4796        this = self.sql(expression, "this")
4797        # the DATABASE keyword is required if IF EXISTS is set
4798        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
4799        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
4800        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
4801
4802        return f"DETACH{exists_sql} {this}"
4803
4804    def attachoption_sql(self, expression: exp.AttachOption) -> str:
4805        this = self.sql(expression, "this")
4806        value = self.sql(expression, "expression")
4807        value = f" {value}" if value else ""
4808        return f"{this}{value}"
4809
4810    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4811        this_sql = self.sql(expression, "this")
4812        if isinstance(expression.this, exp.Table):
4813            this_sql = f"TABLE {this_sql}"
4814
4815        return self.func(
4816            "FEATURES_AT_TIME",
4817            this_sql,
4818            expression.args.get("time"),
4819            expression.args.get("num_rows"),
4820            expression.args.get("ignore_feature_nulls"),
4821        )
4822
4823    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
4824        return (
4825            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
4826        )
4827
4828    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
4829        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
4830        encode = f"{encode} {self.sql(expression, 'this')}"
4831
4832        properties = expression.args.get("properties")
4833        if properties:
4834            encode = f"{encode} {self.properties(properties)}"
4835
4836        return encode
4837
4838    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
4839        this = self.sql(expression, "this")
4840        include = f"INCLUDE {this}"
4841
4842        column_def = self.sql(expression, "column_def")
4843        if column_def:
4844            include = f"{include} {column_def}"
4845
4846        alias = self.sql(expression, "alias")
4847        if alias:
4848            include = f"{include} AS {alias}"
4849
4850        return include
4851
4852    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
4853        name = f"NAME {self.sql(expression, 'this')}"
4854        return self.func("XMLELEMENT", name, *expression.expressions)
4855
4856    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
4857        this = self.sql(expression, "this")
4858        expr = self.sql(expression, "expression")
4859        expr = f"({expr})" if expr else ""
4860        return f"{this}{expr}"
4861
4862    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
4863        partitions = self.expressions(expression, "partition_expressions")
4864        create = self.expressions(expression, "create_expressions")
4865        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
4866
4867    def partitionbyrangepropertydynamic_sql(
4868        self, expression: exp.PartitionByRangePropertyDynamic
4869    ) -> str:
4870        start = self.sql(expression, "start")
4871        end = self.sql(expression, "end")
4872
4873        every = expression.args["every"]
4874        if isinstance(every, exp.Interval) and every.this.is_string:
4875            every.this.replace(exp.Literal.number(every.name))
4876
4877        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
4878
4879    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
4880        name = self.sql(expression, "this")
4881        values = self.expressions(expression, flat=True)
4882
4883        return f"NAME {name} VALUE {values}"
4884
4885    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
4886        kind = self.sql(expression, "kind")
4887        sample = self.sql(expression, "sample")
4888        return f"SAMPLE {sample} {kind}"
4889
4890    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
4891        kind = self.sql(expression, "kind")
4892        option = self.sql(expression, "option")
4893        option = f" {option}" if option else ""
4894        this = self.sql(expression, "this")
4895        this = f" {this}" if this else ""
4896        columns = self.expressions(expression)
4897        columns = f" {columns}" if columns else ""
4898        return f"{kind}{option} STATISTICS{this}{columns}"
4899
4900    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
4901        this = self.sql(expression, "this")
4902        columns = self.expressions(expression)
4903        inner_expression = self.sql(expression, "expression")
4904        inner_expression = f" {inner_expression}" if inner_expression else ""
4905        update_options = self.sql(expression, "update_options")
4906        update_options = f" {update_options} UPDATE" if update_options else ""
4907        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
4908
4909    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
4910        kind = self.sql(expression, "kind")
4911        kind = f" {kind}" if kind else ""
4912        return f"DELETE{kind} STATISTICS"
4913
4914    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
4915        inner_expression = self.sql(expression, "expression")
4916        return f"LIST CHAINED ROWS{inner_expression}"
4917
4918    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
4919        kind = self.sql(expression, "kind")
4920        this = self.sql(expression, "this")
4921        this = f" {this}" if this else ""
4922        inner_expression = self.sql(expression, "expression")
4923        return f"VALIDATE {kind}{this}{inner_expression}"
4924
4925    def analyze_sql(self, expression: exp.Analyze) -> str:
4926        options = self.expressions(expression, key="options", sep=" ")
4927        options = f" {options}" if options else ""
4928        kind = self.sql(expression, "kind")
4929        kind = f" {kind}" if kind else ""
4930        this = self.sql(expression, "this")
4931        this = f" {this}" if this else ""
4932        mode = self.sql(expression, "mode")
4933        mode = f" {mode}" if mode else ""
4934        properties = self.sql(expression, "properties")
4935        properties = f" {properties}" if properties else ""
4936        partition = self.sql(expression, "partition")
4937        partition = f" {partition}" if partition else ""
4938        inner_expression = self.sql(expression, "expression")
4939        inner_expression = f" {inner_expression}" if inner_expression else ""
4940        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
4941
4942    def xmltable_sql(self, expression: exp.XMLTable) -> str:
4943        this = self.sql(expression, "this")
4944        namespaces = self.expressions(expression, key="namespaces")
4945        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
4946        passing = self.expressions(expression, key="passing")
4947        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
4948        columns = self.expressions(expression, key="columns")
4949        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
4950        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
4951        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
4952
4953    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
4954        this = self.sql(expression, "this")
4955        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
4956
4957    def export_sql(self, expression: exp.Export) -> str:
4958        this = self.sql(expression, "this")
4959        connection = self.sql(expression, "connection")
4960        connection = f"WITH CONNECTION {connection} " if connection else ""
4961        options = self.sql(expression, "options")
4962        return f"EXPORT DATA {connection}{options} AS {this}"
4963
4964    def declare_sql(self, expression: exp.Declare) -> str:
4965        return f"DECLARE {self.expressions(expression, flat=True)}"
4966
4967    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
4968        variable = self.sql(expression, "this")
4969        default = self.sql(expression, "default")
4970        default = f" = {default}" if default else ""
4971
4972        kind = self.sql(expression, "kind")
4973        if isinstance(expression.args.get("kind"), exp.Schema):
4974            kind = f"TABLE {kind}"
4975
4976        return f"{variable} AS {kind}{default}"
4977
4978    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
4979        kind = self.sql(expression, "kind")
4980        this = self.sql(expression, "this")
4981        set = self.sql(expression, "expression")
4982        using = self.sql(expression, "using")
4983        using = f" USING {using}" if using else ""
4984
4985        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
4986
4987        return f"{kind_sql} {this} SET {set}{using}"
4988
4989    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
4990        params = self.expressions(expression, key="params", flat=True)
4991        return self.func(expression.name, *expression.expressions) + f"({params})"
4992
4993    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
4994        return self.func(expression.name, *expression.expressions)
4995
4996    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
4997        return self.anonymousaggfunc_sql(expression)
4998
4999    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5000        return self.parameterizedagg_sql(expression)
5001
5002    def show_sql(self, expression: exp.Show) -> str:
5003        self.unsupported("Unsupported SHOW statement")
5004        return ""
5005
5006    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5007        # Snowflake GET/PUT statements:
5008        #   PUT <file> <internalStage> <properties>
5009        #   GET <internalStage> <file> <properties>
5010        props = expression.args.get("properties")
5011        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5012        this = self.sql(expression, "this")
5013        target = self.sql(expression, "target")
5014
5015        if isinstance(expression, exp.Put):
5016            return f"PUT {this} {target}{props_sql}"
5017        else:
5018            return f"GET {target} {this}{props_sql}"
5019
5020    def translatecharacters_sql(self, expression: exp.TranslateCharacters):
5021        this = self.sql(expression, "this")
5022        expr = self.sql(expression, "expression")
5023        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5024        return f"TRANSLATE({this} USING {expr}{with_error})"
5025
5026    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5027        if self.SUPPORTS_DECODE_CASE:
5028            return self.func("DECODE", *expression.expressions)
5029
5030        expression, *expressions = expression.expressions
5031
5032        ifs = []
5033        for search, result in zip(expressions[::2], expressions[1::2]):
5034            if isinstance(search, exp.Literal):
5035                ifs.append(exp.If(this=expression.eq(search), true=result))
5036            elif isinstance(search, exp.Null):
5037                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5038            else:
5039                if isinstance(search, exp.Binary):
5040                    search = exp.paren(search)
5041
5042                cond = exp.or_(
5043                    expression.eq(search),
5044                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5045                    copy=False,
5046                )
5047                ifs.append(exp.If(this=cond, true=result))
5048
5049        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5050        return self.sql(case)

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

Apply generic preprocessing transformations to a given expression.

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