Edit on GitHub

sqlglot.generator

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

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

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

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

Arguments:
  • pretty: Whether to format the produced SQL string. Default: False.
  • identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
  • normalize: Whether to normalize identifiers to lowercase. Default: False.
  • pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
  • indent: The indentation size in a formatted string. For example, this affects the indentation of subqueries and filters under a WHERE clause. Default: 2.
  • normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
  • unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
  • max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
  • leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
  • max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
  • comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.Dialect, Type[sqlglot.dialects.Dialect], NoneType] = None)
735    def __init__(
736        self,
737        pretty: t.Optional[bool] = None,
738        identify: str | bool = False,
739        normalize: bool = False,
740        pad: int = 2,
741        indent: int = 2,
742        normalize_functions: t.Optional[str | bool] = None,
743        unsupported_level: ErrorLevel = ErrorLevel.WARN,
744        max_unsupported: int = 3,
745        leading_comma: bool = False,
746        max_text_width: int = 80,
747        comments: bool = True,
748        dialect: DialectType = None,
749    ):
750        import sqlglot
751        from sqlglot.dialects import Dialect
752
753        self.pretty = pretty if pretty is not None else sqlglot.pretty
754        self.identify = identify
755        self.normalize = normalize
756        self.pad = pad
757        self._indent = indent
758        self.unsupported_level = unsupported_level
759        self.max_unsupported = max_unsupported
760        self.leading_comma = leading_comma
761        self.max_text_width = max_text_width
762        self.comments = comments
763        self.dialect = Dialect.get_or_raise(dialect)
764
765        # This is both a Dialect property and a Generator argument, so we prioritize the latter
766        self.normalize_functions = (
767            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
768        )
769
770        self.unsupported_messages: t.List[str] = []
771        self._escaped_quote_end: str = (
772            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
773        )
774        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
775
776        self._next_name = name_sequence("_t")
777
778        self._identifier_start = self.dialect.IDENTIFIER_START
779        self._identifier_end = self.dialect.IDENTIFIER_END
780
781        self._quote_json_path_key_using_brackets = True
TRANSFORMS: Dict[Type[sqlglot.expressions.Expression], Callable[..., str]] = {<class 'sqlglot.expressions.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Uuid'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WeekStart'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ForceProperty'>: <function Generator.<lambda>>}
NULL_ORDERING_SUPPORTED: Optional[bool] = True
IGNORE_NULLS_IN_FUNC = False
LOCKING_READS_SUPPORTED = False
EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
WRAP_DERIVED_VALUES = True
CREATE_FUNCTION_RETURN_AS = True
MATCHED_BY_SOURCE = True
SINGLE_STRING_INTERVAL = False
INTERVAL_ALLOWS_PLURAL_FORM = True
LIMIT_FETCH = 'ALL'
LIMIT_ONLY_LITERALS = False
RENAME_TABLE_WITH_DB = True
GROUPINGS_SEP = ','
INDEX_ON = 'ON'
JOIN_HINTS = True
TABLE_HINTS = True
QUERY_HINTS = True
QUERY_HINT_SEP = ', '
IS_BOOL_ALLOWED = True
DUPLICATE_KEY_UPDATE_WITH_SET = True
LIMIT_IS_TOP = False
RETURNING_END = True
EXTRACT_ALLOWS_QUOTES = True
TZ_TO_WITH_TIME_ZONE = False
NVL2_SUPPORTED = True
SELECT_KINDS: Tuple[str, ...] = ('STRUCT', 'VALUE')
VALUES_AS_TABLE = True
ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
UNNEST_WITH_ORDINALITY = True
AGGREGATE_FILTER_SUPPORTED = True
SEMI_ANTI_JOIN_WITH_SIDE = True
COMPUTED_COLUMN_WITH_TYPE = True
SUPPORTS_TABLE_COPY = True
TABLESAMPLE_REQUIRES_PARENS = True
TABLESAMPLE_SIZE_IS_ROWS = True
TABLESAMPLE_KEYWORDS = 'TABLESAMPLE'
TABLESAMPLE_WITH_METHOD = True
TABLESAMPLE_SEED_KEYWORD = 'SEED'
COLLATE_IS_FUNC = False
DATA_TYPE_SPECIFIERS_ALLOWED = False
ENSURE_BOOLS = False
CTE_RECURSIVE_KEYWORD_REQUIRED = True
SUPPORTS_SINGLE_ARG_CONCAT = True
LAST_DAY_SUPPORTS_DATE_PART = True
SUPPORTS_TABLE_ALIAS_COLUMNS = True
UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
JSON_KEY_VALUE_PAIR_SEP = ':'
INSERT_OVERWRITE = ' OVERWRITE TABLE'
SUPPORTS_SELECT_INTO = False
SUPPORTS_UNLOGGED_TABLES = False
SUPPORTS_CREATE_TABLE_LIKE = True
LIKE_PROPERTY_INSIDE_SCHEMA = False
MULTI_ARG_DISTINCT = True
JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
JSON_PATH_BRACKETED_KEY_SUPPORTED = True
JSON_PATH_SINGLE_QUOTE_ESCAPE = False
CAN_IMPLEMENT_ARRAY_ANY = False
SUPPORTS_TO_NUMBER = True
SUPPORTS_WINDOW_EXCLUDE = False
SET_OP_MODIFIERS = True
COPY_PARAMS_ARE_WRAPPED = True
COPY_PARAMS_EQ_REQUIRED = False
COPY_HAS_INTO_KEYWORD = True
TRY_SUPPORTED = True
SUPPORTS_UESCAPE = True
UNICODE_SUBSTITUTE: Optional[Callable[[re.Match[str]], str]] = None
STAR_EXCEPT = 'EXCEPT'
HEX_FUNC = 'HEX'
WITH_PROPERTIES_PREFIX = 'WITH'
QUOTE_JSON_PATH = True
PAD_FILL_PATTERN_IS_REQUIRED = False
SUPPORTS_EXPLODING_PROJECTIONS = True
ARRAY_CONCAT_IS_VAR_LEN = True
SUPPORTS_CONVERT_TIMEZONE = False
SUPPORTS_MEDIAN = True
SUPPORTS_UNIX_SECONDS = False
ALTER_SET_WRAPPED = False
NORMALIZE_EXTRACT_DATE_PARTS = False
PARSE_JSON_NAME: Optional[str] = 'PARSE_JSON'
ARRAY_SIZE_NAME: str = 'ARRAY_LENGTH'
ALTER_SET_TYPE = 'SET DATA TYPE'
ARRAY_SIZE_DIM_REQUIRED: Optional[bool] = None
SUPPORTS_DECODE_CASE = True
SUPPORTS_BETWEEN_FLAGS = False
SUPPORTS_LIKE_QUANTIFIERS = True
MATCH_AGAINST_TABLE_PREFIX: Optional[str] = None
TYPE_MAPPING = {<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
UNSUPPORTED_TYPES: set[sqlglot.expressions.DataType.Type] = set()
TIME_PART_SINGULARS = {'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS = {'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
TOKEN_MAPPING: Dict[sqlglot.tokens.TokenType, str] = {}
STRUCT_DELIMITER = ('<', '>')
PARAMETER_TOKEN = '@'
NAMED_PLACEHOLDER_TOKEN = ':'
EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: Set[str] = set()
PROPERTIES_LOCATION = {<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
RESERVED_KEYWORDS: Set[str] = set()
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES = {<Type.NVARCHAR: 'NVARCHAR'>, <Type.CHAR: 'CHAR'>, <Type.VARCHAR: 'VARCHAR'>, <Type.NCHAR: 'NCHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: Set[Type[sqlglot.expressions.Expression]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: Tuple[Type[sqlglot.expressions.Expression], ...] = ()
SAFE_JSON_PATH_KEY_RE = re.compile('^[_a-zA-Z][\\w]*$')
SENTINEL_LINE_BREAK = '__SQLGLOT__LB__'
pretty
identify
normalize
pad
unsupported_level
max_unsupported
leading_comma
max_text_width
comments
dialect
normalize_functions
unsupported_messages: List[str]
def generate( self, expression: sqlglot.expressions.Expression, copy: bool = True) -> str:
783    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
784        """
785        Generates the SQL string corresponding to the given syntax tree.
786
787        Args:
788            expression: The syntax tree.
789            copy: Whether to copy the expression. The generator performs mutations so
790                it is safer to copy.
791
792        Returns:
793            The SQL string corresponding to `expression`.
794        """
795        if copy:
796            expression = expression.copy()
797
798        expression = self.preprocess(expression)
799
800        self.unsupported_messages = []
801        sql = self.sql(expression).strip()
802
803        if self.pretty:
804            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
805
806        if self.unsupported_level == ErrorLevel.IGNORE:
807            return sql
808
809        if self.unsupported_level == ErrorLevel.WARN:
810            for msg in self.unsupported_messages:
811                logger.warning(msg)
812        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
813            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
814
815        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:
817    def preprocess(self, expression: exp.Expression) -> exp.Expression:
818        """Apply generic preprocessing transformations to a given expression."""
819        expression = self._move_ctes_to_top_level(expression)
820
821        if self.ENSURE_BOOLS:
822            from sqlglot.transforms import ensure_bools
823
824            expression = ensure_bools(expression)
825
826        return expression

Apply generic preprocessing transformations to a given expression.

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