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: Always quote except for specials cases.
  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.Adjacent: lambda self, e: self.binary(e, "-|-"),
 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.CurrentCatalog: lambda *_: "CURRENT_CATALOG",
 142        exp.SessionUser: lambda *_: "SESSION_USER",
 143        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 144        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 145        exp.DynamicProperty: lambda *_: "DYNAMIC",
 146        exp.EmptyProperty: lambda *_: "EMPTY",
 147        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 148        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 149        exp.EphemeralColumnConstraint: lambda self,
 150        e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}",
 151        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 152        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 153        exp.Except: lambda self, e: self.set_operations(e),
 154        exp.ExternalProperty: lambda *_: "EXTERNAL",
 155        exp.Floor: lambda self, e: self.ceil_floor(e),
 156        exp.Get: lambda self, e: self.get_put_sql(e),
 157        exp.GlobalProperty: lambda *_: "GLOBAL",
 158        exp.HeapProperty: lambda *_: "HEAP",
 159        exp.IcebergProperty: lambda *_: "ICEBERG",
 160        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 161        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 162        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 163        exp.Intersect: lambda self, e: self.set_operations(e),
 164        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 165        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)),
 166        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 167        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 168        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 169        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 170        exp.LocationProperty: lambda self, e: self.naked_property(e),
 171        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 172        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 173        exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
 174        exp.NonClusteredColumnConstraint: lambda self,
 175        e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})",
 176        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 177        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 178        exp.OnCommitProperty: lambda _,
 179        e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS",
 180        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 181        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 182        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 183        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 184        exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
 185        exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
 186        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 187        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 188        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 189        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 190        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 191        exp.ProjectionPolicyColumnConstraint: lambda self,
 192        e: f"PROJECTION POLICY {self.sql(e, 'this')}",
 193        exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
 194        exp.Put: lambda self, e: self.get_put_sql(e),
 195        exp.RemoteWithConnectionModelProperty: lambda self,
 196        e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
 197        exp.ReturnsProperty: lambda self, e: (
 198            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 199        ),
 200        exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
 201        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 202        exp.SecureProperty: lambda *_: "SECURE",
 203        exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}",
 204        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 205        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 206        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 207        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 208        exp.SqlReadWriteProperty: lambda _, e: e.name,
 209        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
 210        exp.StabilityProperty: lambda _, e: e.name,
 211        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 212        exp.StreamingTableProperty: lambda *_: "STREAMING",
 213        exp.StrictProperty: lambda *_: "STRICT",
 214        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 215        exp.TableColumn: lambda self, e: self.sql(e.this),
 216        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 217        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 218        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 219        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 220        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 221        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 222        exp.TransientProperty: lambda *_: "TRANSIENT",
 223        exp.Union: lambda self, e: self.set_operations(e),
 224        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 225        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 226        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 227        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 228        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 229        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 230        exp.UtcTimestamp: lambda self, e: self.sql(
 231            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 232        ),
 233        exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
 234        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 235        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 236        exp.VolatileProperty: lambda *_: "VOLATILE",
 237        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 238        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 239        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 240        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 241        exp.ForceProperty: lambda *_: "FORCE",
 242    }
 243
 244    # Whether null ordering is supported in order by
 245    # True: Full Support, None: No support, False: No support for certain cases
 246    # such as window specifications, aggregate functions etc
 247    NULL_ORDERING_SUPPORTED: t.Optional[bool] = True
 248
 249    # Whether ignore nulls is inside the agg or outside.
 250    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 251    IGNORE_NULLS_IN_FUNC = False
 252
 253    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 254    LOCKING_READS_SUPPORTED = False
 255
 256    # Whether the EXCEPT and INTERSECT operations can return duplicates
 257    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 258
 259    # Wrap derived values in parens, usually standard but spark doesn't support it
 260    WRAP_DERIVED_VALUES = True
 261
 262    # Whether create function uses an AS before the RETURN
 263    CREATE_FUNCTION_RETURN_AS = True
 264
 265    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 266    MATCHED_BY_SOURCE = True
 267
 268    # Whether the INTERVAL expression works only with values like '1 day'
 269    SINGLE_STRING_INTERVAL = False
 270
 271    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 272    INTERVAL_ALLOWS_PLURAL_FORM = True
 273
 274    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 275    LIMIT_FETCH = "ALL"
 276
 277    # Whether limit and fetch allows expresions or just limits
 278    LIMIT_ONLY_LITERALS = False
 279
 280    # Whether a table is allowed to be renamed with a db
 281    RENAME_TABLE_WITH_DB = True
 282
 283    # The separator for grouping sets and rollups
 284    GROUPINGS_SEP = ","
 285
 286    # The string used for creating an index on a table
 287    INDEX_ON = "ON"
 288
 289    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 290    INOUT_SEPARATOR = " "
 291
 292    # Whether join hints should be generated
 293    JOIN_HINTS = True
 294
 295    # Whether directed joins are supported
 296    DIRECTED_JOINS = False
 297
 298    # Whether table hints should be generated
 299    TABLE_HINTS = True
 300
 301    # Whether query hints should be generated
 302    QUERY_HINTS = True
 303
 304    # What kind of separator to use for query hints
 305    QUERY_HINT_SEP = ", "
 306
 307    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 308    IS_BOOL_ALLOWED = True
 309
 310    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 311    DUPLICATE_KEY_UPDATE_WITH_SET = True
 312
 313    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 314    LIMIT_IS_TOP = False
 315
 316    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 317    RETURNING_END = True
 318
 319    # Whether to generate an unquoted value for EXTRACT's date part argument
 320    EXTRACT_ALLOWS_QUOTES = True
 321
 322    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 323    TZ_TO_WITH_TIME_ZONE = False
 324
 325    # Whether the NVL2 function is supported
 326    NVL2_SUPPORTED = True
 327
 328    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 329    SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE")
 330
 331    # Whether VALUES statements can be used as derived tables.
 332    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 333    # SELECT * VALUES into SELECT UNION
 334    VALUES_AS_TABLE = True
 335
 336    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 337    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 338
 339    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 340    UNNEST_WITH_ORDINALITY = True
 341
 342    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 343    AGGREGATE_FILTER_SUPPORTED = True
 344
 345    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 346    SEMI_ANTI_JOIN_WITH_SIDE = True
 347
 348    # Whether to include the type of a computed column in the CREATE DDL
 349    COMPUTED_COLUMN_WITH_TYPE = True
 350
 351    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 352    SUPPORTS_TABLE_COPY = True
 353
 354    # Whether parentheses are required around the table sample's expression
 355    TABLESAMPLE_REQUIRES_PARENS = True
 356
 357    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 358    TABLESAMPLE_SIZE_IS_ROWS = True
 359
 360    # The keyword(s) to use when generating a sample clause
 361    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 362
 363    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 364    TABLESAMPLE_WITH_METHOD = True
 365
 366    # The keyword to use when specifying the seed of a sample clause
 367    TABLESAMPLE_SEED_KEYWORD = "SEED"
 368
 369    # Whether COLLATE is a function instead of a binary operator
 370    COLLATE_IS_FUNC = False
 371
 372    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 373    DATA_TYPE_SPECIFIERS_ALLOWED = False
 374
 375    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 376    ENSURE_BOOLS = False
 377
 378    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 379    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 380
 381    # Whether CONCAT requires >1 arguments
 382    SUPPORTS_SINGLE_ARG_CONCAT = True
 383
 384    # Whether LAST_DAY function supports a date part argument
 385    LAST_DAY_SUPPORTS_DATE_PART = True
 386
 387    # Whether named columns are allowed in table aliases
 388    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 389
 390    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 391    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 392
 393    # What delimiter to use for separating JSON key/value pairs
 394    JSON_KEY_VALUE_PAIR_SEP = ":"
 395
 396    # INSERT OVERWRITE TABLE x override
 397    INSERT_OVERWRITE = " OVERWRITE TABLE"
 398
 399    # Whether the SELECT .. INTO syntax is used instead of CTAS
 400    SUPPORTS_SELECT_INTO = False
 401
 402    # Whether UNLOGGED tables can be created
 403    SUPPORTS_UNLOGGED_TABLES = False
 404
 405    # Whether the CREATE TABLE LIKE statement is supported
 406    SUPPORTS_CREATE_TABLE_LIKE = True
 407
 408    # Whether the LikeProperty needs to be specified inside of the schema clause
 409    LIKE_PROPERTY_INSIDE_SCHEMA = False
 410
 411    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 412    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 413    MULTI_ARG_DISTINCT = True
 414
 415    # Whether the JSON extraction operators expect a value of type JSON
 416    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 417
 418    # Whether bracketed keys like ["foo"] are supported in JSON paths
 419    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 420
 421    # Whether to escape keys using single quotes in JSON paths
 422    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 423
 424    # The JSONPathPart expressions supported by this dialect
 425    SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy()
 426
 427    # Whether any(f(x) for x in array) can be implemented by this dialect
 428    CAN_IMPLEMENT_ARRAY_ANY = False
 429
 430    # Whether the function TO_NUMBER is supported
 431    SUPPORTS_TO_NUMBER = True
 432
 433    # Whether EXCLUDE in window specification is supported
 434    SUPPORTS_WINDOW_EXCLUDE = False
 435
 436    # Whether or not set op modifiers apply to the outer set op or select.
 437    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 438    # True means limit 1 happens after the set op, False means it it happens on y.
 439    SET_OP_MODIFIERS = True
 440
 441    # Whether parameters from COPY statement are wrapped in parentheses
 442    COPY_PARAMS_ARE_WRAPPED = True
 443
 444    # Whether values of params are set with "=" token or empty space
 445    COPY_PARAMS_EQ_REQUIRED = False
 446
 447    # Whether COPY statement has INTO keyword
 448    COPY_HAS_INTO_KEYWORD = True
 449
 450    # Whether the conditional TRY(expression) function is supported
 451    TRY_SUPPORTED = True
 452
 453    # Whether the UESCAPE syntax in unicode strings is supported
 454    SUPPORTS_UESCAPE = True
 455
 456    # Function used to replace escaped unicode codes in unicode strings
 457    UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None
 458
 459    # The keyword to use when generating a star projection with excluded columns
 460    STAR_EXCEPT = "EXCEPT"
 461
 462    # The HEX function name
 463    HEX_FUNC = "HEX"
 464
 465    # The keywords to use when prefixing & separating WITH based properties
 466    WITH_PROPERTIES_PREFIX = "WITH"
 467
 468    # Whether to quote the generated expression of exp.JsonPath
 469    QUOTE_JSON_PATH = True
 470
 471    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 472    PAD_FILL_PATTERN_IS_REQUIRED = False
 473
 474    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 475    SUPPORTS_EXPLODING_PROJECTIONS = True
 476
 477    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 478    ARRAY_CONCAT_IS_VAR_LEN = True
 479
 480    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 481    SUPPORTS_CONVERT_TIMEZONE = False
 482
 483    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 484    SUPPORTS_MEDIAN = True
 485
 486    # Whether UNIX_SECONDS(timestamp) is supported
 487    SUPPORTS_UNIX_SECONDS = False
 488
 489    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 490    ALTER_SET_WRAPPED = False
 491
 492    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 493    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 494    # TODO: The normalization should be done by default once we've tested it across all dialects.
 495    NORMALIZE_EXTRACT_DATE_PARTS = False
 496
 497    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 498    PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
 499
 500    # The function name of the exp.ArraySize expression
 501    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 502
 503    # The syntax to use when altering the type of a column
 504    ALTER_SET_TYPE = "SET DATA TYPE"
 505
 506    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 507    # None -> Doesn't support it at all
 508    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 509    # True (Postgres) -> Explicitly requires it
 510    ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None
 511
 512    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 513    SUPPORTS_DECODE_CASE = True
 514
 515    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 516    SUPPORTS_BETWEEN_FLAGS = False
 517
 518    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 519    SUPPORTS_LIKE_QUANTIFIERS = True
 520
 521    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 522    MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None
 523
 524    # Whether to include the VARIABLE keyword for SET assignments
 525    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 526
 527    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 528    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 529    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 530    UPDATE_STATEMENT_SUPPORTS_FROM = True
 531
 532    TYPE_MAPPING = {
 533        exp.DataType.Type.DATETIME2: "TIMESTAMP",
 534        exp.DataType.Type.NCHAR: "CHAR",
 535        exp.DataType.Type.NVARCHAR: "VARCHAR",
 536        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 537        exp.DataType.Type.LONGTEXT: "TEXT",
 538        exp.DataType.Type.TINYTEXT: "TEXT",
 539        exp.DataType.Type.BLOB: "VARBINARY",
 540        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 541        exp.DataType.Type.LONGBLOB: "BLOB",
 542        exp.DataType.Type.TINYBLOB: "BLOB",
 543        exp.DataType.Type.INET: "INET",
 544        exp.DataType.Type.ROWVERSION: "VARBINARY",
 545        exp.DataType.Type.SMALLDATETIME: "TIMESTAMP",
 546    }
 547
 548    UNSUPPORTED_TYPES: set[exp.DataType.Type] = set()
 549
 550    TIME_PART_SINGULARS = {
 551        "MICROSECONDS": "MICROSECOND",
 552        "SECONDS": "SECOND",
 553        "MINUTES": "MINUTE",
 554        "HOURS": "HOUR",
 555        "DAYS": "DAY",
 556        "WEEKS": "WEEK",
 557        "MONTHS": "MONTH",
 558        "QUARTERS": "QUARTER",
 559        "YEARS": "YEAR",
 560    }
 561
 562    AFTER_HAVING_MODIFIER_TRANSFORMS = {
 563        "cluster": lambda self, e: self.sql(e, "cluster"),
 564        "distribute": lambda self, e: self.sql(e, "distribute"),
 565        "sort": lambda self, e: self.sql(e, "sort"),
 566        "windows": lambda self, e: (
 567            self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
 568            if e.args.get("windows")
 569            else ""
 570        ),
 571        "qualify": lambda self, e: self.sql(e, "qualify"),
 572    }
 573
 574    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 575
 576    STRUCT_DELIMITER = ("<", ">")
 577
 578    PARAMETER_TOKEN = "@"
 579    NAMED_PLACEHOLDER_TOKEN = ":"
 580
 581    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set()
 582
 583    PROPERTIES_LOCATION = {
 584        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 585        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 586        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 587        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 588        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 589        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 590        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 591        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 592        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 593        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 594        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 595        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 596        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 597        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 598        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 599        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 600        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 601        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 602        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 603        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 604        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 605        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 606        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 607        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 608        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 609        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 610        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 611        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 612        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 613        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 614        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 615        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 616        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 617        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 618        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 619        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 620        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 621        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 622        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 623        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 624        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 625        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 626        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 627        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 628        exp.LogProperty: exp.Properties.Location.POST_NAME,
 629        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 630        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 631        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 632        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 633        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 634        exp.Order: exp.Properties.Location.POST_SCHEMA,
 635        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 636        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 637        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 638        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 639        exp.Property: exp.Properties.Location.POST_WITH,
 640        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 641        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 642        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 643        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 644        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 645        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 646        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 647        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 648        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 649        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 650        exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA,
 651        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 652        exp.Set: exp.Properties.Location.POST_SCHEMA,
 653        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 654        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 655        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 656        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 657        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 658        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 659        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 660        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 661        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 662        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 663        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 664        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 665        exp.Tags: exp.Properties.Location.POST_WITH,
 666        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 667        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 668        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 669        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 670        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 671        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 672        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 673        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 674        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 675        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 676        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 677        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 678        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 679        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 680        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 681    }
 682
 683    # Keywords that can't be used as unquoted identifier names
 684    RESERVED_KEYWORDS: t.Set[str] = set()
 685
 686    # Expressions whose comments are separated from them for better formatting
 687    WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 688        exp.Command,
 689        exp.Create,
 690        exp.Describe,
 691        exp.Delete,
 692        exp.Drop,
 693        exp.From,
 694        exp.Insert,
 695        exp.Join,
 696        exp.MultitableInserts,
 697        exp.Order,
 698        exp.Group,
 699        exp.Having,
 700        exp.Select,
 701        exp.SetOperation,
 702        exp.Update,
 703        exp.Where,
 704        exp.With,
 705    )
 706
 707    # Expressions that should not have their comments generated in maybe_comment
 708    EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 709        exp.Binary,
 710        exp.SetOperation,
 711    )
 712
 713    # Expressions that can remain unwrapped when appearing in the context of an INTERVAL
 714    UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = (
 715        exp.Column,
 716        exp.Literal,
 717        exp.Neg,
 718        exp.Paren,
 719    )
 720
 721    PARAMETERIZABLE_TEXT_TYPES = {
 722        exp.DataType.Type.NVARCHAR,
 723        exp.DataType.Type.VARCHAR,
 724        exp.DataType.Type.CHAR,
 725        exp.DataType.Type.NCHAR,
 726    }
 727
 728    # Expressions that need to have all CTEs under them bubbled up to them
 729    EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set()
 730
 731    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = ()
 732
 733    SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE
 734
 735    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 736
 737    __slots__ = (
 738        "pretty",
 739        "identify",
 740        "normalize",
 741        "pad",
 742        "_indent",
 743        "normalize_functions",
 744        "unsupported_level",
 745        "max_unsupported",
 746        "leading_comma",
 747        "max_text_width",
 748        "comments",
 749        "dialect",
 750        "unsupported_messages",
 751        "_escaped_quote_end",
 752        "_escaped_byte_quote_end",
 753        "_escaped_identifier_end",
 754        "_next_name",
 755        "_identifier_start",
 756        "_identifier_end",
 757        "_quote_json_path_key_using_brackets",
 758    )
 759
 760    def __init__(
 761        self,
 762        pretty: t.Optional[bool] = None,
 763        identify: str | bool = False,
 764        normalize: bool = False,
 765        pad: int = 2,
 766        indent: int = 2,
 767        normalize_functions: t.Optional[str | bool] = None,
 768        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 769        max_unsupported: int = 3,
 770        leading_comma: bool = False,
 771        max_text_width: int = 80,
 772        comments: bool = True,
 773        dialect: DialectType = None,
 774    ):
 775        import sqlglot
 776        from sqlglot.dialects import Dialect
 777
 778        self.pretty = pretty if pretty is not None else sqlglot.pretty
 779        self.identify = identify
 780        self.normalize = normalize
 781        self.pad = pad
 782        self._indent = indent
 783        self.unsupported_level = unsupported_level
 784        self.max_unsupported = max_unsupported
 785        self.leading_comma = leading_comma
 786        self.max_text_width = max_text_width
 787        self.comments = comments
 788        self.dialect = Dialect.get_or_raise(dialect)
 789
 790        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 791        self.normalize_functions = (
 792            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 793        )
 794
 795        self.unsupported_messages: t.List[str] = []
 796        self._escaped_quote_end: str = (
 797            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 798        )
 799        self._escaped_byte_quote_end: str = (
 800            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 801            if self.dialect.BYTE_END
 802            else ""
 803        )
 804        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 805
 806        self._next_name = name_sequence("_t")
 807
 808        self._identifier_start = self.dialect.IDENTIFIER_START
 809        self._identifier_end = self.dialect.IDENTIFIER_END
 810
 811        self._quote_json_path_key_using_brackets = True
 812
 813    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
 814        """
 815        Generates the SQL string corresponding to the given syntax tree.
 816
 817        Args:
 818            expression: The syntax tree.
 819            copy: Whether to copy the expression. The generator performs mutations so
 820                it is safer to copy.
 821
 822        Returns:
 823            The SQL string corresponding to `expression`.
 824        """
 825        if copy:
 826            expression = expression.copy()
 827
 828        expression = self.preprocess(expression)
 829
 830        self.unsupported_messages = []
 831        sql = self.sql(expression).strip()
 832
 833        if self.pretty:
 834            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 835
 836        if self.unsupported_level == ErrorLevel.IGNORE:
 837            return sql
 838
 839        if self.unsupported_level == ErrorLevel.WARN:
 840            for msg in self.unsupported_messages:
 841                logger.warning(msg)
 842        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 843            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 844
 845        return sql
 846
 847    def preprocess(self, expression: exp.Expression) -> exp.Expression:
 848        """Apply generic preprocessing transformations to a given expression."""
 849        expression = self._move_ctes_to_top_level(expression)
 850
 851        if self.ENSURE_BOOLS:
 852            from sqlglot.transforms import ensure_bools
 853
 854            expression = ensure_bools(expression)
 855
 856        return expression
 857
 858    def _move_ctes_to_top_level(self, expression: E) -> E:
 859        if (
 860            not expression.parent
 861            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 862            and any(node.parent is not expression for node in expression.find_all(exp.With))
 863        ):
 864            from sqlglot.transforms import move_ctes_to_top_level
 865
 866            expression = move_ctes_to_top_level(expression)
 867        return expression
 868
 869    def unsupported(self, message: str) -> None:
 870        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 871            raise UnsupportedError(message)
 872        self.unsupported_messages.append(message)
 873
 874    def sep(self, sep: str = " ") -> str:
 875        return f"{sep.strip()}\n" if self.pretty else sep
 876
 877    def seg(self, sql: str, sep: str = " ") -> str:
 878        return f"{self.sep(sep)}{sql}"
 879
 880    def sanitize_comment(self, comment: str) -> str:
 881        comment = " " + comment if comment[0].strip() else comment
 882        comment = comment + " " if comment[-1].strip() else comment
 883
 884        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
 885            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
 886            comment = comment.replace("*/", "* /")
 887
 888        return comment
 889
 890    def maybe_comment(
 891        self,
 892        sql: str,
 893        expression: t.Optional[exp.Expression] = None,
 894        comments: t.Optional[t.List[str]] = None,
 895        separated: bool = False,
 896    ) -> str:
 897        comments = (
 898            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 899            if self.comments
 900            else None
 901        )
 902
 903        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 904            return sql
 905
 906        comments_sql = " ".join(
 907            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
 908        )
 909
 910        if not comments_sql:
 911            return sql
 912
 913        comments_sql = self._replace_line_breaks(comments_sql)
 914
 915        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 916            return (
 917                f"{self.sep()}{comments_sql}{sql}"
 918                if not sql or sql[0].isspace()
 919                else f"{comments_sql}{self.sep()}{sql}"
 920            )
 921
 922        return f"{sql} {comments_sql}"
 923
 924    def wrap(self, expression: exp.Expression | str) -> str:
 925        this_sql = (
 926            self.sql(expression)
 927            if isinstance(expression, exp.UNWRAPPED_QUERIES)
 928            else self.sql(expression, "this")
 929        )
 930        if not this_sql:
 931            return "()"
 932
 933        this_sql = self.indent(this_sql, level=1, pad=0)
 934        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 935
 936    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 937        original = self.identify
 938        self.identify = False
 939        result = func(*args, **kwargs)
 940        self.identify = original
 941        return result
 942
 943    def normalize_func(self, name: str) -> str:
 944        if self.normalize_functions == "upper" or self.normalize_functions is True:
 945            return name.upper()
 946        if self.normalize_functions == "lower":
 947            return name.lower()
 948        return name
 949
 950    def indent(
 951        self,
 952        sql: str,
 953        level: int = 0,
 954        pad: t.Optional[int] = None,
 955        skip_first: bool = False,
 956        skip_last: bool = False,
 957    ) -> str:
 958        if not self.pretty or not sql:
 959            return sql
 960
 961        pad = self.pad if pad is None else pad
 962        lines = sql.split("\n")
 963
 964        return "\n".join(
 965            (
 966                line
 967                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 968                else f"{' ' * (level * self._indent + pad)}{line}"
 969            )
 970            for i, line in enumerate(lines)
 971        )
 972
 973    def sql(
 974        self,
 975        expression: t.Optional[str | exp.Expression],
 976        key: t.Optional[str] = None,
 977        comment: bool = True,
 978    ) -> str:
 979        if not expression:
 980            return ""
 981
 982        if isinstance(expression, str):
 983            return expression
 984
 985        if key:
 986            value = expression.args.get(key)
 987            if value:
 988                return self.sql(value)
 989            return ""
 990
 991        transform = self.TRANSFORMS.get(expression.__class__)
 992
 993        if callable(transform):
 994            sql = transform(self, expression)
 995        elif isinstance(expression, exp.Expression):
 996            exp_handler_name = f"{expression.key}_sql"
 997
 998            if hasattr(self, exp_handler_name):
 999                sql = getattr(self, exp_handler_name)(expression)
1000            elif isinstance(expression, exp.Func):
1001                sql = self.function_fallback_sql(expression)
1002            elif isinstance(expression, exp.Property):
1003                sql = self.property_sql(expression)
1004            else:
1005                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1006        else:
1007            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
1008
1009        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1010
1011    def uncache_sql(self, expression: exp.Uncache) -> str:
1012        table = self.sql(expression, "this")
1013        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1014        return f"UNCACHE TABLE{exists_sql} {table}"
1015
1016    def cache_sql(self, expression: exp.Cache) -> str:
1017        lazy = " LAZY" if expression.args.get("lazy") else ""
1018        table = self.sql(expression, "this")
1019        options = expression.args.get("options")
1020        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1021        sql = self.sql(expression, "expression")
1022        sql = f" AS{self.sep()}{sql}" if sql else ""
1023        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1024        return self.prepend_ctes(expression, sql)
1025
1026    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1027        if isinstance(expression.parent, exp.Cast):
1028            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
1029        default = "DEFAULT " if expression.args.get("default") else ""
1030        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1031
1032    def column_parts(self, expression: exp.Column) -> str:
1033        return ".".join(
1034            self.sql(part)
1035            for part in (
1036                expression.args.get("catalog"),
1037                expression.args.get("db"),
1038                expression.args.get("table"),
1039                expression.args.get("this"),
1040            )
1041            if part
1042        )
1043
1044    def column_sql(self, expression: exp.Column) -> str:
1045        join_mark = " (+)" if expression.args.get("join_mark") else ""
1046
1047        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1048            join_mark = ""
1049            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1050
1051        return f"{self.column_parts(expression)}{join_mark}"
1052
1053    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1054        return self.column_sql(expression)
1055
1056    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1057        this = self.sql(expression, "this")
1058        this = f" {this}" if this else ""
1059        position = self.sql(expression, "position")
1060        return f"{position}{this}"
1061
1062    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1063        column = self.sql(expression, "this")
1064        kind = self.sql(expression, "kind")
1065        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1066        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1067        kind = f"{sep}{kind}" if kind else ""
1068        constraints = f" {constraints}" if constraints else ""
1069        position = self.sql(expression, "position")
1070        position = f" {position}" if position else ""
1071
1072        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1073            kind = ""
1074
1075        return f"{exists}{column}{kind}{constraints}{position}"
1076
1077    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1078        this = self.sql(expression, "this")
1079        kind_sql = self.sql(expression, "kind").strip()
1080        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1081
1082    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1083        this = self.sql(expression, "this")
1084        if expression.args.get("not_null"):
1085            persisted = " PERSISTED NOT NULL"
1086        elif expression.args.get("persisted"):
1087            persisted = " PERSISTED"
1088        else:
1089            persisted = ""
1090
1091        return f"AS {this}{persisted}"
1092
1093    def autoincrementcolumnconstraint_sql(self, _) -> str:
1094        return self.token_sql(TokenType.AUTO_INCREMENT)
1095
1096    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1097        if isinstance(expression.this, list):
1098            this = self.wrap(self.expressions(expression, key="this", flat=True))
1099        else:
1100            this = self.sql(expression, "this")
1101
1102        return f"COMPRESS {this}"
1103
1104    def generatedasidentitycolumnconstraint_sql(
1105        self, expression: exp.GeneratedAsIdentityColumnConstraint
1106    ) -> str:
1107        this = ""
1108        if expression.this is not None:
1109            on_null = " ON NULL" if expression.args.get("on_null") else ""
1110            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1111
1112        start = expression.args.get("start")
1113        start = f"START WITH {start}" if start else ""
1114        increment = expression.args.get("increment")
1115        increment = f" INCREMENT BY {increment}" if increment else ""
1116        minvalue = expression.args.get("minvalue")
1117        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1118        maxvalue = expression.args.get("maxvalue")
1119        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1120        cycle = expression.args.get("cycle")
1121        cycle_sql = ""
1122
1123        if cycle is not None:
1124            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1125            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1126
1127        sequence_opts = ""
1128        if start or increment or cycle_sql:
1129            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1130            sequence_opts = f" ({sequence_opts.strip()})"
1131
1132        expr = self.sql(expression, "expression")
1133        expr = f"({expr})" if expr else "IDENTITY"
1134
1135        return f"GENERATED{this} AS {expr}{sequence_opts}"
1136
1137    def generatedasrowcolumnconstraint_sql(
1138        self, expression: exp.GeneratedAsRowColumnConstraint
1139    ) -> str:
1140        start = "START" if expression.args.get("start") else "END"
1141        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1142        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1143
1144    def periodforsystemtimeconstraint_sql(
1145        self, expression: exp.PeriodForSystemTimeConstraint
1146    ) -> str:
1147        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1148
1149    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1150        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1151
1152    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1153        desc = expression.args.get("desc")
1154        if desc is not None:
1155            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1156        options = self.expressions(expression, key="options", flat=True, sep=" ")
1157        options = f" {options}" if options else ""
1158        return f"PRIMARY KEY{options}"
1159
1160    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1161        this = self.sql(expression, "this")
1162        this = f" {this}" if this else ""
1163        index_type = expression.args.get("index_type")
1164        index_type = f" USING {index_type}" if index_type else ""
1165        on_conflict = self.sql(expression, "on_conflict")
1166        on_conflict = f" {on_conflict}" if on_conflict else ""
1167        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1168        options = self.expressions(expression, key="options", flat=True, sep=" ")
1169        options = f" {options}" if options else ""
1170        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1171
1172    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1173        input_ = expression.args.get("input_")
1174        output = expression.args.get("output")
1175        variadic = expression.args.get("variadic")
1176
1177        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1178        if variadic:
1179            return "VARIADIC"
1180
1181        if input_ and output:
1182            return f"IN{self.INOUT_SEPARATOR}OUT"
1183        if input_:
1184            return "IN"
1185        if output:
1186            return "OUT"
1187
1188        return ""
1189
1190    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1191        return self.sql(expression, "this")
1192
1193    def create_sql(self, expression: exp.Create) -> str:
1194        kind = self.sql(expression, "kind")
1195        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1196        properties = expression.args.get("properties")
1197        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1198
1199        this = self.createable_sql(expression, properties_locs)
1200
1201        properties_sql = ""
1202        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1203            exp.Properties.Location.POST_WITH
1204        ):
1205            props_ast = exp.Properties(
1206                expressions=[
1207                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1208                    *properties_locs[exp.Properties.Location.POST_WITH],
1209                ]
1210            )
1211            props_ast.parent = expression
1212            properties_sql = self.sql(props_ast)
1213
1214            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1215                properties_sql = self.sep() + properties_sql
1216            elif not self.pretty:
1217                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1218                properties_sql = f" {properties_sql}"
1219
1220        begin = " BEGIN" if expression.args.get("begin") else ""
1221        end = " END" if expression.args.get("end") else ""
1222
1223        expression_sql = self.sql(expression, "expression")
1224        if expression_sql:
1225            expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
1226
1227            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1228                postalias_props_sql = ""
1229                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1230                    postalias_props_sql = self.properties(
1231                        exp.Properties(
1232                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1233                        ),
1234                        wrapped=False,
1235                    )
1236                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1237                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1238
1239        postindex_props_sql = ""
1240        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1241            postindex_props_sql = self.properties(
1242                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1243                wrapped=False,
1244                prefix=" ",
1245            )
1246
1247        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1248        indexes = f" {indexes}" if indexes else ""
1249        index_sql = indexes + postindex_props_sql
1250
1251        replace = " OR REPLACE" if expression.args.get("replace") else ""
1252        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1253        unique = " UNIQUE" if expression.args.get("unique") else ""
1254
1255        clustered = expression.args.get("clustered")
1256        if clustered is None:
1257            clustered_sql = ""
1258        elif clustered:
1259            clustered_sql = " CLUSTERED COLUMNSTORE"
1260        else:
1261            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1262
1263        postcreate_props_sql = ""
1264        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1265            postcreate_props_sql = self.properties(
1266                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1267                sep=" ",
1268                prefix=" ",
1269                wrapped=False,
1270            )
1271
1272        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1273
1274        postexpression_props_sql = ""
1275        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1276            postexpression_props_sql = self.properties(
1277                exp.Properties(
1278                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1279                ),
1280                sep=" ",
1281                prefix=" ",
1282                wrapped=False,
1283            )
1284
1285        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1286        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1287        no_schema_binding = (
1288            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1289        )
1290
1291        clone = self.sql(expression, "clone")
1292        clone = f" {clone}" if clone else ""
1293
1294        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1295            properties_expression = f"{expression_sql}{properties_sql}"
1296        else:
1297            properties_expression = f"{properties_sql}{expression_sql}"
1298
1299        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1300        return self.prepend_ctes(expression, expression_sql)
1301
1302    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1303        start = self.sql(expression, "start")
1304        start = f"START WITH {start}" if start else ""
1305        increment = self.sql(expression, "increment")
1306        increment = f" INCREMENT BY {increment}" if increment else ""
1307        minvalue = self.sql(expression, "minvalue")
1308        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1309        maxvalue = self.sql(expression, "maxvalue")
1310        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1311        owned = self.sql(expression, "owned")
1312        owned = f" OWNED BY {owned}" if owned else ""
1313
1314        cache = expression.args.get("cache")
1315        if cache is None:
1316            cache_str = ""
1317        elif cache is True:
1318            cache_str = " CACHE"
1319        else:
1320            cache_str = f" CACHE {cache}"
1321
1322        options = self.expressions(expression, key="options", flat=True, sep=" ")
1323        options = f" {options}" if options else ""
1324
1325        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1326
1327    def clone_sql(self, expression: exp.Clone) -> str:
1328        this = self.sql(expression, "this")
1329        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1330        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1331        return f"{shallow}{keyword} {this}"
1332
1333    def describe_sql(self, expression: exp.Describe) -> str:
1334        style = expression.args.get("style")
1335        style = f" {style}" if style else ""
1336        partition = self.sql(expression, "partition")
1337        partition = f" {partition}" if partition else ""
1338        format = self.sql(expression, "format")
1339        format = f" {format}" if format else ""
1340        as_json = " AS JSON" if expression.args.get("as_json") else ""
1341
1342        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1343
1344    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1345        tag = self.sql(expression, "tag")
1346        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1347
1348    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1349        with_ = self.sql(expression, "with_")
1350        if with_:
1351            sql = f"{with_}{self.sep()}{sql}"
1352        return sql
1353
1354    def with_sql(self, expression: exp.With) -> str:
1355        sql = self.expressions(expression, flat=True)
1356        recursive = (
1357            "RECURSIVE "
1358            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1359            else ""
1360        )
1361        search = self.sql(expression, "search")
1362        search = f" {search}" if search else ""
1363
1364        return f"WITH {recursive}{sql}{search}"
1365
1366    def cte_sql(self, expression: exp.CTE) -> str:
1367        alias = expression.args.get("alias")
1368        if alias:
1369            alias.add_comments(expression.pop_comments())
1370
1371        alias_sql = self.sql(expression, "alias")
1372
1373        materialized = expression.args.get("materialized")
1374        if materialized is False:
1375            materialized = "NOT MATERIALIZED "
1376        elif materialized:
1377            materialized = "MATERIALIZED "
1378
1379        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1380        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1381
1382        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1383
1384    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1385        alias = self.sql(expression, "this")
1386        columns = self.expressions(expression, key="columns", flat=True)
1387        columns = f"({columns})" if columns else ""
1388
1389        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1390            columns = ""
1391            self.unsupported("Named columns are not supported in table alias.")
1392
1393        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1394            alias = self._next_name()
1395
1396        return f"{alias}{columns}"
1397
1398    def bitstring_sql(self, expression: exp.BitString) -> str:
1399        this = self.sql(expression, "this")
1400        if self.dialect.BIT_START:
1401            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1402        return f"{int(this, 2)}"
1403
1404    def hexstring_sql(
1405        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1406    ) -> str:
1407        this = self.sql(expression, "this")
1408        is_integer_type = expression.args.get("is_integer")
1409
1410        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1411            not self.dialect.HEX_START and not binary_function_repr
1412        ):
1413            # Integer representation will be returned if:
1414            # - The read dialect treats the hex value as integer literal but not the write
1415            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1416            return f"{int(this, 16)}"
1417
1418        if not is_integer_type:
1419            # Read dialect treats the hex value as BINARY/BLOB
1420            if binary_function_repr:
1421                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1422                return self.func(binary_function_repr, exp.Literal.string(this))
1423            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1424                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1425                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1426
1427        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1428
1429    def bytestring_sql(self, expression: exp.ByteString) -> str:
1430        this = self.sql(expression, "this")
1431        if self.dialect.BYTE_START:
1432            escaped_byte_string = self.escape_str(
1433                this,
1434                escape_backslash=False,
1435                delimiter=self.dialect.BYTE_END,
1436                escaped_delimiter=self._escaped_byte_quote_end,
1437                is_byte_string=True,
1438            )
1439            is_bytes = expression.args.get("is_bytes", False)
1440            delimited_byte_string = (
1441                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1442            )
1443            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1444                return self.sql(
1445                    exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect)
1446                )
1447            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1448                return self.sql(
1449                    exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect)
1450                )
1451
1452            return delimited_byte_string
1453        return this
1454
1455    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1456        this = self.sql(expression, "this")
1457        escape = expression.args.get("escape")
1458
1459        if self.dialect.UNICODE_START:
1460            escape_substitute = r"\\\1"
1461            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1462        else:
1463            escape_substitute = r"\\u\1"
1464            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1465
1466        if escape:
1467            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1468            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1469        else:
1470            escape_pattern = ESCAPED_UNICODE_RE
1471            escape_sql = ""
1472
1473        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1474            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1475
1476        return f"{left_quote}{this}{right_quote}{escape_sql}"
1477
1478    def rawstring_sql(self, expression: exp.RawString) -> str:
1479        string = expression.this
1480        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1481            string = string.replace("\\", "\\\\")
1482
1483        string = self.escape_str(string, escape_backslash=False)
1484        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1485
1486    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1487        this = self.sql(expression, "this")
1488        specifier = self.sql(expression, "expression")
1489        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1490        return f"{this}{specifier}"
1491
1492    def datatype_sql(self, expression: exp.DataType) -> str:
1493        nested = ""
1494        values = ""
1495
1496        expr_nested = expression.args.get("nested")
1497        interior = (
1498            self.expressions(
1499                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1500            )
1501            if expr_nested and self.pretty
1502            else self.expressions(expression, flat=True)
1503        )
1504
1505        type_value = expression.this
1506        if type_value in self.UNSUPPORTED_TYPES:
1507            self.unsupported(
1508                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1509            )
1510
1511        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1512            type_sql = self.sql(expression, "kind")
1513        else:
1514            type_sql = (
1515                self.TYPE_MAPPING.get(type_value, type_value.value)
1516                if isinstance(type_value, exp.DataType.Type)
1517                else type_value
1518            )
1519
1520        if interior:
1521            if expr_nested:
1522                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1523                if expression.args.get("values") is not None:
1524                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1525                    values = self.expressions(expression, key="values", flat=True)
1526                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1527            elif type_value == exp.DataType.Type.INTERVAL:
1528                nested = f" {interior}"
1529            else:
1530                nested = f"({interior})"
1531
1532        type_sql = f"{type_sql}{nested}{values}"
1533        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1534            exp.DataType.Type.TIMETZ,
1535            exp.DataType.Type.TIMESTAMPTZ,
1536        ):
1537            type_sql = f"{type_sql} WITH TIME ZONE"
1538
1539        return type_sql
1540
1541    def directory_sql(self, expression: exp.Directory) -> str:
1542        local = "LOCAL " if expression.args.get("local") else ""
1543        row_format = self.sql(expression, "row_format")
1544        row_format = f" {row_format}" if row_format else ""
1545        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1546
1547    def delete_sql(self, expression: exp.Delete) -> str:
1548        this = self.sql(expression, "this")
1549        this = f" FROM {this}" if this else ""
1550        using = self.expressions(expression, key="using")
1551        using = f" USING {using}" if using else ""
1552        cluster = self.sql(expression, "cluster")
1553        cluster = f" {cluster}" if cluster else ""
1554        where = self.sql(expression, "where")
1555        returning = self.sql(expression, "returning")
1556        order = self.sql(expression, "order")
1557        limit = self.sql(expression, "limit")
1558        tables = self.expressions(expression, key="tables")
1559        tables = f" {tables}" if tables else ""
1560        if self.RETURNING_END:
1561            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1562        else:
1563            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1564        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1565
1566    def drop_sql(self, expression: exp.Drop) -> str:
1567        this = self.sql(expression, "this")
1568        expressions = self.expressions(expression, flat=True)
1569        expressions = f" ({expressions})" if expressions else ""
1570        kind = expression.args["kind"]
1571        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1572        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1573        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1574        on_cluster = self.sql(expression, "cluster")
1575        on_cluster = f" {on_cluster}" if on_cluster else ""
1576        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1577        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1578        cascade = " CASCADE" if expression.args.get("cascade") else ""
1579        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1580        purge = " PURGE" if expression.args.get("purge") else ""
1581        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1582
1583    def set_operation(self, expression: exp.SetOperation) -> str:
1584        op_type = type(expression)
1585        op_name = op_type.key.upper()
1586
1587        distinct = expression.args.get("distinct")
1588        if (
1589            distinct is False
1590            and op_type in (exp.Except, exp.Intersect)
1591            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1592        ):
1593            self.unsupported(f"{op_name} ALL is not supported")
1594
1595        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1596
1597        if distinct is None:
1598            distinct = default_distinct
1599            if distinct is None:
1600                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1601
1602        if distinct is default_distinct:
1603            distinct_or_all = ""
1604        else:
1605            distinct_or_all = " DISTINCT" if distinct else " ALL"
1606
1607        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1608        side_kind = f"{side_kind} " if side_kind else ""
1609
1610        by_name = " BY NAME" if expression.args.get("by_name") else ""
1611        on = self.expressions(expression, key="on", flat=True)
1612        on = f" ON ({on})" if on else ""
1613
1614        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1615
1616    def set_operations(self, expression: exp.SetOperation) -> str:
1617        if not self.SET_OP_MODIFIERS:
1618            limit = expression.args.get("limit")
1619            order = expression.args.get("order")
1620
1621            if limit or order:
1622                select = self._move_ctes_to_top_level(
1623                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1624                )
1625
1626                if limit:
1627                    select = select.limit(limit.pop(), copy=False)
1628                if order:
1629                    select = select.order_by(order.pop(), copy=False)
1630                return self.sql(select)
1631
1632        sqls: t.List[str] = []
1633        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1634
1635        while stack:
1636            node = stack.pop()
1637
1638            if isinstance(node, exp.SetOperation):
1639                stack.append(node.expression)
1640                stack.append(
1641                    self.maybe_comment(
1642                        self.set_operation(node), comments=node.comments, separated=True
1643                    )
1644                )
1645                stack.append(node.this)
1646            else:
1647                sqls.append(self.sql(node))
1648
1649        this = self.sep().join(sqls)
1650        this = self.query_modifiers(expression, this)
1651        return self.prepend_ctes(expression, this)
1652
1653    def fetch_sql(self, expression: exp.Fetch) -> str:
1654        direction = expression.args.get("direction")
1655        direction = f" {direction}" if direction else ""
1656        count = self.sql(expression, "count")
1657        count = f" {count}" if count else ""
1658        limit_options = self.sql(expression, "limit_options")
1659        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1660        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1661
1662    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1663        percent = " PERCENT" if expression.args.get("percent") else ""
1664        rows = " ROWS" if expression.args.get("rows") else ""
1665        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1666        if not with_ties and rows:
1667            with_ties = " ONLY"
1668        return f"{percent}{rows}{with_ties}"
1669
1670    def filter_sql(self, expression: exp.Filter) -> str:
1671        if self.AGGREGATE_FILTER_SUPPORTED:
1672            this = self.sql(expression, "this")
1673            where = self.sql(expression, "expression").strip()
1674            return f"{this} FILTER({where})"
1675
1676        agg = expression.this
1677        agg_arg = agg.this
1678        cond = expression.expression.this
1679        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1680        return self.sql(agg)
1681
1682    def hint_sql(self, expression: exp.Hint) -> str:
1683        if not self.QUERY_HINTS:
1684            self.unsupported("Hints are not supported")
1685            return ""
1686
1687        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1688
1689    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1690        using = self.sql(expression, "using")
1691        using = f" USING {using}" if using else ""
1692        columns = self.expressions(expression, key="columns", flat=True)
1693        columns = f"({columns})" if columns else ""
1694        partition_by = self.expressions(expression, key="partition_by", flat=True)
1695        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1696        where = self.sql(expression, "where")
1697        include = self.expressions(expression, key="include", flat=True)
1698        if include:
1699            include = f" INCLUDE ({include})"
1700        with_storage = self.expressions(expression, key="with_storage", flat=True)
1701        with_storage = f" WITH ({with_storage})" if with_storage else ""
1702        tablespace = self.sql(expression, "tablespace")
1703        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1704        on = self.sql(expression, "on")
1705        on = f" ON {on}" if on else ""
1706
1707        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1708
1709    def index_sql(self, expression: exp.Index) -> str:
1710        unique = "UNIQUE " if expression.args.get("unique") else ""
1711        primary = "PRIMARY " if expression.args.get("primary") else ""
1712        amp = "AMP " if expression.args.get("amp") else ""
1713        name = self.sql(expression, "this")
1714        name = f"{name} " if name else ""
1715        table = self.sql(expression, "table")
1716        table = f"{self.INDEX_ON} {table}" if table else ""
1717
1718        index = "INDEX " if not table else ""
1719
1720        params = self.sql(expression, "params")
1721        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1722
1723    def identifier_sql(self, expression: exp.Identifier) -> str:
1724        text = expression.name
1725        lower = text.lower()
1726        text = lower if self.normalize and not expression.quoted else text
1727        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1728        if (
1729            expression.quoted
1730            or self.dialect.can_quote(expression, self.identify)
1731            or lower in self.RESERVED_KEYWORDS
1732            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1733        ):
1734            text = f"{self._identifier_start}{text}{self._identifier_end}"
1735        return text
1736
1737    def hex_sql(self, expression: exp.Hex) -> str:
1738        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1739        if self.dialect.HEX_LOWERCASE:
1740            text = self.func("LOWER", text)
1741
1742        return text
1743
1744    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1745        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1746        if not self.dialect.HEX_LOWERCASE:
1747            text = self.func("LOWER", text)
1748        return text
1749
1750    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1751        input_format = self.sql(expression, "input_format")
1752        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1753        output_format = self.sql(expression, "output_format")
1754        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1755        return self.sep().join((input_format, output_format))
1756
1757    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1758        string = self.sql(exp.Literal.string(expression.name))
1759        return f"{prefix}{string}"
1760
1761    def partition_sql(self, expression: exp.Partition) -> str:
1762        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1763        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1764
1765    def properties_sql(self, expression: exp.Properties) -> str:
1766        root_properties = []
1767        with_properties = []
1768
1769        for p in expression.expressions:
1770            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1771            if p_loc == exp.Properties.Location.POST_WITH:
1772                with_properties.append(p)
1773            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1774                root_properties.append(p)
1775
1776        root_props_ast = exp.Properties(expressions=root_properties)
1777        root_props_ast.parent = expression.parent
1778
1779        with_props_ast = exp.Properties(expressions=with_properties)
1780        with_props_ast.parent = expression.parent
1781
1782        root_props = self.root_properties(root_props_ast)
1783        with_props = self.with_properties(with_props_ast)
1784
1785        if root_props and with_props and not self.pretty:
1786            with_props = " " + with_props
1787
1788        return root_props + with_props
1789
1790    def root_properties(self, properties: exp.Properties) -> str:
1791        if properties.expressions:
1792            return self.expressions(properties, indent=False, sep=" ")
1793        return ""
1794
1795    def properties(
1796        self,
1797        properties: exp.Properties,
1798        prefix: str = "",
1799        sep: str = ", ",
1800        suffix: str = "",
1801        wrapped: bool = True,
1802    ) -> str:
1803        if properties.expressions:
1804            expressions = self.expressions(properties, sep=sep, indent=False)
1805            if expressions:
1806                expressions = self.wrap(expressions) if wrapped else expressions
1807                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1808        return ""
1809
1810    def with_properties(self, properties: exp.Properties) -> str:
1811        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1812
1813    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1814        properties_locs = defaultdict(list)
1815        for p in properties.expressions:
1816            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1817            if p_loc != exp.Properties.Location.UNSUPPORTED:
1818                properties_locs[p_loc].append(p)
1819            else:
1820                self.unsupported(f"Unsupported property {p.key}")
1821
1822        return properties_locs
1823
1824    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1825        if isinstance(expression.this, exp.Dot):
1826            return self.sql(expression, "this")
1827        return f"'{expression.name}'" if string_key else expression.name
1828
1829    def property_sql(self, expression: exp.Property) -> str:
1830        property_cls = expression.__class__
1831        if property_cls == exp.Property:
1832            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1833
1834        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1835        if not property_name:
1836            self.unsupported(f"Unsupported property {expression.key}")
1837
1838        return f"{property_name}={self.sql(expression, 'this')}"
1839
1840    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1841        if self.SUPPORTS_CREATE_TABLE_LIKE:
1842            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1843            options = f" {options}" if options else ""
1844
1845            like = f"LIKE {self.sql(expression, 'this')}{options}"
1846            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1847                like = f"({like})"
1848
1849            return like
1850
1851        if expression.expressions:
1852            self.unsupported("Transpilation of LIKE property options is unsupported")
1853
1854        select = exp.select("*").from_(expression.this).limit(0)
1855        return f"AS {self.sql(select)}"
1856
1857    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1858        no = "NO " if expression.args.get("no") else ""
1859        protection = " PROTECTION" if expression.args.get("protection") else ""
1860        return f"{no}FALLBACK{protection}"
1861
1862    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1863        no = "NO " if expression.args.get("no") else ""
1864        local = expression.args.get("local")
1865        local = f"{local} " if local else ""
1866        dual = "DUAL " if expression.args.get("dual") else ""
1867        before = "BEFORE " if expression.args.get("before") else ""
1868        after = "AFTER " if expression.args.get("after") else ""
1869        return f"{no}{local}{dual}{before}{after}JOURNAL"
1870
1871    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1872        freespace = self.sql(expression, "this")
1873        percent = " PERCENT" if expression.args.get("percent") else ""
1874        return f"FREESPACE={freespace}{percent}"
1875
1876    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1877        if expression.args.get("default"):
1878            property = "DEFAULT"
1879        elif expression.args.get("on"):
1880            property = "ON"
1881        else:
1882            property = "OFF"
1883        return f"CHECKSUM={property}"
1884
1885    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1886        if expression.args.get("no"):
1887            return "NO MERGEBLOCKRATIO"
1888        if expression.args.get("default"):
1889            return "DEFAULT MERGEBLOCKRATIO"
1890
1891        percent = " PERCENT" if expression.args.get("percent") else ""
1892        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1893
1894    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1895        default = expression.args.get("default")
1896        minimum = expression.args.get("minimum")
1897        maximum = expression.args.get("maximum")
1898        if default or minimum or maximum:
1899            if default:
1900                prop = "DEFAULT"
1901            elif minimum:
1902                prop = "MINIMUM"
1903            else:
1904                prop = "MAXIMUM"
1905            return f"{prop} DATABLOCKSIZE"
1906        units = expression.args.get("units")
1907        units = f" {units}" if units else ""
1908        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
1909
1910    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1911        autotemp = expression.args.get("autotemp")
1912        always = expression.args.get("always")
1913        default = expression.args.get("default")
1914        manual = expression.args.get("manual")
1915        never = expression.args.get("never")
1916
1917        if autotemp is not None:
1918            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1919        elif always:
1920            prop = "ALWAYS"
1921        elif default:
1922            prop = "DEFAULT"
1923        elif manual:
1924            prop = "MANUAL"
1925        elif never:
1926            prop = "NEVER"
1927        return f"BLOCKCOMPRESSION={prop}"
1928
1929    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1930        no = expression.args.get("no")
1931        no = " NO" if no else ""
1932        concurrent = expression.args.get("concurrent")
1933        concurrent = " CONCURRENT" if concurrent else ""
1934        target = self.sql(expression, "target")
1935        target = f" {target}" if target else ""
1936        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1937
1938    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1939        if isinstance(expression.this, list):
1940            return f"IN ({self.expressions(expression, key='this', flat=True)})"
1941        if expression.this:
1942            modulus = self.sql(expression, "this")
1943            remainder = self.sql(expression, "expression")
1944            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1945
1946        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1947        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1948        return f"FROM ({from_expressions}) TO ({to_expressions})"
1949
1950    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1951        this = self.sql(expression, "this")
1952
1953        for_values_or_default = expression.expression
1954        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1955            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1956        else:
1957            for_values_or_default = " DEFAULT"
1958
1959        return f"PARTITION OF {this}{for_values_or_default}"
1960
1961    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1962        kind = expression.args.get("kind")
1963        this = f" {self.sql(expression, 'this')}" if expression.this else ""
1964        for_or_in = expression.args.get("for_or_in")
1965        for_or_in = f" {for_or_in}" if for_or_in else ""
1966        lock_type = expression.args.get("lock_type")
1967        override = " OVERRIDE" if expression.args.get("override") else ""
1968        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1969
1970    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1971        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1972        statistics = expression.args.get("statistics")
1973        statistics_sql = ""
1974        if statistics is not None:
1975            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1976        return f"{data_sql}{statistics_sql}"
1977
1978    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1979        this = self.sql(expression, "this")
1980        this = f"HISTORY_TABLE={this}" if this else ""
1981        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
1982        data_consistency = (
1983            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
1984        )
1985        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
1986        retention_period = (
1987            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
1988        )
1989
1990        if this:
1991            on_sql = self.func("ON", this, data_consistency, retention_period)
1992        else:
1993            on_sql = "ON" if expression.args.get("on") else "OFF"
1994
1995        sql = f"SYSTEM_VERSIONING={on_sql}"
1996
1997        return f"WITH({sql})" if expression.args.get("with_") else sql
1998
1999    def insert_sql(self, expression: exp.Insert) -> str:
2000        hint = self.sql(expression, "hint")
2001        overwrite = expression.args.get("overwrite")
2002
2003        if isinstance(expression.this, exp.Directory):
2004            this = " OVERWRITE" if overwrite else " INTO"
2005        else:
2006            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2007
2008        stored = self.sql(expression, "stored")
2009        stored = f" {stored}" if stored else ""
2010        alternative = expression.args.get("alternative")
2011        alternative = f" OR {alternative}" if alternative else ""
2012        ignore = " IGNORE" if expression.args.get("ignore") else ""
2013        is_function = expression.args.get("is_function")
2014        if is_function:
2015            this = f"{this} FUNCTION"
2016        this = f"{this} {self.sql(expression, 'this')}"
2017
2018        exists = " IF EXISTS" if expression.args.get("exists") else ""
2019        where = self.sql(expression, "where")
2020        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2021        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2022        on_conflict = self.sql(expression, "conflict")
2023        on_conflict = f" {on_conflict}" if on_conflict else ""
2024        by_name = " BY NAME" if expression.args.get("by_name") else ""
2025        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2026        returning = self.sql(expression, "returning")
2027
2028        if self.RETURNING_END:
2029            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2030        else:
2031            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2032
2033        partition_by = self.sql(expression, "partition")
2034        partition_by = f" {partition_by}" if partition_by else ""
2035        settings = self.sql(expression, "settings")
2036        settings = f" {settings}" if settings else ""
2037
2038        source = self.sql(expression, "source")
2039        source = f"TABLE {source}" if source else ""
2040
2041        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2042        return self.prepend_ctes(expression, sql)
2043
2044    def introducer_sql(self, expression: exp.Introducer) -> str:
2045        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2046
2047    def kill_sql(self, expression: exp.Kill) -> str:
2048        kind = self.sql(expression, "kind")
2049        kind = f" {kind}" if kind else ""
2050        this = self.sql(expression, "this")
2051        this = f" {this}" if this else ""
2052        return f"KILL{kind}{this}"
2053
2054    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2055        return expression.name
2056
2057    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2058        return expression.name
2059
2060    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2061        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2062
2063        constraint = self.sql(expression, "constraint")
2064        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2065
2066        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2067        if conflict_keys:
2068            conflict_keys = f"({conflict_keys})"
2069
2070        index_predicate = self.sql(expression, "index_predicate")
2071        conflict_keys = f"{conflict_keys}{index_predicate} "
2072
2073        action = self.sql(expression, "action")
2074
2075        expressions = self.expressions(expression, flat=True)
2076        if expressions:
2077            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2078            expressions = f" {set_keyword}{expressions}"
2079
2080        where = self.sql(expression, "where")
2081        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2082
2083    def returning_sql(self, expression: exp.Returning) -> str:
2084        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2085
2086    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2087        fields = self.sql(expression, "fields")
2088        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2089        escaped = self.sql(expression, "escaped")
2090        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2091        items = self.sql(expression, "collection_items")
2092        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2093        keys = self.sql(expression, "map_keys")
2094        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2095        lines = self.sql(expression, "lines")
2096        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2097        null = self.sql(expression, "null")
2098        null = f" NULL DEFINED AS {null}" if null else ""
2099        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2100
2101    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2102        return f"WITH ({self.expressions(expression, flat=True)})"
2103
2104    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2105        this = f"{self.sql(expression, 'this')} INDEX"
2106        target = self.sql(expression, "target")
2107        target = f" FOR {target}" if target else ""
2108        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2109
2110    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2111        this = self.sql(expression, "this")
2112        kind = self.sql(expression, "kind")
2113        expr = self.sql(expression, "expression")
2114        return f"{this} ({kind} => {expr})"
2115
2116    def table_parts(self, expression: exp.Table) -> str:
2117        return ".".join(
2118            self.sql(part)
2119            for part in (
2120                expression.args.get("catalog"),
2121                expression.args.get("db"),
2122                expression.args.get("this"),
2123            )
2124            if part is not None
2125        )
2126
2127    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2128        table = self.table_parts(expression)
2129        only = "ONLY " if expression.args.get("only") else ""
2130        partition = self.sql(expression, "partition")
2131        partition = f" {partition}" if partition else ""
2132        version = self.sql(expression, "version")
2133        version = f" {version}" if version else ""
2134        alias = self.sql(expression, "alias")
2135        alias = f"{sep}{alias}" if alias else ""
2136
2137        sample = self.sql(expression, "sample")
2138        if self.dialect.ALIAS_POST_TABLESAMPLE:
2139            sample_pre_alias = sample
2140            sample_post_alias = ""
2141        else:
2142            sample_pre_alias = ""
2143            sample_post_alias = sample
2144
2145        hints = self.expressions(expression, key="hints", sep=" ")
2146        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2147        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2148        joins = self.indent(
2149            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2150        )
2151        laterals = self.expressions(expression, key="laterals", sep="")
2152
2153        file_format = self.sql(expression, "format")
2154        if file_format:
2155            pattern = self.sql(expression, "pattern")
2156            pattern = f", PATTERN => {pattern}" if pattern else ""
2157            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2158
2159        ordinality = expression.args.get("ordinality") or ""
2160        if ordinality:
2161            ordinality = f" WITH ORDINALITY{alias}"
2162            alias = ""
2163
2164        when = self.sql(expression, "when")
2165        if when:
2166            table = f"{table} {when}"
2167
2168        changes = self.sql(expression, "changes")
2169        changes = f" {changes}" if changes else ""
2170
2171        rows_from = self.expressions(expression, key="rows_from")
2172        if rows_from:
2173            table = f"ROWS FROM {self.wrap(rows_from)}"
2174
2175        indexed = expression.args.get("indexed")
2176        if indexed is not None:
2177            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2178        else:
2179            indexed = ""
2180
2181        return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{indexed}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2182
2183    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2184        table = self.func("TABLE", expression.this)
2185        alias = self.sql(expression, "alias")
2186        alias = f" AS {alias}" if alias else ""
2187        sample = self.sql(expression, "sample")
2188        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2189        joins = self.indent(
2190            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2191        )
2192        return f"{table}{alias}{pivots}{sample}{joins}"
2193
2194    def tablesample_sql(
2195        self,
2196        expression: exp.TableSample,
2197        tablesample_keyword: t.Optional[str] = None,
2198    ) -> str:
2199        method = self.sql(expression, "method")
2200        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2201        numerator = self.sql(expression, "bucket_numerator")
2202        denominator = self.sql(expression, "bucket_denominator")
2203        field = self.sql(expression, "bucket_field")
2204        field = f" ON {field}" if field else ""
2205        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2206        seed = self.sql(expression, "seed")
2207        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2208
2209        size = self.sql(expression, "size")
2210        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2211            size = f"{size} ROWS"
2212
2213        percent = self.sql(expression, "percent")
2214        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2215            percent = f"{percent} PERCENT"
2216
2217        expr = f"{bucket}{percent}{size}"
2218        if self.TABLESAMPLE_REQUIRES_PARENS:
2219            expr = f"({expr})"
2220
2221        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2222
2223    def pivot_sql(self, expression: exp.Pivot) -> str:
2224        expressions = self.expressions(expression, flat=True)
2225        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2226
2227        group = self.sql(expression, "group")
2228
2229        if expression.this:
2230            this = self.sql(expression, "this")
2231            if not expressions:
2232                sql = f"UNPIVOT {this}"
2233            else:
2234                on = f"{self.seg('ON')} {expressions}"
2235                into = self.sql(expression, "into")
2236                into = f"{self.seg('INTO')} {into}" if into else ""
2237                using = self.expressions(expression, key="using", flat=True)
2238                using = f"{self.seg('USING')} {using}" if using else ""
2239                sql = f"{direction} {this}{on}{into}{using}{group}"
2240            return self.prepend_ctes(expression, sql)
2241
2242        alias = self.sql(expression, "alias")
2243        alias = f" AS {alias}" if alias else ""
2244
2245        fields = self.expressions(
2246            expression,
2247            "fields",
2248            sep=" ",
2249            dynamic=True,
2250            new_line=True,
2251            skip_first=True,
2252            skip_last=True,
2253        )
2254
2255        include_nulls = expression.args.get("include_nulls")
2256        if include_nulls is not None:
2257            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2258        else:
2259            nulls = ""
2260
2261        default_on_null = self.sql(expression, "default_on_null")
2262        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2263        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2264        return self.prepend_ctes(expression, sql)
2265
2266    def version_sql(self, expression: exp.Version) -> str:
2267        this = f"FOR {expression.name}"
2268        kind = expression.text("kind")
2269        expr = self.sql(expression, "expression")
2270        return f"{this} {kind} {expr}"
2271
2272    def tuple_sql(self, expression: exp.Tuple) -> str:
2273        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2274
2275    def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]:
2276        """
2277        Returns (join_sql, from_sql) for UPDATE statements.
2278        - join_sql: placed after UPDATE table, before SET
2279        - from_sql: placed after SET clause (standard position)
2280        Dialects like MySQL need to convert FROM to JOIN syntax.
2281        """
2282        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2283            return ("", self.sql(expression, "from_"))
2284
2285        # Qualify unqualified columns in SET clause with the target table
2286        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2287        target_table = expression.this
2288        if isinstance(target_table, exp.Table):
2289            target_name = exp.to_identifier(target_table.alias_or_name)
2290            for eq in expression.expressions:
2291                col = eq.this
2292                if isinstance(col, exp.Column) and not col.table:
2293                    col.set("table", target_name)
2294
2295        table = from_expr.this
2296        if nested_joins := table.args.get("joins", []):
2297            table.set("joins", None)
2298
2299        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2300        for nested in nested_joins:
2301            if not nested.args.get("on") and not nested.args.get("using"):
2302                nested.set("on", exp.true())
2303            join_sql += self.sql(nested)
2304
2305        return (join_sql, "")
2306
2307    def update_sql(self, expression: exp.Update) -> str:
2308        this = self.sql(expression, "this")
2309        join_sql, from_sql = self._update_from_joins_sql(expression)
2310        set_sql = self.expressions(expression, flat=True)
2311        where_sql = self.sql(expression, "where")
2312        returning = self.sql(expression, "returning")
2313        order = self.sql(expression, "order")
2314        limit = self.sql(expression, "limit")
2315        if self.RETURNING_END:
2316            expression_sql = f"{from_sql}{where_sql}{returning}"
2317        else:
2318            expression_sql = f"{returning}{from_sql}{where_sql}"
2319        options = self.expressions(expression, key="options")
2320        options = f" OPTION({options})" if options else ""
2321        sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2322        return self.prepend_ctes(expression, sql)
2323
2324    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2325        values_as_table = values_as_table and self.VALUES_AS_TABLE
2326
2327        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2328        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2329            args = self.expressions(expression)
2330            alias = self.sql(expression, "alias")
2331            values = f"VALUES{self.seg('')}{args}"
2332            values = (
2333                f"({values})"
2334                if self.WRAP_DERIVED_VALUES
2335                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2336                else values
2337            )
2338            values = self.query_modifiers(expression, values)
2339            return f"{values} AS {alias}" if alias else values
2340
2341        # Converts `VALUES...` expression into a series of select unions.
2342        alias_node = expression.args.get("alias")
2343        column_names = alias_node and alias_node.columns
2344
2345        selects: t.List[exp.Query] = []
2346
2347        for i, tup in enumerate(expression.expressions):
2348            row = tup.expressions
2349
2350            if i == 0 and column_names:
2351                row = [
2352                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2353                ]
2354
2355            selects.append(exp.Select(expressions=row))
2356
2357        if self.pretty:
2358            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2359            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2360            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2361            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2362            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2363
2364        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2365        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2366        return f"({unions}){alias}"
2367
2368    def var_sql(self, expression: exp.Var) -> str:
2369        return self.sql(expression, "this")
2370
2371    @unsupported_args("expressions")
2372    def into_sql(self, expression: exp.Into) -> str:
2373        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2374        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2375        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2376
2377    def from_sql(self, expression: exp.From) -> str:
2378        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2379
2380    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2381        grouping_sets = self.expressions(expression, indent=False)
2382        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2383
2384    def rollup_sql(self, expression: exp.Rollup) -> str:
2385        expressions = self.expressions(expression, indent=False)
2386        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2387
2388    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2389        this = self.sql(expression, "this")
2390
2391        columns = self.expressions(expression, flat=True)
2392
2393        from_sql = self.sql(expression, "from_index")
2394        from_sql = f" FROM {from_sql}" if from_sql else ""
2395
2396        properties = expression.args.get("properties")
2397        properties_sql = (
2398            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2399        )
2400
2401        return f"{this}({columns}){from_sql}{properties_sql}"
2402
2403    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2404        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2405
2406    def cube_sql(self, expression: exp.Cube) -> str:
2407        expressions = self.expressions(expression, indent=False)
2408        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2409
2410    def group_sql(self, expression: exp.Group) -> str:
2411        group_by_all = expression.args.get("all")
2412        if group_by_all is True:
2413            modifier = " ALL"
2414        elif group_by_all is False:
2415            modifier = " DISTINCT"
2416        else:
2417            modifier = ""
2418
2419        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2420
2421        grouping_sets = self.expressions(expression, key="grouping_sets")
2422        cube = self.expressions(expression, key="cube")
2423        rollup = self.expressions(expression, key="rollup")
2424
2425        groupings = csv(
2426            self.seg(grouping_sets) if grouping_sets else "",
2427            self.seg(cube) if cube else "",
2428            self.seg(rollup) if rollup else "",
2429            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2430            sep=self.GROUPINGS_SEP,
2431        )
2432
2433        if (
2434            expression.expressions
2435            and groupings
2436            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2437        ):
2438            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2439
2440        return f"{group_by}{groupings}"
2441
2442    def having_sql(self, expression: exp.Having) -> str:
2443        this = self.indent(self.sql(expression, "this"))
2444        return f"{self.seg('HAVING')}{self.sep()}{this}"
2445
2446    def connect_sql(self, expression: exp.Connect) -> str:
2447        start = self.sql(expression, "start")
2448        start = self.seg(f"START WITH {start}") if start else ""
2449        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2450        connect = self.sql(expression, "connect")
2451        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2452        return start + connect
2453
2454    def prior_sql(self, expression: exp.Prior) -> str:
2455        return f"PRIOR {self.sql(expression, 'this')}"
2456
2457    def join_sql(self, expression: exp.Join) -> str:
2458        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2459            side = None
2460        else:
2461            side = expression.side
2462
2463        op_sql = " ".join(
2464            op
2465            for op in (
2466                expression.method,
2467                "GLOBAL" if expression.args.get("global_") else None,
2468                side,
2469                expression.kind,
2470                expression.hint if self.JOIN_HINTS else None,
2471                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2472            )
2473            if op
2474        )
2475        match_cond = self.sql(expression, "match_condition")
2476        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2477        on_sql = self.sql(expression, "on")
2478        using = expression.args.get("using")
2479
2480        if not on_sql and using:
2481            on_sql = csv(*(self.sql(column) for column in using))
2482
2483        this = expression.this
2484        this_sql = self.sql(this)
2485
2486        exprs = self.expressions(expression)
2487        if exprs:
2488            this_sql = f"{this_sql},{self.seg(exprs)}"
2489
2490        if on_sql:
2491            on_sql = self.indent(on_sql, skip_first=True)
2492            space = self.seg(" " * self.pad) if self.pretty else " "
2493            if using:
2494                on_sql = f"{space}USING ({on_sql})"
2495            else:
2496                on_sql = f"{space}ON {on_sql}"
2497        elif not op_sql:
2498            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2499                return f" {this_sql}"
2500
2501            return f", {this_sql}"
2502
2503        if op_sql != "STRAIGHT_JOIN":
2504            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2505
2506        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2507        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2508
2509    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2510        args = self.expressions(expression, flat=True)
2511        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2512        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2513
2514    def lateral_op(self, expression: exp.Lateral) -> str:
2515        cross_apply = expression.args.get("cross_apply")
2516
2517        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2518        if cross_apply is True:
2519            op = "INNER JOIN "
2520        elif cross_apply is False:
2521            op = "LEFT JOIN "
2522        else:
2523            op = ""
2524
2525        return f"{op}LATERAL"
2526
2527    def lateral_sql(self, expression: exp.Lateral) -> str:
2528        this = self.sql(expression, "this")
2529
2530        if expression.args.get("view"):
2531            alias = expression.args["alias"]
2532            columns = self.expressions(alias, key="columns", flat=True)
2533            table = f" {alias.name}" if alias.name else ""
2534            columns = f" AS {columns}" if columns else ""
2535            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2536            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2537
2538        alias = self.sql(expression, "alias")
2539        alias = f" AS {alias}" if alias else ""
2540
2541        ordinality = expression.args.get("ordinality") or ""
2542        if ordinality:
2543            ordinality = f" WITH ORDINALITY{alias}"
2544            alias = ""
2545
2546        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2547
2548    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2549        this = self.sql(expression, "this")
2550
2551        args = [
2552            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2553            for e in (expression.args.get(k) for k in ("offset", "expression"))
2554            if e
2555        ]
2556
2557        args_sql = ", ".join(self.sql(e) for e in args)
2558        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2559        expressions = self.expressions(expression, flat=True)
2560        limit_options = self.sql(expression, "limit_options")
2561        expressions = f" BY {expressions}" if expressions else ""
2562
2563        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2564
2565    def offset_sql(self, expression: exp.Offset) -> str:
2566        this = self.sql(expression, "this")
2567        value = expression.expression
2568        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2569        expressions = self.expressions(expression, flat=True)
2570        expressions = f" BY {expressions}" if expressions else ""
2571        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2572
2573    def setitem_sql(self, expression: exp.SetItem) -> str:
2574        kind = self.sql(expression, "kind")
2575        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2576            kind = ""
2577        else:
2578            kind = f"{kind} " if kind else ""
2579        this = self.sql(expression, "this")
2580        expressions = self.expressions(expression)
2581        collate = self.sql(expression, "collate")
2582        collate = f" COLLATE {collate}" if collate else ""
2583        global_ = "GLOBAL " if expression.args.get("global_") else ""
2584        return f"{global_}{kind}{this}{expressions}{collate}"
2585
2586    def set_sql(self, expression: exp.Set) -> str:
2587        expressions = f" {self.expressions(expression, flat=True)}"
2588        tag = " TAG" if expression.args.get("tag") else ""
2589        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2590
2591    def queryband_sql(self, expression: exp.QueryBand) -> str:
2592        this = self.sql(expression, "this")
2593        update = " UPDATE" if expression.args.get("update") else ""
2594        scope = self.sql(expression, "scope")
2595        scope = f" FOR {scope}" if scope else ""
2596
2597        return f"QUERY_BAND = {this}{update}{scope}"
2598
2599    def pragma_sql(self, expression: exp.Pragma) -> str:
2600        return f"PRAGMA {self.sql(expression, 'this')}"
2601
2602    def lock_sql(self, expression: exp.Lock) -> str:
2603        if not self.LOCKING_READS_SUPPORTED:
2604            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2605            return ""
2606
2607        update = expression.args["update"]
2608        key = expression.args.get("key")
2609        if update:
2610            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2611        else:
2612            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2613        expressions = self.expressions(expression, flat=True)
2614        expressions = f" OF {expressions}" if expressions else ""
2615        wait = expression.args.get("wait")
2616
2617        if wait is not None:
2618            if isinstance(wait, exp.Literal):
2619                wait = f" WAIT {self.sql(wait)}"
2620            else:
2621                wait = " NOWAIT" if wait else " SKIP LOCKED"
2622
2623        return f"{lock_type}{expressions}{wait or ''}"
2624
2625    def literal_sql(self, expression: exp.Literal) -> str:
2626        text = expression.this or ""
2627        if expression.is_string:
2628            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2629        return text
2630
2631    def escape_str(
2632        self,
2633        text: str,
2634        escape_backslash: bool = True,
2635        delimiter: t.Optional[str] = None,
2636        escaped_delimiter: t.Optional[str] = None,
2637        is_byte_string: bool = False,
2638    ) -> str:
2639        if is_byte_string:
2640            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
2641        else:
2642            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
2643
2644        if supports_escape_sequences:
2645            text = "".join(
2646                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
2647                for ch in text
2648            )
2649
2650        delimiter = delimiter or self.dialect.QUOTE_END
2651        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2652
2653        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2654
2655    def loaddata_sql(self, expression: exp.LoadData) -> str:
2656        local = " LOCAL" if expression.args.get("local") else ""
2657        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2658        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2659        this = f" INTO TABLE {self.sql(expression, 'this')}"
2660        partition = self.sql(expression, "partition")
2661        partition = f" {partition}" if partition else ""
2662        input_format = self.sql(expression, "input_format")
2663        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2664        serde = self.sql(expression, "serde")
2665        serde = f" SERDE {serde}" if serde else ""
2666        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2667
2668    def null_sql(self, *_) -> str:
2669        return "NULL"
2670
2671    def boolean_sql(self, expression: exp.Boolean) -> str:
2672        return "TRUE" if expression.this else "FALSE"
2673
2674    def booland_sql(self, expression: exp.Booland) -> str:
2675        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
2676
2677    def boolor_sql(self, expression: exp.Boolor) -> str:
2678        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
2679
2680    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2681        this = self.sql(expression, "this")
2682        this = f"{this} " if this else this
2683        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2684        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
2685
2686    def withfill_sql(self, expression: exp.WithFill) -> str:
2687        from_sql = self.sql(expression, "from_")
2688        from_sql = f" FROM {from_sql}" if from_sql else ""
2689        to_sql = self.sql(expression, "to")
2690        to_sql = f" TO {to_sql}" if to_sql else ""
2691        step_sql = self.sql(expression, "step")
2692        step_sql = f" STEP {step_sql}" if step_sql else ""
2693        interpolated_values = [
2694            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2695            if isinstance(e, exp.Alias)
2696            else self.sql(e, "this")
2697            for e in expression.args.get("interpolate") or []
2698        ]
2699        interpolate = (
2700            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2701        )
2702        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2703
2704    def cluster_sql(self, expression: exp.Cluster) -> str:
2705        return self.op_expressions("CLUSTER BY", expression)
2706
2707    def distribute_sql(self, expression: exp.Distribute) -> str:
2708        return self.op_expressions("DISTRIBUTE BY", expression)
2709
2710    def sort_sql(self, expression: exp.Sort) -> str:
2711        return self.op_expressions("SORT BY", expression)
2712
2713    def ordered_sql(self, expression: exp.Ordered) -> str:
2714        desc = expression.args.get("desc")
2715        asc = not desc
2716
2717        nulls_first = expression.args.get("nulls_first")
2718        nulls_last = not nulls_first
2719        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2720        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2721        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2722
2723        this = self.sql(expression, "this")
2724
2725        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2726        nulls_sort_change = ""
2727        if nulls_first and (
2728            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2729        ):
2730            nulls_sort_change = " NULLS FIRST"
2731        elif (
2732            nulls_last
2733            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2734            and not nulls_are_last
2735        ):
2736            nulls_sort_change = " NULLS LAST"
2737
2738        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2739        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2740            window = expression.find_ancestor(exp.Window, exp.Select)
2741            if isinstance(window, exp.Window) and window.args.get("spec"):
2742                self.unsupported(
2743                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2744                )
2745                nulls_sort_change = ""
2746            elif self.NULL_ORDERING_SUPPORTED is False and (
2747                (asc and nulls_sort_change == " NULLS LAST")
2748                or (desc and nulls_sort_change == " NULLS FIRST")
2749            ):
2750                # BigQuery does not allow these ordering/nulls combinations when used under
2751                # an aggregation func or under a window containing one
2752                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2753
2754                if isinstance(ancestor, exp.Window):
2755                    ancestor = ancestor.this
2756                if isinstance(ancestor, exp.AggFunc):
2757                    self.unsupported(
2758                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2759                    )
2760                    nulls_sort_change = ""
2761            elif self.NULL_ORDERING_SUPPORTED is None:
2762                if expression.this.is_int:
2763                    self.unsupported(
2764                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2765                    )
2766                elif not isinstance(expression.this, exp.Rand):
2767                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2768                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2769                nulls_sort_change = ""
2770
2771        with_fill = self.sql(expression, "with_fill")
2772        with_fill = f" {with_fill}" if with_fill else ""
2773
2774        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2775
2776    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2777        window_frame = self.sql(expression, "window_frame")
2778        window_frame = f"{window_frame} " if window_frame else ""
2779
2780        this = self.sql(expression, "this")
2781
2782        return f"{window_frame}{this}"
2783
2784    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2785        partition = self.partition_by_sql(expression)
2786        order = self.sql(expression, "order")
2787        measures = self.expressions(expression, key="measures")
2788        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2789        rows = self.sql(expression, "rows")
2790        rows = self.seg(rows) if rows else ""
2791        after = self.sql(expression, "after")
2792        after = self.seg(after) if after else ""
2793        pattern = self.sql(expression, "pattern")
2794        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2795        definition_sqls = [
2796            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2797            for definition in expression.args.get("define", [])
2798        ]
2799        definitions = self.expressions(sqls=definition_sqls)
2800        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2801        body = "".join(
2802            (
2803                partition,
2804                order,
2805                measures,
2806                rows,
2807                after,
2808                pattern,
2809                define,
2810            )
2811        )
2812        alias = self.sql(expression, "alias")
2813        alias = f" {alias}" if alias else ""
2814        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2815
2816    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2817        limit = expression.args.get("limit")
2818
2819        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2820            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2821        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2822            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2823
2824        return csv(
2825            *sqls,
2826            *[self.sql(join) for join in expression.args.get("joins") or []],
2827            self.sql(expression, "match"),
2828            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2829            self.sql(expression, "prewhere"),
2830            self.sql(expression, "where"),
2831            self.sql(expression, "connect"),
2832            self.sql(expression, "group"),
2833            self.sql(expression, "having"),
2834            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2835            self.sql(expression, "order"),
2836            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2837            *self.after_limit_modifiers(expression),
2838            self.options_modifier(expression),
2839            self.for_modifiers(expression),
2840            sep="",
2841        )
2842
2843    def options_modifier(self, expression: exp.Expression) -> str:
2844        options = self.expressions(expression, key="options")
2845        return f" {options}" if options else ""
2846
2847    def for_modifiers(self, expression: exp.Expression) -> str:
2848        for_modifiers = self.expressions(expression, key="for_")
2849        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
2850
2851    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2852        self.unsupported("Unsupported query option.")
2853        return ""
2854
2855    def offset_limit_modifiers(
2856        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2857    ) -> t.List[str]:
2858        return [
2859            self.sql(expression, "offset") if fetch else self.sql(limit),
2860            self.sql(limit) if fetch else self.sql(expression, "offset"),
2861        ]
2862
2863    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2864        locks = self.expressions(expression, key="locks", sep=" ")
2865        locks = f" {locks}" if locks else ""
2866        return [locks, self.sql(expression, "sample")]
2867
2868    def select_sql(self, expression: exp.Select) -> str:
2869        into = expression.args.get("into")
2870        if not self.SUPPORTS_SELECT_INTO and into:
2871            into.pop()
2872
2873        hint = self.sql(expression, "hint")
2874        distinct = self.sql(expression, "distinct")
2875        distinct = f" {distinct}" if distinct else ""
2876        kind = self.sql(expression, "kind")
2877
2878        limit = expression.args.get("limit")
2879        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2880            top = self.limit_sql(limit, top=True)
2881            limit.pop()
2882        else:
2883            top = ""
2884
2885        expressions = self.expressions(expression)
2886
2887        if kind:
2888            if kind in self.SELECT_KINDS:
2889                kind = f" AS {kind}"
2890            else:
2891                if kind == "STRUCT":
2892                    expressions = self.expressions(
2893                        sqls=[
2894                            self.sql(
2895                                exp.Struct(
2896                                    expressions=[
2897                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2898                                        if isinstance(e, exp.Alias)
2899                                        else e
2900                                        for e in expression.expressions
2901                                    ]
2902                                )
2903                            )
2904                        ]
2905                    )
2906                kind = ""
2907
2908        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2909        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2910
2911        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2912        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2913        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2914        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2915        sql = self.query_modifiers(
2916            expression,
2917            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2918            self.sql(expression, "into", comment=False),
2919            self.sql(expression, "from_", comment=False),
2920        )
2921
2922        # If both the CTE and SELECT clauses have comments, generate the latter earlier
2923        if expression.args.get("with_"):
2924            sql = self.maybe_comment(sql, expression)
2925            expression.pop_comments()
2926
2927        sql = self.prepend_ctes(expression, sql)
2928
2929        if not self.SUPPORTS_SELECT_INTO and into:
2930            if into.args.get("temporary"):
2931                table_kind = " TEMPORARY"
2932            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
2933                table_kind = " UNLOGGED"
2934            else:
2935                table_kind = ""
2936            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
2937
2938        return sql
2939
2940    def schema_sql(self, expression: exp.Schema) -> str:
2941        this = self.sql(expression, "this")
2942        sql = self.schema_columns_sql(expression)
2943        return f"{this} {sql}" if this and sql else this or sql
2944
2945    def schema_columns_sql(self, expression: exp.Schema) -> str:
2946        if expression.expressions:
2947            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2948        return ""
2949
2950    def star_sql(self, expression: exp.Star) -> str:
2951        except_ = self.expressions(expression, key="except_", flat=True)
2952        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2953        replace = self.expressions(expression, key="replace", flat=True)
2954        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
2955        rename = self.expressions(expression, key="rename", flat=True)
2956        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
2957        return f"*{except_}{replace}{rename}"
2958
2959    def parameter_sql(self, expression: exp.Parameter) -> str:
2960        this = self.sql(expression, "this")
2961        return f"{self.PARAMETER_TOKEN}{this}"
2962
2963    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2964        this = self.sql(expression, "this")
2965        kind = expression.text("kind")
2966        if kind:
2967            kind = f"{kind}."
2968        return f"@@{kind}{this}"
2969
2970    def placeholder_sql(self, expression: exp.Placeholder) -> str:
2971        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
2972
2973    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2974        alias = self.sql(expression, "alias")
2975        alias = f"{sep}{alias}" if alias else ""
2976        sample = self.sql(expression, "sample")
2977        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
2978            alias = f"{sample}{alias}"
2979
2980            # Set to None so it's not generated again by self.query_modifiers()
2981            expression.set("sample", None)
2982
2983        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2984        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2985        return self.prepend_ctes(expression, sql)
2986
2987    def qualify_sql(self, expression: exp.Qualify) -> str:
2988        this = self.indent(self.sql(expression, "this"))
2989        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
2990
2991    def unnest_sql(self, expression: exp.Unnest) -> str:
2992        args = self.expressions(expression, flat=True)
2993
2994        alias = expression.args.get("alias")
2995        offset = expression.args.get("offset")
2996
2997        if self.UNNEST_WITH_ORDINALITY:
2998            if alias and isinstance(offset, exp.Expression):
2999                alias.append("columns", offset)
3000
3001        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3002            columns = alias.columns
3003            alias = self.sql(columns[0]) if columns else ""
3004        else:
3005            alias = self.sql(alias)
3006
3007        alias = f" AS {alias}" if alias else alias
3008        if self.UNNEST_WITH_ORDINALITY:
3009            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3010        else:
3011            if isinstance(offset, exp.Expression):
3012                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3013            elif offset:
3014                suffix = f"{alias} WITH OFFSET"
3015            else:
3016                suffix = alias
3017
3018        return f"UNNEST({args}){suffix}"
3019
3020    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3021        return ""
3022
3023    def where_sql(self, expression: exp.Where) -> str:
3024        this = self.indent(self.sql(expression, "this"))
3025        return f"{self.seg('WHERE')}{self.sep()}{this}"
3026
3027    def window_sql(self, expression: exp.Window) -> str:
3028        this = self.sql(expression, "this")
3029        partition = self.partition_by_sql(expression)
3030        order = expression.args.get("order")
3031        order = self.order_sql(order, flat=True) if order else ""
3032        spec = self.sql(expression, "spec")
3033        alias = self.sql(expression, "alias")
3034        over = self.sql(expression, "over") or "OVER"
3035
3036        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3037
3038        first = expression.args.get("first")
3039        if first is None:
3040            first = ""
3041        else:
3042            first = "FIRST" if first else "LAST"
3043
3044        if not partition and not order and not spec and alias:
3045            return f"{this} {alias}"
3046
3047        args = self.format_args(
3048            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3049        )
3050        return f"{this} ({args})"
3051
3052    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3053        partition = self.expressions(expression, key="partition_by", flat=True)
3054        return f"PARTITION BY {partition}" if partition else ""
3055
3056    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3057        kind = self.sql(expression, "kind")
3058        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3059        end = (
3060            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3061            or "CURRENT ROW"
3062        )
3063
3064        window_spec = f"{kind} BETWEEN {start} AND {end}"
3065
3066        exclude = self.sql(expression, "exclude")
3067        if exclude:
3068            if self.SUPPORTS_WINDOW_EXCLUDE:
3069                window_spec += f" EXCLUDE {exclude}"
3070            else:
3071                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3072
3073        return window_spec
3074
3075    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3076        this = self.sql(expression, "this")
3077        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3078        return f"{this} WITHIN GROUP ({expression_sql})"
3079
3080    def between_sql(self, expression: exp.Between) -> str:
3081        this = self.sql(expression, "this")
3082        low = self.sql(expression, "low")
3083        high = self.sql(expression, "high")
3084        symmetric = expression.args.get("symmetric")
3085
3086        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3087            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3088
3089        flag = (
3090            " SYMMETRIC"
3091            if symmetric
3092            else " ASYMMETRIC"
3093            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3094            else ""  # silently drop ASYMMETRIC – semantics identical
3095        )
3096        return f"{this} BETWEEN{flag} {low} AND {high}"
3097
3098    def bracket_offset_expressions(
3099        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
3100    ) -> t.List[exp.Expression]:
3101        return apply_index_offset(
3102            expression.this,
3103            expression.expressions,
3104            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3105            dialect=self.dialect,
3106        )
3107
3108    def bracket_sql(self, expression: exp.Bracket) -> str:
3109        expressions = self.bracket_offset_expressions(expression)
3110        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3111        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3112
3113    def all_sql(self, expression: exp.All) -> str:
3114        this = self.sql(expression, "this")
3115        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3116            this = self.wrap(this)
3117        return f"ALL {this}"
3118
3119    def any_sql(self, expression: exp.Any) -> str:
3120        this = self.sql(expression, "this")
3121        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3122            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3123                this = self.wrap(this)
3124            return f"ANY{this}"
3125        return f"ANY {this}"
3126
3127    def exists_sql(self, expression: exp.Exists) -> str:
3128        return f"EXISTS{self.wrap(expression)}"
3129
3130    def case_sql(self, expression: exp.Case) -> str:
3131        this = self.sql(expression, "this")
3132        statements = [f"CASE {this}" if this else "CASE"]
3133
3134        for e in expression.args["ifs"]:
3135            statements.append(f"WHEN {self.sql(e, 'this')}")
3136            statements.append(f"THEN {self.sql(e, 'true')}")
3137
3138        default = self.sql(expression, "default")
3139
3140        if default:
3141            statements.append(f"ELSE {default}")
3142
3143        statements.append("END")
3144
3145        if self.pretty and self.too_wide(statements):
3146            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3147
3148        return " ".join(statements)
3149
3150    def constraint_sql(self, expression: exp.Constraint) -> str:
3151        this = self.sql(expression, "this")
3152        expressions = self.expressions(expression, flat=True)
3153        return f"CONSTRAINT {this} {expressions}"
3154
3155    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3156        order = expression.args.get("order")
3157        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3158        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3159
3160    def extract_sql(self, expression: exp.Extract) -> str:
3161        from sqlglot.dialects.dialect import map_date_part
3162
3163        this = (
3164            map_date_part(expression.this, self.dialect)
3165            if self.NORMALIZE_EXTRACT_DATE_PARTS
3166            else expression.this
3167        )
3168        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3169        expression_sql = self.sql(expression, "expression")
3170
3171        return f"EXTRACT({this_sql} FROM {expression_sql})"
3172
3173    def trim_sql(self, expression: exp.Trim) -> str:
3174        trim_type = self.sql(expression, "position")
3175
3176        if trim_type == "LEADING":
3177            func_name = "LTRIM"
3178        elif trim_type == "TRAILING":
3179            func_name = "RTRIM"
3180        else:
3181            func_name = "TRIM"
3182
3183        return self.func(func_name, expression.this, expression.expression)
3184
3185    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
3186        args = expression.expressions
3187        if isinstance(expression, exp.ConcatWs):
3188            args = args[1:]  # Skip the delimiter
3189
3190        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3191            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
3192
3193        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
3194
3195            def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression:
3196                if not e.type:
3197                    from sqlglot.optimizer.annotate_types import annotate_types
3198
3199                    e = annotate_types(e, dialect=self.dialect)
3200
3201                if e.is_string or e.is_type(exp.DataType.Type.ARRAY):
3202                    return e
3203
3204                return exp.func("coalesce", e, exp.Literal.string(""))
3205
3206            args = [_wrap_with_coalesce(e) for e in args]
3207
3208        return args
3209
3210    def concat_sql(self, expression: exp.Concat) -> str:
3211        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3212            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3213            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3214            # instead of coalescing them to empty string.
3215            from sqlglot.dialects.dialect import concat_to_dpipe_sql
3216
3217            return concat_to_dpipe_sql(self, expression)
3218
3219        expressions = self.convert_concat_args(expression)
3220
3221        # Some dialects don't allow a single-argument CONCAT call
3222        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3223            return self.sql(expressions[0])
3224
3225        return self.func("CONCAT", *expressions)
3226
3227    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3228        return self.func(
3229            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3230        )
3231
3232    def check_sql(self, expression: exp.Check) -> str:
3233        this = self.sql(expression, key="this")
3234        return f"CHECK ({this})"
3235
3236    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3237        expressions = self.expressions(expression, flat=True)
3238        expressions = f" ({expressions})" if expressions else ""
3239        reference = self.sql(expression, "reference")
3240        reference = f" {reference}" if reference else ""
3241        delete = self.sql(expression, "delete")
3242        delete = f" ON DELETE {delete}" if delete else ""
3243        update = self.sql(expression, "update")
3244        update = f" ON UPDATE {update}" if update else ""
3245        options = self.expressions(expression, key="options", flat=True, sep=" ")
3246        options = f" {options}" if options else ""
3247        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3248
3249    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3250        this = self.sql(expression, "this")
3251        this = f" {this}" if this else ""
3252        expressions = self.expressions(expression, flat=True)
3253        include = self.sql(expression, "include")
3254        options = self.expressions(expression, key="options", flat=True, sep=" ")
3255        options = f" {options}" if options else ""
3256        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3257
3258    def if_sql(self, expression: exp.If) -> str:
3259        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3260
3261    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3262        if self.MATCH_AGAINST_TABLE_PREFIX:
3263            expressions = []
3264            for expr in expression.expressions:
3265                if isinstance(expr, exp.Table):
3266                    expressions.append(f"TABLE {self.sql(expr)}")
3267                else:
3268                    expressions.append(expr)
3269        else:
3270            expressions = expression.expressions
3271
3272        modifier = expression.args.get("modifier")
3273        modifier = f" {modifier}" if modifier else ""
3274        return (
3275            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3276        )
3277
3278    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3279        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3280
3281    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3282        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3283
3284        if expression.args.get("escape"):
3285            path = self.escape_str(path)
3286
3287        if self.QUOTE_JSON_PATH:
3288            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3289
3290        return path
3291
3292    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3293        if isinstance(expression, exp.JSONPathPart):
3294            transform = self.TRANSFORMS.get(expression.__class__)
3295            if not callable(transform):
3296                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3297                return ""
3298
3299            return transform(self, expression)
3300
3301        if isinstance(expression, int):
3302            return str(expression)
3303
3304        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3305            escaped = expression.replace("'", "\\'")
3306            escaped = f"\\'{expression}\\'"
3307        else:
3308            escaped = expression.replace('"', '\\"')
3309            escaped = f'"{escaped}"'
3310
3311        return escaped
3312
3313    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3314        return f"{self.sql(expression, 'this')} FORMAT JSON"
3315
3316    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3317        # Output the Teradata column FORMAT override.
3318        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3319        this = self.sql(expression, "this")
3320        fmt = self.sql(expression, "format")
3321        return f"{this} (FORMAT {fmt})"
3322
3323    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3324        null_handling = expression.args.get("null_handling")
3325        null_handling = f" {null_handling}" if null_handling else ""
3326
3327        unique_keys = expression.args.get("unique_keys")
3328        if unique_keys is not None:
3329            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3330        else:
3331            unique_keys = ""
3332
3333        return_type = self.sql(expression, "return_type")
3334        return_type = f" RETURNING {return_type}" if return_type else ""
3335        encoding = self.sql(expression, "encoding")
3336        encoding = f" ENCODING {encoding}" if encoding else ""
3337
3338        return self.func(
3339            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3340            *expression.expressions,
3341            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3342        )
3343
3344    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3345        return self.jsonobject_sql(expression)
3346
3347    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3348        null_handling = expression.args.get("null_handling")
3349        null_handling = f" {null_handling}" if null_handling else ""
3350        return_type = self.sql(expression, "return_type")
3351        return_type = f" RETURNING {return_type}" if return_type else ""
3352        strict = " STRICT" if expression.args.get("strict") else ""
3353        return self.func(
3354            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3355        )
3356
3357    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3358        this = self.sql(expression, "this")
3359        order = self.sql(expression, "order")
3360        null_handling = expression.args.get("null_handling")
3361        null_handling = f" {null_handling}" if null_handling else ""
3362        return_type = self.sql(expression, "return_type")
3363        return_type = f" RETURNING {return_type}" if return_type else ""
3364        strict = " STRICT" if expression.args.get("strict") else ""
3365        return self.func(
3366            "JSON_ARRAYAGG",
3367            this,
3368            suffix=f"{order}{null_handling}{return_type}{strict})",
3369        )
3370
3371    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3372        path = self.sql(expression, "path")
3373        path = f" PATH {path}" if path else ""
3374        nested_schema = self.sql(expression, "nested_schema")
3375
3376        if nested_schema:
3377            return f"NESTED{path} {nested_schema}"
3378
3379        this = self.sql(expression, "this")
3380        kind = self.sql(expression, "kind")
3381        kind = f" {kind}" if kind else ""
3382
3383        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3384        return f"{this}{kind}{path}{ordinality}"
3385
3386    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3387        return self.func("COLUMNS", *expression.expressions)
3388
3389    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3390        this = self.sql(expression, "this")
3391        path = self.sql(expression, "path")
3392        path = f", {path}" if path else ""
3393        error_handling = expression.args.get("error_handling")
3394        error_handling = f" {error_handling}" if error_handling else ""
3395        empty_handling = expression.args.get("empty_handling")
3396        empty_handling = f" {empty_handling}" if empty_handling else ""
3397        schema = self.sql(expression, "schema")
3398        return self.func(
3399            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3400        )
3401
3402    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3403        this = self.sql(expression, "this")
3404        kind = self.sql(expression, "kind")
3405        path = self.sql(expression, "path")
3406        path = f" {path}" if path else ""
3407        as_json = " AS JSON" if expression.args.get("as_json") else ""
3408        return f"{this} {kind}{path}{as_json}"
3409
3410    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3411        this = self.sql(expression, "this")
3412        path = self.sql(expression, "path")
3413        path = f", {path}" if path else ""
3414        expressions = self.expressions(expression)
3415        with_ = (
3416            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3417            if expressions
3418            else ""
3419        )
3420        return f"OPENJSON({this}{path}){with_}"
3421
3422    def in_sql(self, expression: exp.In) -> str:
3423        query = expression.args.get("query")
3424        unnest = expression.args.get("unnest")
3425        field = expression.args.get("field")
3426        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3427
3428        if query:
3429            in_sql = self.sql(query)
3430        elif unnest:
3431            in_sql = self.in_unnest_op(unnest)
3432        elif field:
3433            in_sql = self.sql(field)
3434        else:
3435            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3436
3437        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3438
3439    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3440        return f"(SELECT {self.sql(unnest)})"
3441
3442    def interval_sql(self, expression: exp.Interval) -> str:
3443        unit_expression = expression.args.get("unit")
3444        unit = self.sql(unit_expression) if unit_expression else ""
3445        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3446            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3447        unit = f" {unit}" if unit else ""
3448
3449        if self.SINGLE_STRING_INTERVAL:
3450            this = expression.this.name if expression.this else ""
3451            if this:
3452                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3453                    return f"INTERVAL '{this}'{unit}"
3454                return f"INTERVAL '{this}{unit}'"
3455            return f"INTERVAL{unit}"
3456
3457        this = self.sql(expression, "this")
3458        if this:
3459            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3460            this = f" {this}" if unwrapped else f" ({this})"
3461
3462        return f"INTERVAL{this}{unit}"
3463
3464    def return_sql(self, expression: exp.Return) -> str:
3465        return f"RETURN {self.sql(expression, 'this')}"
3466
3467    def reference_sql(self, expression: exp.Reference) -> str:
3468        this = self.sql(expression, "this")
3469        expressions = self.expressions(expression, flat=True)
3470        expressions = f"({expressions})" if expressions else ""
3471        options = self.expressions(expression, key="options", flat=True, sep=" ")
3472        options = f" {options}" if options else ""
3473        return f"REFERENCES {this}{expressions}{options}"
3474
3475    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3476        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3477        parent = expression.parent
3478        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3479        return self.func(
3480            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3481        )
3482
3483    def paren_sql(self, expression: exp.Paren) -> str:
3484        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3485        return f"({sql}{self.seg(')', sep='')}"
3486
3487    def neg_sql(self, expression: exp.Neg) -> str:
3488        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3489        this_sql = self.sql(expression, "this")
3490        sep = " " if this_sql[0] == "-" else ""
3491        return f"-{sep}{this_sql}"
3492
3493    def not_sql(self, expression: exp.Not) -> str:
3494        return f"NOT {self.sql(expression, 'this')}"
3495
3496    def alias_sql(self, expression: exp.Alias) -> str:
3497        alias = self.sql(expression, "alias")
3498        alias = f" AS {alias}" if alias else ""
3499        return f"{self.sql(expression, 'this')}{alias}"
3500
3501    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3502        alias = expression.args["alias"]
3503
3504        parent = expression.parent
3505        pivot = parent and parent.parent
3506
3507        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3508            identifier_alias = isinstance(alias, exp.Identifier)
3509            literal_alias = isinstance(alias, exp.Literal)
3510
3511            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3512                alias.replace(exp.Literal.string(alias.output_name))
3513            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3514                alias.replace(exp.to_identifier(alias.output_name))
3515
3516        return self.alias_sql(expression)
3517
3518    def aliases_sql(self, expression: exp.Aliases) -> str:
3519        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3520
3521    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3522        this = self.sql(expression, "this")
3523        index = self.sql(expression, "expression")
3524        return f"{this} AT {index}"
3525
3526    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3527        this = self.sql(expression, "this")
3528        zone = self.sql(expression, "zone")
3529        return f"{this} AT TIME ZONE {zone}"
3530
3531    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3532        this = self.sql(expression, "this")
3533        zone = self.sql(expression, "zone")
3534        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3535
3536    def add_sql(self, expression: exp.Add) -> str:
3537        return self.binary(expression, "+")
3538
3539    def and_sql(
3540        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3541    ) -> str:
3542        return self.connector_sql(expression, "AND", stack)
3543
3544    def or_sql(
3545        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3546    ) -> str:
3547        return self.connector_sql(expression, "OR", stack)
3548
3549    def xor_sql(
3550        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3551    ) -> str:
3552        return self.connector_sql(expression, "XOR", stack)
3553
3554    def connector_sql(
3555        self,
3556        expression: exp.Connector,
3557        op: str,
3558        stack: t.Optional[t.List[str | exp.Expression]] = None,
3559    ) -> str:
3560        if stack is not None:
3561            if expression.expressions:
3562                stack.append(self.expressions(expression, sep=f" {op} "))
3563            else:
3564                stack.append(expression.right)
3565                if expression.comments and self.comments:
3566                    for comment in expression.comments:
3567                        if comment:
3568                            op += f" /*{self.sanitize_comment(comment)}*/"
3569                stack.extend((op, expression.left))
3570            return op
3571
3572        stack = [expression]
3573        sqls: t.List[str] = []
3574        ops = set()
3575
3576        while stack:
3577            node = stack.pop()
3578            if isinstance(node, exp.Connector):
3579                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3580            else:
3581                sql = self.sql(node)
3582                if sqls and sqls[-1] in ops:
3583                    sqls[-1] += f" {sql}"
3584                else:
3585                    sqls.append(sql)
3586
3587        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3588        return sep.join(sqls)
3589
3590    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3591        return self.binary(expression, "&")
3592
3593    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3594        return self.binary(expression, "<<")
3595
3596    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3597        return f"~{self.sql(expression, 'this')}"
3598
3599    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3600        return self.binary(expression, "|")
3601
3602    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3603        return self.binary(expression, ">>")
3604
3605    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3606        return self.binary(expression, "^")
3607
3608    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3609        format_sql = self.sql(expression, "format")
3610        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3611        to_sql = self.sql(expression, "to")
3612        to_sql = f" {to_sql}" if to_sql else ""
3613        action = self.sql(expression, "action")
3614        action = f" {action}" if action else ""
3615        default = self.sql(expression, "default")
3616        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3617        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3618
3619    # Base implementation that excludes safe, zone, and target_type metadata args
3620    def strtotime_sql(self, expression: exp.StrToTime) -> str:
3621        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
3622
3623    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3624        zone = self.sql(expression, "this")
3625        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3626
3627    def collate_sql(self, expression: exp.Collate) -> str:
3628        if self.COLLATE_IS_FUNC:
3629            return self.function_fallback_sql(expression)
3630        return self.binary(expression, "COLLATE")
3631
3632    def command_sql(self, expression: exp.Command) -> str:
3633        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3634
3635    def comment_sql(self, expression: exp.Comment) -> str:
3636        this = self.sql(expression, "this")
3637        kind = expression.args["kind"]
3638        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3639        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3640        expression_sql = self.sql(expression, "expression")
3641        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3642
3643    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3644        this = self.sql(expression, "this")
3645        delete = " DELETE" if expression.args.get("delete") else ""
3646        recompress = self.sql(expression, "recompress")
3647        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3648        to_disk = self.sql(expression, "to_disk")
3649        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3650        to_volume = self.sql(expression, "to_volume")
3651        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3652        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3653
3654    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3655        where = self.sql(expression, "where")
3656        group = self.sql(expression, "group")
3657        aggregates = self.expressions(expression, key="aggregates")
3658        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3659
3660        if not (where or group or aggregates) and len(expression.expressions) == 1:
3661            return f"TTL {self.expressions(expression, flat=True)}"
3662
3663        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3664
3665    def transaction_sql(self, expression: exp.Transaction) -> str:
3666        modes = self.expressions(expression, key="modes")
3667        modes = f" {modes}" if modes else ""
3668        return f"BEGIN{modes}"
3669
3670    def commit_sql(self, expression: exp.Commit) -> str:
3671        chain = expression.args.get("chain")
3672        if chain is not None:
3673            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3674
3675        return f"COMMIT{chain or ''}"
3676
3677    def rollback_sql(self, expression: exp.Rollback) -> str:
3678        savepoint = expression.args.get("savepoint")
3679        savepoint = f" TO {savepoint}" if savepoint else ""
3680        return f"ROLLBACK{savepoint}"
3681
3682    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3683        this = self.sql(expression, "this")
3684
3685        dtype = self.sql(expression, "dtype")
3686        if dtype:
3687            collate = self.sql(expression, "collate")
3688            collate = f" COLLATE {collate}" if collate else ""
3689            using = self.sql(expression, "using")
3690            using = f" USING {using}" if using else ""
3691            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3692            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3693
3694        default = self.sql(expression, "default")
3695        if default:
3696            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3697
3698        comment = self.sql(expression, "comment")
3699        if comment:
3700            return f"ALTER COLUMN {this} COMMENT {comment}"
3701
3702        visible = expression.args.get("visible")
3703        if visible:
3704            return f"ALTER COLUMN {this} SET {visible}"
3705
3706        allow_null = expression.args.get("allow_null")
3707        drop = expression.args.get("drop")
3708
3709        if not drop and not allow_null:
3710            self.unsupported("Unsupported ALTER COLUMN syntax")
3711
3712        if allow_null is not None:
3713            keyword = "DROP" if drop else "SET"
3714            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3715
3716        return f"ALTER COLUMN {this} DROP DEFAULT"
3717
3718    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3719        this = self.sql(expression, "this")
3720
3721        visible = expression.args.get("visible")
3722        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3723
3724        return f"ALTER INDEX {this} {visible_sql}"
3725
3726    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3727        this = self.sql(expression, "this")
3728        if not isinstance(expression.this, exp.Var):
3729            this = f"KEY DISTKEY {this}"
3730        return f"ALTER DISTSTYLE {this}"
3731
3732    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3733        compound = " COMPOUND" if expression.args.get("compound") else ""
3734        this = self.sql(expression, "this")
3735        expressions = self.expressions(expression, flat=True)
3736        expressions = f"({expressions})" if expressions else ""
3737        return f"ALTER{compound} SORTKEY {this or expressions}"
3738
3739    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
3740        if not self.RENAME_TABLE_WITH_DB:
3741            # Remove db from tables
3742            expression = expression.transform(
3743                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3744            ).assert_is(exp.AlterRename)
3745        this = self.sql(expression, "this")
3746        to_kw = " TO" if include_to else ""
3747        return f"RENAME{to_kw} {this}"
3748
3749    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3750        exists = " IF EXISTS" if expression.args.get("exists") else ""
3751        old_column = self.sql(expression, "this")
3752        new_column = self.sql(expression, "to")
3753        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3754
3755    def alterset_sql(self, expression: exp.AlterSet) -> str:
3756        exprs = self.expressions(expression, flat=True)
3757        if self.ALTER_SET_WRAPPED:
3758            exprs = f"({exprs})"
3759
3760        return f"SET {exprs}"
3761
3762    def alter_sql(self, expression: exp.Alter) -> str:
3763        actions = expression.args["actions"]
3764
3765        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3766            actions[0], exp.ColumnDef
3767        ):
3768            actions_sql = self.expressions(expression, key="actions", flat=True)
3769            actions_sql = f"ADD {actions_sql}"
3770        else:
3771            actions_list = []
3772            for action in actions:
3773                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3774                    action_sql = self.add_column_sql(action)
3775                else:
3776                    action_sql = self.sql(action)
3777                    if isinstance(action, exp.Query):
3778                        action_sql = f"AS {action_sql}"
3779
3780                actions_list.append(action_sql)
3781
3782            actions_sql = self.format_args(*actions_list).lstrip("\n")
3783
3784        exists = " IF EXISTS" if expression.args.get("exists") else ""
3785        on_cluster = self.sql(expression, "cluster")
3786        on_cluster = f" {on_cluster}" if on_cluster else ""
3787        only = " ONLY" if expression.args.get("only") else ""
3788        options = self.expressions(expression, key="options")
3789        options = f", {options}" if options else ""
3790        kind = self.sql(expression, "kind")
3791        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3792        check = " WITH CHECK" if expression.args.get("check") else ""
3793        cascade = (
3794            " CASCADE"
3795            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
3796            else ""
3797        )
3798        this = self.sql(expression, "this")
3799        this = f" {this}" if this else ""
3800
3801        return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
3802
3803    def altersession_sql(self, expression: exp.AlterSession) -> str:
3804        items_sql = self.expressions(expression, flat=True)
3805        keyword = "UNSET" if expression.args.get("unset") else "SET"
3806        return f"{keyword} {items_sql}"
3807
3808    def add_column_sql(self, expression: exp.Expression) -> str:
3809        sql = self.sql(expression)
3810        if isinstance(expression, exp.Schema):
3811            column_text = " COLUMNS"
3812        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3813            column_text = " COLUMN"
3814        else:
3815            column_text = ""
3816
3817        return f"ADD{column_text} {sql}"
3818
3819    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3820        expressions = self.expressions(expression)
3821        exists = " IF EXISTS " if expression.args.get("exists") else " "
3822        return f"DROP{exists}{expressions}"
3823
3824    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3825        return f"ADD {self.expressions(expression, indent=False)}"
3826
3827    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3828        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3829        location = self.sql(expression, "location")
3830        location = f" {location}" if location else ""
3831        return f"ADD {exists}{self.sql(expression.this)}{location}"
3832
3833    def distinct_sql(self, expression: exp.Distinct) -> str:
3834        this = self.expressions(expression, flat=True)
3835
3836        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3837            case = exp.case()
3838            for arg in expression.expressions:
3839                case = case.when(arg.is_(exp.null()), exp.null())
3840            this = self.sql(case.else_(f"({this})"))
3841
3842        this = f" {this}" if this else ""
3843
3844        on = self.sql(expression, "on")
3845        on = f" ON {on}" if on else ""
3846        return f"DISTINCT{this}{on}"
3847
3848    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3849        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
3850
3851    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3852        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
3853
3854    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3855        this_sql = self.sql(expression, "this")
3856        expression_sql = self.sql(expression, "expression")
3857        kind = "MAX" if expression.args.get("max") else "MIN"
3858        return f"{this_sql} HAVING {kind} {expression_sql}"
3859
3860    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3861        return self.sql(
3862            exp.Cast(
3863                this=exp.Div(this=expression.this, expression=expression.expression),
3864                to=exp.DataType(this=exp.DataType.Type.INT),
3865            )
3866        )
3867
3868    def dpipe_sql(self, expression: exp.DPipe) -> str:
3869        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3870            return self.func(
3871                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3872            )
3873        return self.binary(expression, "||")
3874
3875    def div_sql(self, expression: exp.Div) -> str:
3876        l, r = expression.left, expression.right
3877
3878        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3879            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3880
3881        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3882            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3883                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3884
3885        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3886            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3887                return self.sql(
3888                    exp.cast(
3889                        l / r,
3890                        to=exp.DataType.Type.BIGINT,
3891                    )
3892                )
3893
3894        return self.binary(expression, "/")
3895
3896    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3897        n = exp._wrap(expression.this, exp.Binary)
3898        d = exp._wrap(expression.expression, exp.Binary)
3899        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
3900
3901    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3902        return self.binary(expression, "OVERLAPS")
3903
3904    def distance_sql(self, expression: exp.Distance) -> str:
3905        return self.binary(expression, "<->")
3906
3907    def dot_sql(self, expression: exp.Dot) -> str:
3908        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
3909
3910    def eq_sql(self, expression: exp.EQ) -> str:
3911        return self.binary(expression, "=")
3912
3913    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3914        return self.binary(expression, ":=")
3915
3916    def escape_sql(self, expression: exp.Escape) -> str:
3917        return self.binary(expression, "ESCAPE")
3918
3919    def glob_sql(self, expression: exp.Glob) -> str:
3920        return self.binary(expression, "GLOB")
3921
3922    def gt_sql(self, expression: exp.GT) -> str:
3923        return self.binary(expression, ">")
3924
3925    def gte_sql(self, expression: exp.GTE) -> str:
3926        return self.binary(expression, ">=")
3927
3928    def is_sql(self, expression: exp.Is) -> str:
3929        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
3930            return self.sql(
3931                expression.this if expression.expression.this else exp.not_(expression.this)
3932            )
3933        return self.binary(expression, "IS")
3934
3935    def _like_sql(self, expression: exp.Like | exp.ILike) -> str:
3936        this = expression.this
3937        rhs = expression.expression
3938
3939        if isinstance(expression, exp.Like):
3940            exp_class: t.Type[exp.Like | exp.ILike] = exp.Like
3941            op = "LIKE"
3942        else:
3943            exp_class = exp.ILike
3944            op = "ILIKE"
3945
3946        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
3947            exprs = rhs.this.unnest()
3948
3949            if isinstance(exprs, exp.Tuple):
3950                exprs = exprs.expressions
3951
3952            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
3953
3954            like_expr: exp.Expression = exp_class(this=this, expression=exprs[0])
3955            for expr in exprs[1:]:
3956                like_expr = connective(like_expr, exp_class(this=this, expression=expr))
3957
3958            parent = expression.parent
3959            if not isinstance(parent, type(like_expr)) and isinstance(parent, exp.Condition):
3960                like_expr = exp.paren(like_expr, copy=False)
3961
3962            return self.sql(like_expr)
3963
3964        return self.binary(expression, op)
3965
3966    def like_sql(self, expression: exp.Like) -> str:
3967        return self._like_sql(expression)
3968
3969    def ilike_sql(self, expression: exp.ILike) -> str:
3970        return self._like_sql(expression)
3971
3972    def match_sql(self, expression: exp.Match) -> str:
3973        return self.binary(expression, "MATCH")
3974
3975    def similarto_sql(self, expression: exp.SimilarTo) -> str:
3976        return self.binary(expression, "SIMILAR TO")
3977
3978    def lt_sql(self, expression: exp.LT) -> str:
3979        return self.binary(expression, "<")
3980
3981    def lte_sql(self, expression: exp.LTE) -> str:
3982        return self.binary(expression, "<=")
3983
3984    def mod_sql(self, expression: exp.Mod) -> str:
3985        return self.binary(expression, "%")
3986
3987    def mul_sql(self, expression: exp.Mul) -> str:
3988        return self.binary(expression, "*")
3989
3990    def neq_sql(self, expression: exp.NEQ) -> str:
3991        return self.binary(expression, "<>")
3992
3993    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
3994        return self.binary(expression, "IS NOT DISTINCT FROM")
3995
3996    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3997        return self.binary(expression, "IS DISTINCT FROM")
3998
3999    def sub_sql(self, expression: exp.Sub) -> str:
4000        return self.binary(expression, "-")
4001
4002    def trycast_sql(self, expression: exp.TryCast) -> str:
4003        return self.cast_sql(expression, safe_prefix="TRY_")
4004
4005    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4006        return self.cast_sql(expression)
4007
4008    def try_sql(self, expression: exp.Try) -> str:
4009        if not self.TRY_SUPPORTED:
4010            self.unsupported("Unsupported TRY function")
4011            return self.sql(expression, "this")
4012
4013        return self.func("TRY", expression.this)
4014
4015    def log_sql(self, expression: exp.Log) -> str:
4016        this = expression.this
4017        expr = expression.expression
4018
4019        if self.dialect.LOG_BASE_FIRST is False:
4020            this, expr = expr, this
4021        elif self.dialect.LOG_BASE_FIRST is None and expr:
4022            if this.name in ("2", "10"):
4023                return self.func(f"LOG{this.name}", expr)
4024
4025            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4026
4027        return self.func("LOG", this, expr)
4028
4029    def use_sql(self, expression: exp.Use) -> str:
4030        kind = self.sql(expression, "kind")
4031        kind = f" {kind}" if kind else ""
4032        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4033        this = f" {this}" if this else ""
4034        return f"USE{kind}{this}"
4035
4036    def binary(self, expression: exp.Binary, op: str) -> str:
4037        sqls: t.List[str] = []
4038        stack: t.List[t.Union[str, exp.Expression]] = [expression]
4039        binary_type = type(expression)
4040
4041        while stack:
4042            node = stack.pop()
4043
4044            if type(node) is binary_type:
4045                op_func = node.args.get("operator")
4046                if op_func:
4047                    op = f"OPERATOR({self.sql(op_func)})"
4048
4049                stack.append(node.right)
4050                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4051                stack.append(node.left)
4052            else:
4053                sqls.append(self.sql(node))
4054
4055        return "".join(sqls)
4056
4057    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4058        to_clause = self.sql(expression, "to")
4059        if to_clause:
4060            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4061
4062        return self.function_fallback_sql(expression)
4063
4064    def function_fallback_sql(self, expression: exp.Func) -> str:
4065        args = []
4066
4067        for key in expression.arg_types:
4068            arg_value = expression.args.get(key)
4069
4070            if isinstance(arg_value, list):
4071                for value in arg_value:
4072                    args.append(value)
4073            elif arg_value is not None:
4074                args.append(arg_value)
4075
4076        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4077            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
4078        else:
4079            name = expression.sql_name()
4080
4081        return self.func(name, *args)
4082
4083    def func(
4084        self,
4085        name: str,
4086        *args: t.Optional[exp.Expression | str],
4087        prefix: str = "(",
4088        suffix: str = ")",
4089        normalize: bool = True,
4090    ) -> str:
4091        name = self.normalize_func(name) if normalize else name
4092        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4093
4094    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
4095        arg_sqls = tuple(
4096            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4097        )
4098        if self.pretty and self.too_wide(arg_sqls):
4099            return self.indent(
4100                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4101            )
4102        return sep.join(arg_sqls)
4103
4104    def too_wide(self, args: t.Iterable) -> bool:
4105        return sum(len(arg) for arg in args) > self.max_text_width
4106
4107    def format_time(
4108        self,
4109        expression: exp.Expression,
4110        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
4111        inverse_time_trie: t.Optional[t.Dict] = None,
4112    ) -> t.Optional[str]:
4113        return format_time(
4114            self.sql(expression, "format"),
4115            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4116            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4117        )
4118
4119    def expressions(
4120        self,
4121        expression: t.Optional[exp.Expression] = None,
4122        key: t.Optional[str] = None,
4123        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
4124        flat: bool = False,
4125        indent: bool = True,
4126        skip_first: bool = False,
4127        skip_last: bool = False,
4128        sep: str = ", ",
4129        prefix: str = "",
4130        dynamic: bool = False,
4131        new_line: bool = False,
4132    ) -> str:
4133        expressions = expression.args.get(key or "expressions") if expression else sqls
4134
4135        if not expressions:
4136            return ""
4137
4138        if flat:
4139            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4140
4141        num_sqls = len(expressions)
4142        result_sqls = []
4143
4144        for i, e in enumerate(expressions):
4145            sql = self.sql(e, comment=False)
4146            if not sql:
4147                continue
4148
4149            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
4150
4151            if self.pretty:
4152                if self.leading_comma:
4153                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4154                else:
4155                    result_sqls.append(
4156                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4157                    )
4158            else:
4159                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4160
4161        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4162            if new_line:
4163                result_sqls.insert(0, "")
4164                result_sqls.append("")
4165            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4166        else:
4167            result_sql = "".join(result_sqls)
4168
4169        return (
4170            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4171            if indent
4172            else result_sql
4173        )
4174
4175    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
4176        flat = flat or isinstance(expression.parent, exp.Properties)
4177        expressions_sql = self.expressions(expression, flat=flat)
4178        if flat:
4179            return f"{op} {expressions_sql}"
4180        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4181
4182    def naked_property(self, expression: exp.Property) -> str:
4183        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4184        if not property_name:
4185            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4186        return f"{property_name} {self.sql(expression, 'this')}"
4187
4188    def tag_sql(self, expression: exp.Tag) -> str:
4189        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4190
4191    def token_sql(self, token_type: TokenType) -> str:
4192        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4193
4194    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4195        this = self.sql(expression, "this")
4196        expressions = self.no_identify(self.expressions, expression)
4197        expressions = (
4198            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4199        )
4200        return f"{this}{expressions}" if expressions.strip() != "" else this
4201
4202    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4203        this = self.sql(expression, "this")
4204        expressions = self.expressions(expression, flat=True)
4205        return f"{this}({expressions})"
4206
4207    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4208        return self.binary(expression, "=>")
4209
4210    def when_sql(self, expression: exp.When) -> str:
4211        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4212        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4213        condition = self.sql(expression, "condition")
4214        condition = f" AND {condition}" if condition else ""
4215
4216        then_expression = expression.args.get("then")
4217        if isinstance(then_expression, exp.Insert):
4218            this = self.sql(then_expression, "this")
4219            this = f"INSERT {this}" if this else "INSERT"
4220            then = self.sql(then_expression, "expression")
4221            then = f"{this} VALUES {then}" if then else this
4222        elif isinstance(then_expression, exp.Update):
4223            if isinstance(then_expression.args.get("expressions"), exp.Star):
4224                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4225            else:
4226                expressions_sql = self.expressions(then_expression)
4227                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4228
4229        else:
4230            then = self.sql(then_expression)
4231        return f"WHEN {matched}{source}{condition} THEN {then}"
4232
4233    def whens_sql(self, expression: exp.Whens) -> str:
4234        return self.expressions(expression, sep=" ", indent=False)
4235
4236    def merge_sql(self, expression: exp.Merge) -> str:
4237        table = expression.this
4238        table_alias = ""
4239
4240        hints = table.args.get("hints")
4241        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4242            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4243            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4244
4245        this = self.sql(table)
4246        using = f"USING {self.sql(expression, 'using')}"
4247        whens = self.sql(expression, "whens")
4248
4249        on = self.sql(expression, "on")
4250        on = f"ON {on}" if on else ""
4251
4252        if not on:
4253            on = self.expressions(expression, key="using_cond")
4254            on = f"USING ({on})" if on else ""
4255
4256        returning = self.sql(expression, "returning")
4257        if returning:
4258            whens = f"{whens}{returning}"
4259
4260        sep = self.sep()
4261
4262        return self.prepend_ctes(
4263            expression,
4264            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4265        )
4266
4267    @unsupported_args("format")
4268    def tochar_sql(self, expression: exp.ToChar) -> str:
4269        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
4270
4271    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4272        if not self.SUPPORTS_TO_NUMBER:
4273            self.unsupported("Unsupported TO_NUMBER function")
4274            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4275
4276        fmt = expression.args.get("format")
4277        if not fmt:
4278            self.unsupported("Conversion format is required for TO_NUMBER")
4279            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4280
4281        return self.func("TO_NUMBER", expression.this, fmt)
4282
4283    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4284        this = self.sql(expression, "this")
4285        kind = self.sql(expression, "kind")
4286        settings_sql = self.expressions(expression, key="settings", sep=" ")
4287        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4288        return f"{this}({kind}{args})"
4289
4290    def dictrange_sql(self, expression: exp.DictRange) -> str:
4291        this = self.sql(expression, "this")
4292        max = self.sql(expression, "max")
4293        min = self.sql(expression, "min")
4294        return f"{this}(MIN {min} MAX {max})"
4295
4296    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4297        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4298
4299    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4300        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4301
4302    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4303    def uniquekeyproperty_sql(
4304        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4305    ) -> str:
4306        return f"{prefix} ({self.expressions(expression, flat=True)})"
4307
4308    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4309    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4310        expressions = self.expressions(expression, flat=True)
4311        expressions = f" {self.wrap(expressions)}" if expressions else ""
4312        buckets = self.sql(expression, "buckets")
4313        kind = self.sql(expression, "kind")
4314        buckets = f" BUCKETS {buckets}" if buckets else ""
4315        order = self.sql(expression, "order")
4316        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4317
4318    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4319        return ""
4320
4321    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4322        expressions = self.expressions(expression, key="expressions", flat=True)
4323        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4324        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4325        buckets = self.sql(expression, "buckets")
4326        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4327
4328    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4329        this = self.sql(expression, "this")
4330        having = self.sql(expression, "having")
4331
4332        if having:
4333            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4334
4335        return self.func("ANY_VALUE", this)
4336
4337    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4338        transform = self.func("TRANSFORM", *expression.expressions)
4339        row_format_before = self.sql(expression, "row_format_before")
4340        row_format_before = f" {row_format_before}" if row_format_before else ""
4341        record_writer = self.sql(expression, "record_writer")
4342        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4343        using = f" USING {self.sql(expression, 'command_script')}"
4344        schema = self.sql(expression, "schema")
4345        schema = f" AS {schema}" if schema else ""
4346        row_format_after = self.sql(expression, "row_format_after")
4347        row_format_after = f" {row_format_after}" if row_format_after else ""
4348        record_reader = self.sql(expression, "record_reader")
4349        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4350        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4351
4352    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4353        key_block_size = self.sql(expression, "key_block_size")
4354        if key_block_size:
4355            return f"KEY_BLOCK_SIZE = {key_block_size}"
4356
4357        using = self.sql(expression, "using")
4358        if using:
4359            return f"USING {using}"
4360
4361        parser = self.sql(expression, "parser")
4362        if parser:
4363            return f"WITH PARSER {parser}"
4364
4365        comment = self.sql(expression, "comment")
4366        if comment:
4367            return f"COMMENT {comment}"
4368
4369        visible = expression.args.get("visible")
4370        if visible is not None:
4371            return "VISIBLE" if visible else "INVISIBLE"
4372
4373        engine_attr = self.sql(expression, "engine_attr")
4374        if engine_attr:
4375            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4376
4377        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4378        if secondary_engine_attr:
4379            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4380
4381        self.unsupported("Unsupported index constraint option.")
4382        return ""
4383
4384    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4385        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4386        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4387
4388    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4389        kind = self.sql(expression, "kind")
4390        kind = f"{kind} INDEX" if kind else "INDEX"
4391        this = self.sql(expression, "this")
4392        this = f" {this}" if this else ""
4393        index_type = self.sql(expression, "index_type")
4394        index_type = f" USING {index_type}" if index_type else ""
4395        expressions = self.expressions(expression, flat=True)
4396        expressions = f" ({expressions})" if expressions else ""
4397        options = self.expressions(expression, key="options", sep=" ")
4398        options = f" {options}" if options else ""
4399        return f"{kind}{this}{index_type}{expressions}{options}"
4400
4401    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4402        if self.NVL2_SUPPORTED:
4403            return self.function_fallback_sql(expression)
4404
4405        case = exp.Case().when(
4406            expression.this.is_(exp.null()).not_(copy=False),
4407            expression.args["true"],
4408            copy=False,
4409        )
4410        else_cond = expression.args.get("false")
4411        if else_cond:
4412            case.else_(else_cond, copy=False)
4413
4414        return self.sql(case)
4415
4416    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4417        this = self.sql(expression, "this")
4418        expr = self.sql(expression, "expression")
4419        position = self.sql(expression, "position")
4420        position = f", {position}" if position else ""
4421        iterator = self.sql(expression, "iterator")
4422        condition = self.sql(expression, "condition")
4423        condition = f" IF {condition}" if condition else ""
4424        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
4425
4426    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4427        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4428
4429    def opclass_sql(self, expression: exp.Opclass) -> str:
4430        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4431
4432    def _ml_sql(self, expression: exp.Func, name: str) -> str:
4433        model = self.sql(expression, "this")
4434        model = f"MODEL {model}"
4435        expr = expression.expression
4436        if expr:
4437            expr_sql = self.sql(expression, "expression")
4438            expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql
4439        else:
4440            expr_sql = None
4441
4442        parameters = self.sql(expression, "params_struct") or None
4443
4444        return self.func(name, model, expr_sql, parameters)
4445
4446    def predict_sql(self, expression: exp.Predict) -> str:
4447        return self._ml_sql(expression, "PREDICT")
4448
4449    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4450        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4451        return self._ml_sql(expression, name)
4452
4453    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
4454        return self._ml_sql(expression, "TRANSLATE")
4455
4456    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
4457        return self._ml_sql(expression, "FORECAST")
4458
4459    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4460        this_sql = self.sql(expression, "this")
4461        if isinstance(expression.this, exp.Table):
4462            this_sql = f"TABLE {this_sql}"
4463
4464        return self.func(
4465            "FEATURES_AT_TIME",
4466            this_sql,
4467            expression.args.get("time"),
4468            expression.args.get("num_rows"),
4469            expression.args.get("ignore_feature_nulls"),
4470        )
4471
4472    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
4473        this_sql = self.sql(expression, "this")
4474        if isinstance(expression.this, exp.Table):
4475            this_sql = f"TABLE {this_sql}"
4476
4477        query_table = self.sql(expression, "query_table")
4478        if isinstance(expression.args["query_table"], exp.Table):
4479            query_table = f"TABLE {query_table}"
4480
4481        return self.func(
4482            "VECTOR_SEARCH",
4483            this_sql,
4484            expression.args.get("column_to_search"),
4485            query_table,
4486            expression.args.get("query_column_to_search"),
4487            expression.args.get("top_k"),
4488            expression.args.get("distance_type"),
4489            expression.args.get("options"),
4490        )
4491
4492    def forin_sql(self, expression: exp.ForIn) -> str:
4493        this = self.sql(expression, "this")
4494        expression_sql = self.sql(expression, "expression")
4495        return f"FOR {this} DO {expression_sql}"
4496
4497    def refresh_sql(self, expression: exp.Refresh) -> str:
4498        this = self.sql(expression, "this")
4499        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
4500        return f"REFRESH {kind}{this}"
4501
4502    def toarray_sql(self, expression: exp.ToArray) -> str:
4503        arg = expression.this
4504        if not arg.type:
4505            from sqlglot.optimizer.annotate_types import annotate_types
4506
4507            arg = annotate_types(arg, dialect=self.dialect)
4508
4509        if arg.is_type(exp.DataType.Type.ARRAY):
4510            return self.sql(arg)
4511
4512        cond_for_null = arg.is_(exp.null())
4513        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4514
4515    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4516        this = expression.this
4517        time_format = self.format_time(expression)
4518
4519        if time_format:
4520            return self.sql(
4521                exp.cast(
4522                    exp.StrToTime(this=this, format=expression.args["format"]),
4523                    exp.DataType.Type.TIME,
4524                )
4525            )
4526
4527        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4528            return self.sql(this)
4529
4530        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4531
4532    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4533        this = expression.this
4534        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4535            return self.sql(this)
4536
4537        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4538
4539    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4540        this = expression.this
4541        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4542            return self.sql(this)
4543
4544        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4545
4546    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4547        this = expression.this
4548        time_format = self.format_time(expression)
4549        safe = expression.args.get("safe")
4550        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4551            return self.sql(
4552                exp.cast(
4553                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
4554                    exp.DataType.Type.DATE,
4555                )
4556            )
4557
4558        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4559            return self.sql(this)
4560
4561        if safe:
4562            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DataType.Type.DATE)))
4563
4564        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4565
4566    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4567        return self.sql(
4568            exp.func(
4569                "DATEDIFF",
4570                expression.this,
4571                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4572                "day",
4573            )
4574        )
4575
4576    def lastday_sql(self, expression: exp.LastDay) -> str:
4577        if self.LAST_DAY_SUPPORTS_DATE_PART:
4578            return self.function_fallback_sql(expression)
4579
4580        unit = expression.text("unit")
4581        if unit and unit != "MONTH":
4582            self.unsupported("Date parts are not supported in LAST_DAY.")
4583
4584        return self.func("LAST_DAY", expression.this)
4585
4586    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4587        from sqlglot.dialects.dialect import unit_to_str
4588
4589        return self.func(
4590            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4591        )
4592
4593    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4594        if self.CAN_IMPLEMENT_ARRAY_ANY:
4595            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4596            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4597            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4598            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4599
4600        from sqlglot.dialects import Dialect
4601
4602        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4603        if self.dialect.__class__ != Dialect:
4604            self.unsupported("ARRAY_ANY is unsupported")
4605
4606        return self.function_fallback_sql(expression)
4607
4608    def struct_sql(self, expression: exp.Struct) -> str:
4609        expression.set(
4610            "expressions",
4611            [
4612                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4613                if isinstance(e, exp.PropertyEQ)
4614                else e
4615                for e in expression.expressions
4616            ],
4617        )
4618
4619        return self.function_fallback_sql(expression)
4620
4621    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4622        low = self.sql(expression, "this")
4623        high = self.sql(expression, "expression")
4624
4625        return f"{low} TO {high}"
4626
4627    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4628        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4629        tables = f" {self.expressions(expression)}"
4630
4631        exists = " IF EXISTS" if expression.args.get("exists") else ""
4632
4633        on_cluster = self.sql(expression, "cluster")
4634        on_cluster = f" {on_cluster}" if on_cluster else ""
4635
4636        identity = self.sql(expression, "identity")
4637        identity = f" {identity} IDENTITY" if identity else ""
4638
4639        option = self.sql(expression, "option")
4640        option = f" {option}" if option else ""
4641
4642        partition = self.sql(expression, "partition")
4643        partition = f" {partition}" if partition else ""
4644
4645        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4646
4647    # This transpiles T-SQL's CONVERT function
4648    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4649    def convert_sql(self, expression: exp.Convert) -> str:
4650        to = expression.this
4651        value = expression.expression
4652        style = expression.args.get("style")
4653        safe = expression.args.get("safe")
4654        strict = expression.args.get("strict")
4655
4656        if not to or not value:
4657            return ""
4658
4659        # Retrieve length of datatype and override to default if not specified
4660        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4661            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4662
4663        transformed: t.Optional[exp.Expression] = None
4664        cast = exp.Cast if strict else exp.TryCast
4665
4666        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4667        if isinstance(style, exp.Literal) and style.is_int:
4668            from sqlglot.dialects.tsql import TSQL
4669
4670            style_value = style.name
4671            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4672            if not converted_style:
4673                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4674
4675            fmt = exp.Literal.string(converted_style)
4676
4677            if to.this == exp.DataType.Type.DATE:
4678                transformed = exp.StrToDate(this=value, format=fmt)
4679            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4680                transformed = exp.StrToTime(this=value, format=fmt)
4681            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4682                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4683            elif to.this == exp.DataType.Type.TEXT:
4684                transformed = exp.TimeToStr(this=value, format=fmt)
4685
4686        if not transformed:
4687            transformed = cast(this=value, to=to, safe=safe)
4688
4689        return self.sql(transformed)
4690
4691    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4692        this = expression.this
4693        if isinstance(this, exp.JSONPathWildcard):
4694            this = self.json_path_part(this)
4695            return f".{this}" if this else ""
4696
4697        if self.SAFE_JSON_PATH_KEY_RE.match(this):
4698            return f".{this}"
4699
4700        this = self.json_path_part(this)
4701        return (
4702            f"[{this}]"
4703            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
4704            else f".{this}"
4705        )
4706
4707    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
4708        this = self.json_path_part(expression.this)
4709        return f"[{this}]" if this else ""
4710
4711    def _simplify_unless_literal(self, expression: E) -> E:
4712        if not isinstance(expression, exp.Literal):
4713            from sqlglot.optimizer.simplify import simplify
4714
4715            expression = simplify(expression, dialect=self.dialect)
4716
4717        return expression
4718
4719    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
4720        this = expression.this
4721        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
4722            self.unsupported(
4723                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
4724            )
4725            return self.sql(this)
4726
4727        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
4728            # The first modifier here will be the one closest to the AggFunc's arg
4729            mods = sorted(
4730                expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
4731                key=lambda x: 0
4732                if isinstance(x, exp.HavingMax)
4733                else (1 if isinstance(x, exp.Order) else 2),
4734            )
4735
4736            if mods:
4737                mod = mods[0]
4738                this = expression.__class__(this=mod.this.copy())
4739                this.meta["inline"] = True
4740                mod.this.replace(this)
4741                return self.sql(expression.this)
4742
4743            agg_func = expression.find(exp.AggFunc)
4744
4745            if agg_func:
4746                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
4747                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
4748
4749        return f"{self.sql(expression, 'this')} {text}"
4750
4751    def _replace_line_breaks(self, string: str) -> str:
4752        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
4753        if self.pretty:
4754            return string.replace("\n", self.SENTINEL_LINE_BREAK)
4755        return string
4756
4757    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4758        option = self.sql(expression, "this")
4759
4760        if expression.expressions:
4761            upper = option.upper()
4762
4763            # Snowflake FILE_FORMAT options are separated by whitespace
4764            sep = " " if upper == "FILE_FORMAT" else ", "
4765
4766            # Databricks copy/format options do not set their list of values with EQ
4767            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4768            values = self.expressions(expression, flat=True, sep=sep)
4769            return f"{option}{op}({values})"
4770
4771        value = self.sql(expression, "expression")
4772
4773        if not value:
4774            return option
4775
4776        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4777
4778        return f"{option}{op}{value}"
4779
4780    def credentials_sql(self, expression: exp.Credentials) -> str:
4781        cred_expr = expression.args.get("credentials")
4782        if isinstance(cred_expr, exp.Literal):
4783            # Redshift case: CREDENTIALS <string>
4784            credentials = self.sql(expression, "credentials")
4785            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4786        else:
4787            # Snowflake case: CREDENTIALS = (...)
4788            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4789            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4790
4791        storage = self.sql(expression, "storage")
4792        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4793
4794        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4795        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4796
4797        iam_role = self.sql(expression, "iam_role")
4798        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4799
4800        region = self.sql(expression, "region")
4801        region = f" REGION {region}" if region else ""
4802
4803        return f"{credentials}{storage}{encryption}{iam_role}{region}"
4804
4805    def copy_sql(self, expression: exp.Copy) -> str:
4806        this = self.sql(expression, "this")
4807        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4808
4809        credentials = self.sql(expression, "credentials")
4810        credentials = self.seg(credentials) if credentials else ""
4811        files = self.expressions(expression, key="files", flat=True)
4812        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
4813
4814        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4815        params = self.expressions(
4816            expression,
4817            key="params",
4818            sep=sep,
4819            new_line=True,
4820            skip_last=True,
4821            skip_first=True,
4822            indent=self.COPY_PARAMS_ARE_WRAPPED,
4823        )
4824
4825        if params:
4826            if self.COPY_PARAMS_ARE_WRAPPED:
4827                params = f" WITH ({params})"
4828            elif not self.pretty and (files or credentials):
4829                params = f" {params}"
4830
4831        return f"COPY{this}{kind} {files}{credentials}{params}"
4832
4833    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4834        return ""
4835
4836    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4837        on_sql = "ON" if expression.args.get("on") else "OFF"
4838        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4839        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4840        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4841        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4842
4843        if filter_col or retention_period:
4844            on_sql = self.func("ON", filter_col, retention_period)
4845
4846        return f"DATA_DELETION={on_sql}"
4847
4848    def maskingpolicycolumnconstraint_sql(
4849        self, expression: exp.MaskingPolicyColumnConstraint
4850    ) -> str:
4851        this = self.sql(expression, "this")
4852        expressions = self.expressions(expression, flat=True)
4853        expressions = f" USING ({expressions})" if expressions else ""
4854        return f"MASKING POLICY {this}{expressions}"
4855
4856    def gapfill_sql(self, expression: exp.GapFill) -> str:
4857        this = self.sql(expression, "this")
4858        this = f"TABLE {this}"
4859        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
4860
4861    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4862        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
4863
4864    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4865        this = self.sql(expression, "this")
4866        expr = expression.expression
4867
4868        if isinstance(expr, exp.Func):
4869            # T-SQL's CLR functions are case sensitive
4870            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4871        else:
4872            expr = self.sql(expression, "expression")
4873
4874        return self.scope_resolution(expr, this)
4875
4876    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4877        if self.PARSE_JSON_NAME is None:
4878            return self.sql(expression.this)
4879
4880        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
4881
4882    def rand_sql(self, expression: exp.Rand) -> str:
4883        lower = self.sql(expression, "lower")
4884        upper = self.sql(expression, "upper")
4885
4886        if lower and upper:
4887            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4888        return self.func("RAND", expression.this)
4889
4890    def changes_sql(self, expression: exp.Changes) -> str:
4891        information = self.sql(expression, "information")
4892        information = f"INFORMATION => {information}"
4893        at_before = self.sql(expression, "at_before")
4894        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4895        end = self.sql(expression, "end")
4896        end = f"{self.seg('')}{end}" if end else ""
4897
4898        return f"CHANGES ({information}){at_before}{end}"
4899
4900    def pad_sql(self, expression: exp.Pad) -> str:
4901        prefix = "L" if expression.args.get("is_left") else "R"
4902
4903        fill_pattern = self.sql(expression, "fill_pattern") or None
4904        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4905            fill_pattern = "' '"
4906
4907        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
4908
4909    def summarize_sql(self, expression: exp.Summarize) -> str:
4910        table = " TABLE" if expression.args.get("table") else ""
4911        return f"SUMMARIZE{table} {self.sql(expression.this)}"
4912
4913    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4914        generate_series = exp.GenerateSeries(**expression.args)
4915
4916        parent = expression.parent
4917        if isinstance(parent, (exp.Alias, exp.TableAlias)):
4918            parent = parent.parent
4919
4920        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
4921            return self.sql(exp.Unnest(expressions=[generate_series]))
4922
4923        if isinstance(parent, exp.Select):
4924            self.unsupported("GenerateSeries projection unnesting is not supported.")
4925
4926        return self.sql(generate_series)
4927
4928    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
4929        if self.SUPPORTS_CONVERT_TIMEZONE:
4930            return self.function_fallback_sql(expression)
4931
4932        source_tz = expression.args.get("source_tz")
4933        target_tz = expression.args.get("target_tz")
4934        timestamp = expression.args.get("timestamp")
4935
4936        if source_tz and timestamp:
4937            timestamp = exp.AtTimeZone(
4938                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
4939            )
4940
4941        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
4942
4943        return self.sql(expr)
4944
4945    def json_sql(self, expression: exp.JSON) -> str:
4946        this = self.sql(expression, "this")
4947        this = f" {this}" if this else ""
4948
4949        _with = expression.args.get("with_")
4950
4951        if _with is None:
4952            with_sql = ""
4953        elif not _with:
4954            with_sql = " WITHOUT"
4955        else:
4956            with_sql = " WITH"
4957
4958        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
4959
4960        return f"JSON{this}{with_sql}{unique_sql}"
4961
4962    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
4963        def _generate_on_options(arg: t.Any) -> str:
4964            return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}"
4965
4966        path = self.sql(expression, "path")
4967        returning = self.sql(expression, "returning")
4968        returning = f" RETURNING {returning}" if returning else ""
4969
4970        on_condition = self.sql(expression, "on_condition")
4971        on_condition = f" {on_condition}" if on_condition else ""
4972
4973        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4974
4975    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
4976        else_ = "ELSE " if expression.args.get("else_") else ""
4977        condition = self.sql(expression, "expression")
4978        condition = f"WHEN {condition} THEN " if condition else else_
4979        insert = self.sql(expression, "this")[len("INSERT") :].strip()
4980        return f"{condition}{insert}"
4981
4982    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
4983        kind = self.sql(expression, "kind")
4984        expressions = self.seg(self.expressions(expression, sep=" "))
4985        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
4986        return res
4987
4988    def oncondition_sql(self, expression: exp.OnCondition) -> str:
4989        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
4990        empty = expression.args.get("empty")
4991        empty = (
4992            f"DEFAULT {empty} ON EMPTY"
4993            if isinstance(empty, exp.Expression)
4994            else self.sql(expression, "empty")
4995        )
4996
4997        error = expression.args.get("error")
4998        error = (
4999            f"DEFAULT {error} ON ERROR"
5000            if isinstance(error, exp.Expression)
5001            else self.sql(expression, "error")
5002        )
5003
5004        if error and empty:
5005            error = (
5006                f"{empty} {error}"
5007                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5008                else f"{error} {empty}"
5009            )
5010            empty = ""
5011
5012        null = self.sql(expression, "null")
5013
5014        return f"{empty}{error}{null}"
5015
5016    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5017        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5018        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5019
5020    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5021        this = self.sql(expression, "this")
5022        path = self.sql(expression, "path")
5023
5024        passing = self.expressions(expression, "passing")
5025        passing = f" PASSING {passing}" if passing else ""
5026
5027        on_condition = self.sql(expression, "on_condition")
5028        on_condition = f" {on_condition}" if on_condition else ""
5029
5030        path = f"{path}{passing}{on_condition}"
5031
5032        return self.func("JSON_EXISTS", this, path)
5033
5034    def _add_arrayagg_null_filter(
5035        self,
5036        array_agg_sql: str,
5037        array_agg_expr: exp.ArrayAgg,
5038        column_expr: exp.Expression,
5039    ) -> str:
5040        """
5041        Add NULL filter to ARRAY_AGG if dialect requires it.
5042
5043        Args:
5044            array_agg_sql: The generated ARRAY_AGG SQL string
5045            array_agg_expr: The ArrayAgg expression node
5046            column_expr: The column/expression to filter (before ORDER BY wrapping)
5047
5048        Returns:
5049            SQL string with FILTER clause added if needed
5050        """
5051        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5052        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5053        if not (
5054            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5055        ):
5056            return array_agg_sql
5057
5058        parent = array_agg_expr.parent
5059        if isinstance(parent, exp.Filter):
5060            parent_cond = parent.expression.this
5061            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5062        elif column_expr.find(exp.Column):
5063            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5064            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5065            this_sql = (
5066                self.expressions(column_expr)
5067                if isinstance(column_expr, exp.Distinct)
5068                else self.sql(column_expr)
5069            )
5070            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5071
5072        return array_agg_sql
5073
5074    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5075        array_agg = self.function_fallback_sql(expression)
5076        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
5077
5078    def slice_sql(self, expression: exp.Slice) -> str:
5079        step = self.sql(expression, "step")
5080        end = self.sql(expression.expression)
5081        begin = self.sql(expression.this)
5082
5083        sql = f"{end}:{step}" if step else end
5084        return f"{begin}:{sql}" if sql else f"{begin}:"
5085
5086    def apply_sql(self, expression: exp.Apply) -> str:
5087        this = self.sql(expression, "this")
5088        expr = self.sql(expression, "expression")
5089
5090        return f"{this} APPLY({expr})"
5091
5092    def _grant_or_revoke_sql(
5093        self,
5094        expression: exp.Grant | exp.Revoke,
5095        keyword: str,
5096        preposition: str,
5097        grant_option_prefix: str = "",
5098        grant_option_suffix: str = "",
5099    ) -> str:
5100        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5101
5102        kind = self.sql(expression, "kind")
5103        kind = f" {kind}" if kind else ""
5104
5105        securable = self.sql(expression, "securable")
5106        securable = f" {securable}" if securable else ""
5107
5108        principals = self.expressions(expression, key="principals", flat=True)
5109
5110        if not expression.args.get("grant_option"):
5111            grant_option_prefix = grant_option_suffix = ""
5112
5113        # cascade for revoke only
5114        cascade = self.sql(expression, "cascade")
5115        cascade = f" {cascade}" if cascade else ""
5116
5117        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5118
5119    def grant_sql(self, expression: exp.Grant) -> str:
5120        return self._grant_or_revoke_sql(
5121            expression,
5122            keyword="GRANT",
5123            preposition="TO",
5124            grant_option_suffix=" WITH GRANT OPTION",
5125        )
5126
5127    def revoke_sql(self, expression: exp.Revoke) -> str:
5128        return self._grant_or_revoke_sql(
5129            expression,
5130            keyword="REVOKE",
5131            preposition="FROM",
5132            grant_option_prefix="GRANT OPTION FOR ",
5133        )
5134
5135    def grantprivilege_sql(self, expression: exp.GrantPrivilege):
5136        this = self.sql(expression, "this")
5137        columns = self.expressions(expression, flat=True)
5138        columns = f"({columns})" if columns else ""
5139
5140        return f"{this}{columns}"
5141
5142    def grantprincipal_sql(self, expression: exp.GrantPrincipal):
5143        this = self.sql(expression, "this")
5144
5145        kind = self.sql(expression, "kind")
5146        kind = f"{kind} " if kind else ""
5147
5148        return f"{kind}{this}"
5149
5150    def columns_sql(self, expression: exp.Columns):
5151        func = self.function_fallback_sql(expression)
5152        if expression.args.get("unpack"):
5153            func = f"*{func}"
5154
5155        return func
5156
5157    def overlay_sql(self, expression: exp.Overlay):
5158        this = self.sql(expression, "this")
5159        expr = self.sql(expression, "expression")
5160        from_sql = self.sql(expression, "from_")
5161        for_sql = self.sql(expression, "for_")
5162        for_sql = f" FOR {for_sql}" if for_sql else ""
5163
5164        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
5165
5166    @unsupported_args("format")
5167    def todouble_sql(self, expression: exp.ToDouble) -> str:
5168        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5169        return self.sql(cast(this=expression.this, to=exp.DataType.build(exp.DataType.Type.DOUBLE)))
5170
5171    def string_sql(self, expression: exp.String) -> str:
5172        this = expression.this
5173        zone = expression.args.get("zone")
5174
5175        if zone:
5176            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5177            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5178            # set for source_tz to transpile the time conversion before the STRING cast
5179            this = exp.ConvertTimezone(
5180                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5181            )
5182
5183        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
5184
5185    def median_sql(self, expression: exp.Median):
5186        if not self.SUPPORTS_MEDIAN:
5187            return self.sql(
5188                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5189            )
5190
5191        return self.function_fallback_sql(expression)
5192
5193    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5194        filler = self.sql(expression, "this")
5195        filler = f" {filler}" if filler else ""
5196        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5197        return f"TRUNCATE{filler} {with_count}"
5198
5199    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5200        if self.SUPPORTS_UNIX_SECONDS:
5201            return self.function_fallback_sql(expression)
5202
5203        start_ts = exp.cast(
5204            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
5205        )
5206
5207        return self.sql(
5208            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5209        )
5210
5211    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5212        dim = expression.expression
5213
5214        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5215        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5216            if not (dim.is_int and dim.name == "1"):
5217                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5218            dim = None
5219
5220        # If dimension is required but not specified, default initialize it
5221        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5222            dim = exp.Literal.number(1)
5223
5224        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5225
5226    def attach_sql(self, expression: exp.Attach) -> str:
5227        this = self.sql(expression, "this")
5228        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5229        expressions = self.expressions(expression)
5230        expressions = f" ({expressions})" if expressions else ""
5231
5232        return f"ATTACH{exists_sql} {this}{expressions}"
5233
5234    def detach_sql(self, expression: exp.Detach) -> str:
5235        this = self.sql(expression, "this")
5236        # the DATABASE keyword is required if IF EXISTS is set
5237        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
5238        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5239        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
5240
5241        return f"DETACH{exists_sql} {this}"
5242
5243    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5244        this = self.sql(expression, "this")
5245        value = self.sql(expression, "expression")
5246        value = f" {value}" if value else ""
5247        return f"{this}{value}"
5248
5249    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5250        return (
5251            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5252        )
5253
5254    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5255        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5256        encode = f"{encode} {self.sql(expression, 'this')}"
5257
5258        properties = expression.args.get("properties")
5259        if properties:
5260            encode = f"{encode} {self.properties(properties)}"
5261
5262        return encode
5263
5264    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5265        this = self.sql(expression, "this")
5266        include = f"INCLUDE {this}"
5267
5268        column_def = self.sql(expression, "column_def")
5269        if column_def:
5270            include = f"{include} {column_def}"
5271
5272        alias = self.sql(expression, "alias")
5273        if alias:
5274            include = f"{include} AS {alias}"
5275
5276        return include
5277
5278    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5279        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
5280        name = f"{prefix} {self.sql(expression, 'this')}"
5281        return self.func("XMLELEMENT", name, *expression.expressions)
5282
5283    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5284        this = self.sql(expression, "this")
5285        expr = self.sql(expression, "expression")
5286        expr = f"({expr})" if expr else ""
5287        return f"{this}{expr}"
5288
5289    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5290        partitions = self.expressions(expression, "partition_expressions")
5291        create = self.expressions(expression, "create_expressions")
5292        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
5293
5294    def partitionbyrangepropertydynamic_sql(
5295        self, expression: exp.PartitionByRangePropertyDynamic
5296    ) -> str:
5297        start = self.sql(expression, "start")
5298        end = self.sql(expression, "end")
5299
5300        every = expression.args["every"]
5301        if isinstance(every, exp.Interval) and every.this.is_string:
5302            every.this.replace(exp.Literal.number(every.name))
5303
5304        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5305
5306    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5307        name = self.sql(expression, "this")
5308        values = self.expressions(expression, flat=True)
5309
5310        return f"NAME {name} VALUE {values}"
5311
5312    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5313        kind = self.sql(expression, "kind")
5314        sample = self.sql(expression, "sample")
5315        return f"SAMPLE {sample} {kind}"
5316
5317    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5318        kind = self.sql(expression, "kind")
5319        option = self.sql(expression, "option")
5320        option = f" {option}" if option else ""
5321        this = self.sql(expression, "this")
5322        this = f" {this}" if this else ""
5323        columns = self.expressions(expression)
5324        columns = f" {columns}" if columns else ""
5325        return f"{kind}{option} STATISTICS{this}{columns}"
5326
5327    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5328        this = self.sql(expression, "this")
5329        columns = self.expressions(expression)
5330        inner_expression = self.sql(expression, "expression")
5331        inner_expression = f" {inner_expression}" if inner_expression else ""
5332        update_options = self.sql(expression, "update_options")
5333        update_options = f" {update_options} UPDATE" if update_options else ""
5334        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
5335
5336    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5337        kind = self.sql(expression, "kind")
5338        kind = f" {kind}" if kind else ""
5339        return f"DELETE{kind} STATISTICS"
5340
5341    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5342        inner_expression = self.sql(expression, "expression")
5343        return f"LIST CHAINED ROWS{inner_expression}"
5344
5345    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5346        kind = self.sql(expression, "kind")
5347        this = self.sql(expression, "this")
5348        this = f" {this}" if this else ""
5349        inner_expression = self.sql(expression, "expression")
5350        return f"VALIDATE {kind}{this}{inner_expression}"
5351
5352    def analyze_sql(self, expression: exp.Analyze) -> str:
5353        options = self.expressions(expression, key="options", sep=" ")
5354        options = f" {options}" if options else ""
5355        kind = self.sql(expression, "kind")
5356        kind = f" {kind}" if kind else ""
5357        this = self.sql(expression, "this")
5358        this = f" {this}" if this else ""
5359        mode = self.sql(expression, "mode")
5360        mode = f" {mode}" if mode else ""
5361        properties = self.sql(expression, "properties")
5362        properties = f" {properties}" if properties else ""
5363        partition = self.sql(expression, "partition")
5364        partition = f" {partition}" if partition else ""
5365        inner_expression = self.sql(expression, "expression")
5366        inner_expression = f" {inner_expression}" if inner_expression else ""
5367        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5368
5369    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5370        this = self.sql(expression, "this")
5371        namespaces = self.expressions(expression, key="namespaces")
5372        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5373        passing = self.expressions(expression, key="passing")
5374        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5375        columns = self.expressions(expression, key="columns")
5376        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5377        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5378        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5379
5380    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5381        this = self.sql(expression, "this")
5382        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
5383
5384    def export_sql(self, expression: exp.Export) -> str:
5385        this = self.sql(expression, "this")
5386        connection = self.sql(expression, "connection")
5387        connection = f"WITH CONNECTION {connection} " if connection else ""
5388        options = self.sql(expression, "options")
5389        return f"EXPORT DATA {connection}{options} AS {this}"
5390
5391    def declare_sql(self, expression: exp.Declare) -> str:
5392        return f"DECLARE {self.expressions(expression, flat=True)}"
5393
5394    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5395        variable = self.sql(expression, "this")
5396        default = self.sql(expression, "default")
5397        default = f" = {default}" if default else ""
5398
5399        kind = self.sql(expression, "kind")
5400        if isinstance(expression.args.get("kind"), exp.Schema):
5401            kind = f"TABLE {kind}"
5402
5403        return f"{variable} AS {kind}{default}"
5404
5405    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5406        kind = self.sql(expression, "kind")
5407        this = self.sql(expression, "this")
5408        set = self.sql(expression, "expression")
5409        using = self.sql(expression, "using")
5410        using = f" USING {using}" if using else ""
5411
5412        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5413
5414        return f"{kind_sql} {this} SET {set}{using}"
5415
5416    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5417        params = self.expressions(expression, key="params", flat=True)
5418        return self.func(expression.name, *expression.expressions) + f"({params})"
5419
5420    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5421        return self.func(expression.name, *expression.expressions)
5422
5423    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5424        return self.anonymousaggfunc_sql(expression)
5425
5426    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5427        return self.parameterizedagg_sql(expression)
5428
5429    def show_sql(self, expression: exp.Show) -> str:
5430        self.unsupported("Unsupported SHOW statement")
5431        return ""
5432
5433    def install_sql(self, expression: exp.Install) -> str:
5434        self.unsupported("Unsupported INSTALL statement")
5435        return ""
5436
5437    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5438        # Snowflake GET/PUT statements:
5439        #   PUT <file> <internalStage> <properties>
5440        #   GET <internalStage> <file> <properties>
5441        props = expression.args.get("properties")
5442        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5443        this = self.sql(expression, "this")
5444        target = self.sql(expression, "target")
5445
5446        if isinstance(expression, exp.Put):
5447            return f"PUT {this} {target}{props_sql}"
5448        else:
5449            return f"GET {target} {this}{props_sql}"
5450
5451    def translatecharacters_sql(self, expression: exp.TranslateCharacters):
5452        this = self.sql(expression, "this")
5453        expr = self.sql(expression, "expression")
5454        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5455        return f"TRANSLATE({this} USING {expr}{with_error})"
5456
5457    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5458        if self.SUPPORTS_DECODE_CASE:
5459            return self.func("DECODE", *expression.expressions)
5460
5461        expression, *expressions = expression.expressions
5462
5463        ifs = []
5464        for search, result in zip(expressions[::2], expressions[1::2]):
5465            if isinstance(search, exp.Literal):
5466                ifs.append(exp.If(this=expression.eq(search), true=result))
5467            elif isinstance(search, exp.Null):
5468                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5469            else:
5470                if isinstance(search, exp.Binary):
5471                    search = exp.paren(search)
5472
5473                cond = exp.or_(
5474                    expression.eq(search),
5475                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5476                    copy=False,
5477                )
5478                ifs.append(exp.If(this=cond, true=result))
5479
5480        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5481        return self.sql(case)
5482
5483    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5484        this = self.sql(expression, "this")
5485        this = self.seg(this, sep="")
5486        dimensions = self.expressions(
5487            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5488        )
5489        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5490        metrics = self.expressions(
5491            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5492        )
5493        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5494        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
5495        facts = self.seg(f"FACTS {facts}") if facts else ""
5496        where = self.sql(expression, "where")
5497        where = self.seg(f"WHERE {where}") if where else ""
5498        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
5499        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5500
5501    def getextract_sql(self, expression: exp.GetExtract) -> str:
5502        this = expression.this
5503        expr = expression.expression
5504
5505        if not this.type or not expression.type:
5506            from sqlglot.optimizer.annotate_types import annotate_types
5507
5508            this = annotate_types(this, dialect=self.dialect)
5509
5510        if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)):
5511            return self.sql(exp.Bracket(this=this, expressions=[expr]))
5512
5513        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
5514
5515    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
5516        return self.sql(
5517            exp.DateAdd(
5518                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
5519                expression=expression.this,
5520                unit=exp.var("DAY"),
5521            )
5522        )
5523
5524    def space_sql(self: Generator, expression: exp.Space) -> str:
5525        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
5526
5527    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
5528        return f"BUILD {self.sql(expression, 'this')}"
5529
5530    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
5531        method = self.sql(expression, "method")
5532        kind = expression.args.get("kind")
5533        if not kind:
5534            return f"REFRESH {method}"
5535
5536        every = self.sql(expression, "every")
5537        unit = self.sql(expression, "unit")
5538        every = f" EVERY {every} {unit}" if every else ""
5539        starts = self.sql(expression, "starts")
5540        starts = f" STARTS {starts}" if starts else ""
5541
5542        return f"REFRESH {method} ON {kind}{every}{starts}"
5543
5544    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
5545        self.unsupported("The model!attribute syntax is not supported")
5546        return ""
5547
5548    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
5549        return self.func("DIRECTORY", expression.this)
5550
5551    def uuid_sql(self, expression: exp.Uuid) -> str:
5552        is_string = expression.args.get("is_string", False)
5553        uuid_func_sql = self.func("UUID")
5554
5555        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
5556            return self.sql(
5557                exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect)
5558            )
5559
5560        return uuid_func_sql
5561
5562    def initcap_sql(self, expression: exp.Initcap) -> str:
5563        delimiters = expression.expression
5564
5565        if delimiters:
5566            # do not generate delimiters arg if we are round-tripping from default delimiters
5567            if (
5568                delimiters.is_string
5569                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
5570            ):
5571                delimiters = None
5572            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
5573                self.unsupported("INITCAP does not support custom delimiters")
5574                delimiters = None
5575
5576        return self.func("INITCAP", expression.this, delimiters)
5577
5578    def localtime_sql(self, expression: exp.Localtime) -> str:
5579        this = expression.this
5580        return self.func("LOCALTIME", this) if this else "LOCALTIME"
5581
5582    def localtimestamp_sql(self, expression: exp.Localtime) -> str:
5583        this = expression.this
5584        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
5585
5586    def weekstart_sql(self, expression: exp.WeekStart) -> str:
5587        this = expression.this.name.upper()
5588        if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
5589            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
5590            return "WEEK"
5591
5592        return self.func("WEEK", expression.this)
5593
5594    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
5595        this = self.expressions(expression)
5596        charset = self.sql(expression, "charset")
5597        using = f" USING {charset}" if charset else ""
5598        return self.func(name, this + using)
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: Always quote except for specials cases.
  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.Adjacent: lambda self, e: self.binary(e, "-|-"),
 117        exp.AllowedValuesProperty: lambda self,
 118        e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}",
 119        exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
 120        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
 121        exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
 122        exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
 123        exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
 124        exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
 125        exp.CaseSpecificColumnConstraint: lambda _,
 126        e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC",
 127        exp.Ceil: lambda self, e: self.ceil_floor(e),
 128        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
 129        exp.CharacterSetProperty: lambda self,
 130        e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}",
 131        exp.ClusteredColumnConstraint: lambda self,
 132        e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})",
 133        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
 134        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
 135        exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}",
 136        exp.ConvertToCharset: lambda self, e: self.func(
 137            "CONVERT", e.this, e.args["dest"], e.args.get("source")
 138        ),
 139        exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
 140        exp.CredentialsProperty: lambda self,
 141        e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})",
 142        exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG",
 143        exp.SessionUser: lambda *_: "SESSION_USER",
 144        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 145        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 146        exp.DynamicProperty: lambda *_: "DYNAMIC",
 147        exp.EmptyProperty: lambda *_: "EMPTY",
 148        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 149        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 150        exp.EphemeralColumnConstraint: lambda self,
 151        e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}",
 152        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 153        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 154        exp.Except: lambda self, e: self.set_operations(e),
 155        exp.ExternalProperty: lambda *_: "EXTERNAL",
 156        exp.Floor: lambda self, e: self.ceil_floor(e),
 157        exp.Get: lambda self, e: self.get_put_sql(e),
 158        exp.GlobalProperty: lambda *_: "GLOBAL",
 159        exp.HeapProperty: lambda *_: "HEAP",
 160        exp.IcebergProperty: lambda *_: "ICEBERG",
 161        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 162        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 163        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 164        exp.Intersect: lambda self, e: self.set_operations(e),
 165        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 166        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)),
 167        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 168        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 169        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 170        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 171        exp.LocationProperty: lambda self, e: self.naked_property(e),
 172        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 173        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 174        exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
 175        exp.NonClusteredColumnConstraint: lambda self,
 176        e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})",
 177        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 178        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 179        exp.OnCommitProperty: lambda _,
 180        e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS",
 181        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 182        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 183        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 184        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 185        exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
 186        exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
 187        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 188        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 189        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 190        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 191        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 192        exp.ProjectionPolicyColumnConstraint: lambda self,
 193        e: f"PROJECTION POLICY {self.sql(e, 'this')}",
 194        exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
 195        exp.Put: lambda self, e: self.get_put_sql(e),
 196        exp.RemoteWithConnectionModelProperty: lambda self,
 197        e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
 198        exp.ReturnsProperty: lambda self, e: (
 199            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 200        ),
 201        exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
 202        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 203        exp.SecureProperty: lambda *_: "SECURE",
 204        exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}",
 205        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 206        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 207        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 208        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 209        exp.SqlReadWriteProperty: lambda _, e: e.name,
 210        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
 211        exp.StabilityProperty: lambda _, e: e.name,
 212        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 213        exp.StreamingTableProperty: lambda *_: "STREAMING",
 214        exp.StrictProperty: lambda *_: "STRICT",
 215        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 216        exp.TableColumn: lambda self, e: self.sql(e.this),
 217        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 218        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 219        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 220        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 221        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 222        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 223        exp.TransientProperty: lambda *_: "TRANSIENT",
 224        exp.Union: lambda self, e: self.set_operations(e),
 225        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 226        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 227        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 228        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 229        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 230        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 231        exp.UtcTimestamp: lambda self, e: self.sql(
 232            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 233        ),
 234        exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
 235        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 236        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 237        exp.VolatileProperty: lambda *_: "VOLATILE",
 238        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 239        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 240        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 241        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 242        exp.ForceProperty: lambda *_: "FORCE",
 243    }
 244
 245    # Whether null ordering is supported in order by
 246    # True: Full Support, None: No support, False: No support for certain cases
 247    # such as window specifications, aggregate functions etc
 248    NULL_ORDERING_SUPPORTED: t.Optional[bool] = True
 249
 250    # Whether ignore nulls is inside the agg or outside.
 251    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 252    IGNORE_NULLS_IN_FUNC = False
 253
 254    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 255    LOCKING_READS_SUPPORTED = False
 256
 257    # Whether the EXCEPT and INTERSECT operations can return duplicates
 258    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 259
 260    # Wrap derived values in parens, usually standard but spark doesn't support it
 261    WRAP_DERIVED_VALUES = True
 262
 263    # Whether create function uses an AS before the RETURN
 264    CREATE_FUNCTION_RETURN_AS = True
 265
 266    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 267    MATCHED_BY_SOURCE = True
 268
 269    # Whether the INTERVAL expression works only with values like '1 day'
 270    SINGLE_STRING_INTERVAL = False
 271
 272    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 273    INTERVAL_ALLOWS_PLURAL_FORM = True
 274
 275    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 276    LIMIT_FETCH = "ALL"
 277
 278    # Whether limit and fetch allows expresions or just limits
 279    LIMIT_ONLY_LITERALS = False
 280
 281    # Whether a table is allowed to be renamed with a db
 282    RENAME_TABLE_WITH_DB = True
 283
 284    # The separator for grouping sets and rollups
 285    GROUPINGS_SEP = ","
 286
 287    # The string used for creating an index on a table
 288    INDEX_ON = "ON"
 289
 290    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 291    INOUT_SEPARATOR = " "
 292
 293    # Whether join hints should be generated
 294    JOIN_HINTS = True
 295
 296    # Whether directed joins are supported
 297    DIRECTED_JOINS = False
 298
 299    # Whether table hints should be generated
 300    TABLE_HINTS = True
 301
 302    # Whether query hints should be generated
 303    QUERY_HINTS = True
 304
 305    # What kind of separator to use for query hints
 306    QUERY_HINT_SEP = ", "
 307
 308    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 309    IS_BOOL_ALLOWED = True
 310
 311    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 312    DUPLICATE_KEY_UPDATE_WITH_SET = True
 313
 314    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 315    LIMIT_IS_TOP = False
 316
 317    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 318    RETURNING_END = True
 319
 320    # Whether to generate an unquoted value for EXTRACT's date part argument
 321    EXTRACT_ALLOWS_QUOTES = True
 322
 323    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 324    TZ_TO_WITH_TIME_ZONE = False
 325
 326    # Whether the NVL2 function is supported
 327    NVL2_SUPPORTED = True
 328
 329    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 330    SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE")
 331
 332    # Whether VALUES statements can be used as derived tables.
 333    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 334    # SELECT * VALUES into SELECT UNION
 335    VALUES_AS_TABLE = True
 336
 337    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 338    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 339
 340    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 341    UNNEST_WITH_ORDINALITY = True
 342
 343    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 344    AGGREGATE_FILTER_SUPPORTED = True
 345
 346    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 347    SEMI_ANTI_JOIN_WITH_SIDE = True
 348
 349    # Whether to include the type of a computed column in the CREATE DDL
 350    COMPUTED_COLUMN_WITH_TYPE = True
 351
 352    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 353    SUPPORTS_TABLE_COPY = True
 354
 355    # Whether parentheses are required around the table sample's expression
 356    TABLESAMPLE_REQUIRES_PARENS = True
 357
 358    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 359    TABLESAMPLE_SIZE_IS_ROWS = True
 360
 361    # The keyword(s) to use when generating a sample clause
 362    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 363
 364    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 365    TABLESAMPLE_WITH_METHOD = True
 366
 367    # The keyword to use when specifying the seed of a sample clause
 368    TABLESAMPLE_SEED_KEYWORD = "SEED"
 369
 370    # Whether COLLATE is a function instead of a binary operator
 371    COLLATE_IS_FUNC = False
 372
 373    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 374    DATA_TYPE_SPECIFIERS_ALLOWED = False
 375
 376    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 377    ENSURE_BOOLS = False
 378
 379    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 380    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 381
 382    # Whether CONCAT requires >1 arguments
 383    SUPPORTS_SINGLE_ARG_CONCAT = True
 384
 385    # Whether LAST_DAY function supports a date part argument
 386    LAST_DAY_SUPPORTS_DATE_PART = True
 387
 388    # Whether named columns are allowed in table aliases
 389    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 390
 391    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 392    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 393
 394    # What delimiter to use for separating JSON key/value pairs
 395    JSON_KEY_VALUE_PAIR_SEP = ":"
 396
 397    # INSERT OVERWRITE TABLE x override
 398    INSERT_OVERWRITE = " OVERWRITE TABLE"
 399
 400    # Whether the SELECT .. INTO syntax is used instead of CTAS
 401    SUPPORTS_SELECT_INTO = False
 402
 403    # Whether UNLOGGED tables can be created
 404    SUPPORTS_UNLOGGED_TABLES = False
 405
 406    # Whether the CREATE TABLE LIKE statement is supported
 407    SUPPORTS_CREATE_TABLE_LIKE = True
 408
 409    # Whether the LikeProperty needs to be specified inside of the schema clause
 410    LIKE_PROPERTY_INSIDE_SCHEMA = False
 411
 412    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 413    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 414    MULTI_ARG_DISTINCT = True
 415
 416    # Whether the JSON extraction operators expect a value of type JSON
 417    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 418
 419    # Whether bracketed keys like ["foo"] are supported in JSON paths
 420    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 421
 422    # Whether to escape keys using single quotes in JSON paths
 423    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 424
 425    # The JSONPathPart expressions supported by this dialect
 426    SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy()
 427
 428    # Whether any(f(x) for x in array) can be implemented by this dialect
 429    CAN_IMPLEMENT_ARRAY_ANY = False
 430
 431    # Whether the function TO_NUMBER is supported
 432    SUPPORTS_TO_NUMBER = True
 433
 434    # Whether EXCLUDE in window specification is supported
 435    SUPPORTS_WINDOW_EXCLUDE = False
 436
 437    # Whether or not set op modifiers apply to the outer set op or select.
 438    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 439    # True means limit 1 happens after the set op, False means it it happens on y.
 440    SET_OP_MODIFIERS = True
 441
 442    # Whether parameters from COPY statement are wrapped in parentheses
 443    COPY_PARAMS_ARE_WRAPPED = True
 444
 445    # Whether values of params are set with "=" token or empty space
 446    COPY_PARAMS_EQ_REQUIRED = False
 447
 448    # Whether COPY statement has INTO keyword
 449    COPY_HAS_INTO_KEYWORD = True
 450
 451    # Whether the conditional TRY(expression) function is supported
 452    TRY_SUPPORTED = True
 453
 454    # Whether the UESCAPE syntax in unicode strings is supported
 455    SUPPORTS_UESCAPE = True
 456
 457    # Function used to replace escaped unicode codes in unicode strings
 458    UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None
 459
 460    # The keyword to use when generating a star projection with excluded columns
 461    STAR_EXCEPT = "EXCEPT"
 462
 463    # The HEX function name
 464    HEX_FUNC = "HEX"
 465
 466    # The keywords to use when prefixing & separating WITH based properties
 467    WITH_PROPERTIES_PREFIX = "WITH"
 468
 469    # Whether to quote the generated expression of exp.JsonPath
 470    QUOTE_JSON_PATH = True
 471
 472    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 473    PAD_FILL_PATTERN_IS_REQUIRED = False
 474
 475    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 476    SUPPORTS_EXPLODING_PROJECTIONS = True
 477
 478    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 479    ARRAY_CONCAT_IS_VAR_LEN = True
 480
 481    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 482    SUPPORTS_CONVERT_TIMEZONE = False
 483
 484    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 485    SUPPORTS_MEDIAN = True
 486
 487    # Whether UNIX_SECONDS(timestamp) is supported
 488    SUPPORTS_UNIX_SECONDS = False
 489
 490    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 491    ALTER_SET_WRAPPED = False
 492
 493    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 494    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 495    # TODO: The normalization should be done by default once we've tested it across all dialects.
 496    NORMALIZE_EXTRACT_DATE_PARTS = False
 497
 498    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 499    PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
 500
 501    # The function name of the exp.ArraySize expression
 502    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 503
 504    # The syntax to use when altering the type of a column
 505    ALTER_SET_TYPE = "SET DATA TYPE"
 506
 507    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 508    # None -> Doesn't support it at all
 509    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 510    # True (Postgres) -> Explicitly requires it
 511    ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None
 512
 513    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 514    SUPPORTS_DECODE_CASE = True
 515
 516    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 517    SUPPORTS_BETWEEN_FLAGS = False
 518
 519    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 520    SUPPORTS_LIKE_QUANTIFIERS = True
 521
 522    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 523    MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None
 524
 525    # Whether to include the VARIABLE keyword for SET assignments
 526    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 527
 528    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 529    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 530    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 531    UPDATE_STATEMENT_SUPPORTS_FROM = True
 532
 533    TYPE_MAPPING = {
 534        exp.DataType.Type.DATETIME2: "TIMESTAMP",
 535        exp.DataType.Type.NCHAR: "CHAR",
 536        exp.DataType.Type.NVARCHAR: "VARCHAR",
 537        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 538        exp.DataType.Type.LONGTEXT: "TEXT",
 539        exp.DataType.Type.TINYTEXT: "TEXT",
 540        exp.DataType.Type.BLOB: "VARBINARY",
 541        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 542        exp.DataType.Type.LONGBLOB: "BLOB",
 543        exp.DataType.Type.TINYBLOB: "BLOB",
 544        exp.DataType.Type.INET: "INET",
 545        exp.DataType.Type.ROWVERSION: "VARBINARY",
 546        exp.DataType.Type.SMALLDATETIME: "TIMESTAMP",
 547    }
 548
 549    UNSUPPORTED_TYPES: set[exp.DataType.Type] = set()
 550
 551    TIME_PART_SINGULARS = {
 552        "MICROSECONDS": "MICROSECOND",
 553        "SECONDS": "SECOND",
 554        "MINUTES": "MINUTE",
 555        "HOURS": "HOUR",
 556        "DAYS": "DAY",
 557        "WEEKS": "WEEK",
 558        "MONTHS": "MONTH",
 559        "QUARTERS": "QUARTER",
 560        "YEARS": "YEAR",
 561    }
 562
 563    AFTER_HAVING_MODIFIER_TRANSFORMS = {
 564        "cluster": lambda self, e: self.sql(e, "cluster"),
 565        "distribute": lambda self, e: self.sql(e, "distribute"),
 566        "sort": lambda self, e: self.sql(e, "sort"),
 567        "windows": lambda self, e: (
 568            self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
 569            if e.args.get("windows")
 570            else ""
 571        ),
 572        "qualify": lambda self, e: self.sql(e, "qualify"),
 573    }
 574
 575    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 576
 577    STRUCT_DELIMITER = ("<", ">")
 578
 579    PARAMETER_TOKEN = "@"
 580    NAMED_PLACEHOLDER_TOKEN = ":"
 581
 582    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set()
 583
 584    PROPERTIES_LOCATION = {
 585        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 586        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 587        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 588        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 589        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 590        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 591        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 592        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 593        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 594        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 595        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 596        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 597        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 598        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 599        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 600        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 601        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 602        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 603        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 604        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 605        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 606        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 607        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 608        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 609        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 610        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 611        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 612        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 613        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 614        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 615        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 616        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 617        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 618        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 619        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 620        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 621        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 622        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 623        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 624        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 625        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 626        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 627        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 628        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 629        exp.LogProperty: exp.Properties.Location.POST_NAME,
 630        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 631        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 632        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 633        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 634        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 635        exp.Order: exp.Properties.Location.POST_SCHEMA,
 636        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 637        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 638        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 639        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 640        exp.Property: exp.Properties.Location.POST_WITH,
 641        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 642        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 643        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 644        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 645        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 646        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 647        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 648        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 649        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 650        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 651        exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA,
 652        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 653        exp.Set: exp.Properties.Location.POST_SCHEMA,
 654        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 655        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 656        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 657        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 658        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 659        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 660        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 661        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 662        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 663        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 664        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 665        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 666        exp.Tags: exp.Properties.Location.POST_WITH,
 667        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 668        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 669        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 670        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 671        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 672        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 673        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 674        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 675        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 676        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 677        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 678        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 679        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 680        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 681        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 682    }
 683
 684    # Keywords that can't be used as unquoted identifier names
 685    RESERVED_KEYWORDS: t.Set[str] = set()
 686
 687    # Expressions whose comments are separated from them for better formatting
 688    WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 689        exp.Command,
 690        exp.Create,
 691        exp.Describe,
 692        exp.Delete,
 693        exp.Drop,
 694        exp.From,
 695        exp.Insert,
 696        exp.Join,
 697        exp.MultitableInserts,
 698        exp.Order,
 699        exp.Group,
 700        exp.Having,
 701        exp.Select,
 702        exp.SetOperation,
 703        exp.Update,
 704        exp.Where,
 705        exp.With,
 706    )
 707
 708    # Expressions that should not have their comments generated in maybe_comment
 709    EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 710        exp.Binary,
 711        exp.SetOperation,
 712    )
 713
 714    # Expressions that can remain unwrapped when appearing in the context of an INTERVAL
 715    UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = (
 716        exp.Column,
 717        exp.Literal,
 718        exp.Neg,
 719        exp.Paren,
 720    )
 721
 722    PARAMETERIZABLE_TEXT_TYPES = {
 723        exp.DataType.Type.NVARCHAR,
 724        exp.DataType.Type.VARCHAR,
 725        exp.DataType.Type.CHAR,
 726        exp.DataType.Type.NCHAR,
 727    }
 728
 729    # Expressions that need to have all CTEs under them bubbled up to them
 730    EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set()
 731
 732    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = ()
 733
 734    SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE
 735
 736    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 737
 738    __slots__ = (
 739        "pretty",
 740        "identify",
 741        "normalize",
 742        "pad",
 743        "_indent",
 744        "normalize_functions",
 745        "unsupported_level",
 746        "max_unsupported",
 747        "leading_comma",
 748        "max_text_width",
 749        "comments",
 750        "dialect",
 751        "unsupported_messages",
 752        "_escaped_quote_end",
 753        "_escaped_byte_quote_end",
 754        "_escaped_identifier_end",
 755        "_next_name",
 756        "_identifier_start",
 757        "_identifier_end",
 758        "_quote_json_path_key_using_brackets",
 759    )
 760
 761    def __init__(
 762        self,
 763        pretty: t.Optional[bool] = None,
 764        identify: str | bool = False,
 765        normalize: bool = False,
 766        pad: int = 2,
 767        indent: int = 2,
 768        normalize_functions: t.Optional[str | bool] = None,
 769        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 770        max_unsupported: int = 3,
 771        leading_comma: bool = False,
 772        max_text_width: int = 80,
 773        comments: bool = True,
 774        dialect: DialectType = None,
 775    ):
 776        import sqlglot
 777        from sqlglot.dialects import Dialect
 778
 779        self.pretty = pretty if pretty is not None else sqlglot.pretty
 780        self.identify = identify
 781        self.normalize = normalize
 782        self.pad = pad
 783        self._indent = indent
 784        self.unsupported_level = unsupported_level
 785        self.max_unsupported = max_unsupported
 786        self.leading_comma = leading_comma
 787        self.max_text_width = max_text_width
 788        self.comments = comments
 789        self.dialect = Dialect.get_or_raise(dialect)
 790
 791        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 792        self.normalize_functions = (
 793            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 794        )
 795
 796        self.unsupported_messages: t.List[str] = []
 797        self._escaped_quote_end: str = (
 798            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 799        )
 800        self._escaped_byte_quote_end: str = (
 801            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 802            if self.dialect.BYTE_END
 803            else ""
 804        )
 805        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 806
 807        self._next_name = name_sequence("_t")
 808
 809        self._identifier_start = self.dialect.IDENTIFIER_START
 810        self._identifier_end = self.dialect.IDENTIFIER_END
 811
 812        self._quote_json_path_key_using_brackets = True
 813
 814    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
 815        """
 816        Generates the SQL string corresponding to the given syntax tree.
 817
 818        Args:
 819            expression: The syntax tree.
 820            copy: Whether to copy the expression. The generator performs mutations so
 821                it is safer to copy.
 822
 823        Returns:
 824            The SQL string corresponding to `expression`.
 825        """
 826        if copy:
 827            expression = expression.copy()
 828
 829        expression = self.preprocess(expression)
 830
 831        self.unsupported_messages = []
 832        sql = self.sql(expression).strip()
 833
 834        if self.pretty:
 835            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 836
 837        if self.unsupported_level == ErrorLevel.IGNORE:
 838            return sql
 839
 840        if self.unsupported_level == ErrorLevel.WARN:
 841            for msg in self.unsupported_messages:
 842                logger.warning(msg)
 843        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 844            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 845
 846        return sql
 847
 848    def preprocess(self, expression: exp.Expression) -> exp.Expression:
 849        """Apply generic preprocessing transformations to a given expression."""
 850        expression = self._move_ctes_to_top_level(expression)
 851
 852        if self.ENSURE_BOOLS:
 853            from sqlglot.transforms import ensure_bools
 854
 855            expression = ensure_bools(expression)
 856
 857        return expression
 858
 859    def _move_ctes_to_top_level(self, expression: E) -> E:
 860        if (
 861            not expression.parent
 862            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 863            and any(node.parent is not expression for node in expression.find_all(exp.With))
 864        ):
 865            from sqlglot.transforms import move_ctes_to_top_level
 866
 867            expression = move_ctes_to_top_level(expression)
 868        return expression
 869
 870    def unsupported(self, message: str) -> None:
 871        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 872            raise UnsupportedError(message)
 873        self.unsupported_messages.append(message)
 874
 875    def sep(self, sep: str = " ") -> str:
 876        return f"{sep.strip()}\n" if self.pretty else sep
 877
 878    def seg(self, sql: str, sep: str = " ") -> str:
 879        return f"{self.sep(sep)}{sql}"
 880
 881    def sanitize_comment(self, comment: str) -> str:
 882        comment = " " + comment if comment[0].strip() else comment
 883        comment = comment + " " if comment[-1].strip() else comment
 884
 885        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
 886            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
 887            comment = comment.replace("*/", "* /")
 888
 889        return comment
 890
 891    def maybe_comment(
 892        self,
 893        sql: str,
 894        expression: t.Optional[exp.Expression] = None,
 895        comments: t.Optional[t.List[str]] = None,
 896        separated: bool = False,
 897    ) -> str:
 898        comments = (
 899            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 900            if self.comments
 901            else None
 902        )
 903
 904        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 905            return sql
 906
 907        comments_sql = " ".join(
 908            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
 909        )
 910
 911        if not comments_sql:
 912            return sql
 913
 914        comments_sql = self._replace_line_breaks(comments_sql)
 915
 916        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 917            return (
 918                f"{self.sep()}{comments_sql}{sql}"
 919                if not sql or sql[0].isspace()
 920                else f"{comments_sql}{self.sep()}{sql}"
 921            )
 922
 923        return f"{sql} {comments_sql}"
 924
 925    def wrap(self, expression: exp.Expression | str) -> str:
 926        this_sql = (
 927            self.sql(expression)
 928            if isinstance(expression, exp.UNWRAPPED_QUERIES)
 929            else self.sql(expression, "this")
 930        )
 931        if not this_sql:
 932            return "()"
 933
 934        this_sql = self.indent(this_sql, level=1, pad=0)
 935        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 936
 937    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 938        original = self.identify
 939        self.identify = False
 940        result = func(*args, **kwargs)
 941        self.identify = original
 942        return result
 943
 944    def normalize_func(self, name: str) -> str:
 945        if self.normalize_functions == "upper" or self.normalize_functions is True:
 946            return name.upper()
 947        if self.normalize_functions == "lower":
 948            return name.lower()
 949        return name
 950
 951    def indent(
 952        self,
 953        sql: str,
 954        level: int = 0,
 955        pad: t.Optional[int] = None,
 956        skip_first: bool = False,
 957        skip_last: bool = False,
 958    ) -> str:
 959        if not self.pretty or not sql:
 960            return sql
 961
 962        pad = self.pad if pad is None else pad
 963        lines = sql.split("\n")
 964
 965        return "\n".join(
 966            (
 967                line
 968                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 969                else f"{' ' * (level * self._indent + pad)}{line}"
 970            )
 971            for i, line in enumerate(lines)
 972        )
 973
 974    def sql(
 975        self,
 976        expression: t.Optional[str | exp.Expression],
 977        key: t.Optional[str] = None,
 978        comment: bool = True,
 979    ) -> str:
 980        if not expression:
 981            return ""
 982
 983        if isinstance(expression, str):
 984            return expression
 985
 986        if key:
 987            value = expression.args.get(key)
 988            if value:
 989                return self.sql(value)
 990            return ""
 991
 992        transform = self.TRANSFORMS.get(expression.__class__)
 993
 994        if callable(transform):
 995            sql = transform(self, expression)
 996        elif isinstance(expression, exp.Expression):
 997            exp_handler_name = f"{expression.key}_sql"
 998
 999            if hasattr(self, exp_handler_name):
1000                sql = getattr(self, exp_handler_name)(expression)
1001            elif isinstance(expression, exp.Func):
1002                sql = self.function_fallback_sql(expression)
1003            elif isinstance(expression, exp.Property):
1004                sql = self.property_sql(expression)
1005            else:
1006                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1007        else:
1008            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
1009
1010        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1011
1012    def uncache_sql(self, expression: exp.Uncache) -> str:
1013        table = self.sql(expression, "this")
1014        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1015        return f"UNCACHE TABLE{exists_sql} {table}"
1016
1017    def cache_sql(self, expression: exp.Cache) -> str:
1018        lazy = " LAZY" if expression.args.get("lazy") else ""
1019        table = self.sql(expression, "this")
1020        options = expression.args.get("options")
1021        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1022        sql = self.sql(expression, "expression")
1023        sql = f" AS{self.sep()}{sql}" if sql else ""
1024        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1025        return self.prepend_ctes(expression, sql)
1026
1027    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1028        if isinstance(expression.parent, exp.Cast):
1029            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
1030        default = "DEFAULT " if expression.args.get("default") else ""
1031        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1032
1033    def column_parts(self, expression: exp.Column) -> str:
1034        return ".".join(
1035            self.sql(part)
1036            for part in (
1037                expression.args.get("catalog"),
1038                expression.args.get("db"),
1039                expression.args.get("table"),
1040                expression.args.get("this"),
1041            )
1042            if part
1043        )
1044
1045    def column_sql(self, expression: exp.Column) -> str:
1046        join_mark = " (+)" if expression.args.get("join_mark") else ""
1047
1048        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1049            join_mark = ""
1050            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1051
1052        return f"{self.column_parts(expression)}{join_mark}"
1053
1054    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1055        return self.column_sql(expression)
1056
1057    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1058        this = self.sql(expression, "this")
1059        this = f" {this}" if this else ""
1060        position = self.sql(expression, "position")
1061        return f"{position}{this}"
1062
1063    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1064        column = self.sql(expression, "this")
1065        kind = self.sql(expression, "kind")
1066        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1067        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1068        kind = f"{sep}{kind}" if kind else ""
1069        constraints = f" {constraints}" if constraints else ""
1070        position = self.sql(expression, "position")
1071        position = f" {position}" if position else ""
1072
1073        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1074            kind = ""
1075
1076        return f"{exists}{column}{kind}{constraints}{position}"
1077
1078    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1079        this = self.sql(expression, "this")
1080        kind_sql = self.sql(expression, "kind").strip()
1081        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1082
1083    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1084        this = self.sql(expression, "this")
1085        if expression.args.get("not_null"):
1086            persisted = " PERSISTED NOT NULL"
1087        elif expression.args.get("persisted"):
1088            persisted = " PERSISTED"
1089        else:
1090            persisted = ""
1091
1092        return f"AS {this}{persisted}"
1093
1094    def autoincrementcolumnconstraint_sql(self, _) -> str:
1095        return self.token_sql(TokenType.AUTO_INCREMENT)
1096
1097    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1098        if isinstance(expression.this, list):
1099            this = self.wrap(self.expressions(expression, key="this", flat=True))
1100        else:
1101            this = self.sql(expression, "this")
1102
1103        return f"COMPRESS {this}"
1104
1105    def generatedasidentitycolumnconstraint_sql(
1106        self, expression: exp.GeneratedAsIdentityColumnConstraint
1107    ) -> str:
1108        this = ""
1109        if expression.this is not None:
1110            on_null = " ON NULL" if expression.args.get("on_null") else ""
1111            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1112
1113        start = expression.args.get("start")
1114        start = f"START WITH {start}" if start else ""
1115        increment = expression.args.get("increment")
1116        increment = f" INCREMENT BY {increment}" if increment else ""
1117        minvalue = expression.args.get("minvalue")
1118        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1119        maxvalue = expression.args.get("maxvalue")
1120        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1121        cycle = expression.args.get("cycle")
1122        cycle_sql = ""
1123
1124        if cycle is not None:
1125            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1126            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1127
1128        sequence_opts = ""
1129        if start or increment or cycle_sql:
1130            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1131            sequence_opts = f" ({sequence_opts.strip()})"
1132
1133        expr = self.sql(expression, "expression")
1134        expr = f"({expr})" if expr else "IDENTITY"
1135
1136        return f"GENERATED{this} AS {expr}{sequence_opts}"
1137
1138    def generatedasrowcolumnconstraint_sql(
1139        self, expression: exp.GeneratedAsRowColumnConstraint
1140    ) -> str:
1141        start = "START" if expression.args.get("start") else "END"
1142        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1143        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1144
1145    def periodforsystemtimeconstraint_sql(
1146        self, expression: exp.PeriodForSystemTimeConstraint
1147    ) -> str:
1148        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1149
1150    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1151        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1152
1153    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1154        desc = expression.args.get("desc")
1155        if desc is not None:
1156            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1157        options = self.expressions(expression, key="options", flat=True, sep=" ")
1158        options = f" {options}" if options else ""
1159        return f"PRIMARY KEY{options}"
1160
1161    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1162        this = self.sql(expression, "this")
1163        this = f" {this}" if this else ""
1164        index_type = expression.args.get("index_type")
1165        index_type = f" USING {index_type}" if index_type else ""
1166        on_conflict = self.sql(expression, "on_conflict")
1167        on_conflict = f" {on_conflict}" if on_conflict else ""
1168        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1169        options = self.expressions(expression, key="options", flat=True, sep=" ")
1170        options = f" {options}" if options else ""
1171        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1172
1173    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1174        input_ = expression.args.get("input_")
1175        output = expression.args.get("output")
1176        variadic = expression.args.get("variadic")
1177
1178        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1179        if variadic:
1180            return "VARIADIC"
1181
1182        if input_ and output:
1183            return f"IN{self.INOUT_SEPARATOR}OUT"
1184        if input_:
1185            return "IN"
1186        if output:
1187            return "OUT"
1188
1189        return ""
1190
1191    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1192        return self.sql(expression, "this")
1193
1194    def create_sql(self, expression: exp.Create) -> str:
1195        kind = self.sql(expression, "kind")
1196        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1197        properties = expression.args.get("properties")
1198        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1199
1200        this = self.createable_sql(expression, properties_locs)
1201
1202        properties_sql = ""
1203        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1204            exp.Properties.Location.POST_WITH
1205        ):
1206            props_ast = exp.Properties(
1207                expressions=[
1208                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1209                    *properties_locs[exp.Properties.Location.POST_WITH],
1210                ]
1211            )
1212            props_ast.parent = expression
1213            properties_sql = self.sql(props_ast)
1214
1215            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1216                properties_sql = self.sep() + properties_sql
1217            elif not self.pretty:
1218                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1219                properties_sql = f" {properties_sql}"
1220
1221        begin = " BEGIN" if expression.args.get("begin") else ""
1222        end = " END" if expression.args.get("end") else ""
1223
1224        expression_sql = self.sql(expression, "expression")
1225        if expression_sql:
1226            expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
1227
1228            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1229                postalias_props_sql = ""
1230                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1231                    postalias_props_sql = self.properties(
1232                        exp.Properties(
1233                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1234                        ),
1235                        wrapped=False,
1236                    )
1237                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1238                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1239
1240        postindex_props_sql = ""
1241        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1242            postindex_props_sql = self.properties(
1243                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1244                wrapped=False,
1245                prefix=" ",
1246            )
1247
1248        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1249        indexes = f" {indexes}" if indexes else ""
1250        index_sql = indexes + postindex_props_sql
1251
1252        replace = " OR REPLACE" if expression.args.get("replace") else ""
1253        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1254        unique = " UNIQUE" if expression.args.get("unique") else ""
1255
1256        clustered = expression.args.get("clustered")
1257        if clustered is None:
1258            clustered_sql = ""
1259        elif clustered:
1260            clustered_sql = " CLUSTERED COLUMNSTORE"
1261        else:
1262            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1263
1264        postcreate_props_sql = ""
1265        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1266            postcreate_props_sql = self.properties(
1267                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1268                sep=" ",
1269                prefix=" ",
1270                wrapped=False,
1271            )
1272
1273        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1274
1275        postexpression_props_sql = ""
1276        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1277            postexpression_props_sql = self.properties(
1278                exp.Properties(
1279                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1280                ),
1281                sep=" ",
1282                prefix=" ",
1283                wrapped=False,
1284            )
1285
1286        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1287        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1288        no_schema_binding = (
1289            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1290        )
1291
1292        clone = self.sql(expression, "clone")
1293        clone = f" {clone}" if clone else ""
1294
1295        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1296            properties_expression = f"{expression_sql}{properties_sql}"
1297        else:
1298            properties_expression = f"{properties_sql}{expression_sql}"
1299
1300        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1301        return self.prepend_ctes(expression, expression_sql)
1302
1303    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1304        start = self.sql(expression, "start")
1305        start = f"START WITH {start}" if start else ""
1306        increment = self.sql(expression, "increment")
1307        increment = f" INCREMENT BY {increment}" if increment else ""
1308        minvalue = self.sql(expression, "minvalue")
1309        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1310        maxvalue = self.sql(expression, "maxvalue")
1311        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1312        owned = self.sql(expression, "owned")
1313        owned = f" OWNED BY {owned}" if owned else ""
1314
1315        cache = expression.args.get("cache")
1316        if cache is None:
1317            cache_str = ""
1318        elif cache is True:
1319            cache_str = " CACHE"
1320        else:
1321            cache_str = f" CACHE {cache}"
1322
1323        options = self.expressions(expression, key="options", flat=True, sep=" ")
1324        options = f" {options}" if options else ""
1325
1326        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1327
1328    def clone_sql(self, expression: exp.Clone) -> str:
1329        this = self.sql(expression, "this")
1330        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1331        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1332        return f"{shallow}{keyword} {this}"
1333
1334    def describe_sql(self, expression: exp.Describe) -> str:
1335        style = expression.args.get("style")
1336        style = f" {style}" if style else ""
1337        partition = self.sql(expression, "partition")
1338        partition = f" {partition}" if partition else ""
1339        format = self.sql(expression, "format")
1340        format = f" {format}" if format else ""
1341        as_json = " AS JSON" if expression.args.get("as_json") else ""
1342
1343        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1344
1345    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1346        tag = self.sql(expression, "tag")
1347        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1348
1349    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1350        with_ = self.sql(expression, "with_")
1351        if with_:
1352            sql = f"{with_}{self.sep()}{sql}"
1353        return sql
1354
1355    def with_sql(self, expression: exp.With) -> str:
1356        sql = self.expressions(expression, flat=True)
1357        recursive = (
1358            "RECURSIVE "
1359            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1360            else ""
1361        )
1362        search = self.sql(expression, "search")
1363        search = f" {search}" if search else ""
1364
1365        return f"WITH {recursive}{sql}{search}"
1366
1367    def cte_sql(self, expression: exp.CTE) -> str:
1368        alias = expression.args.get("alias")
1369        if alias:
1370            alias.add_comments(expression.pop_comments())
1371
1372        alias_sql = self.sql(expression, "alias")
1373
1374        materialized = expression.args.get("materialized")
1375        if materialized is False:
1376            materialized = "NOT MATERIALIZED "
1377        elif materialized:
1378            materialized = "MATERIALIZED "
1379
1380        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1381        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1382
1383        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1384
1385    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1386        alias = self.sql(expression, "this")
1387        columns = self.expressions(expression, key="columns", flat=True)
1388        columns = f"({columns})" if columns else ""
1389
1390        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1391            columns = ""
1392            self.unsupported("Named columns are not supported in table alias.")
1393
1394        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1395            alias = self._next_name()
1396
1397        return f"{alias}{columns}"
1398
1399    def bitstring_sql(self, expression: exp.BitString) -> str:
1400        this = self.sql(expression, "this")
1401        if self.dialect.BIT_START:
1402            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1403        return f"{int(this, 2)}"
1404
1405    def hexstring_sql(
1406        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1407    ) -> str:
1408        this = self.sql(expression, "this")
1409        is_integer_type = expression.args.get("is_integer")
1410
1411        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1412            not self.dialect.HEX_START and not binary_function_repr
1413        ):
1414            # Integer representation will be returned if:
1415            # - The read dialect treats the hex value as integer literal but not the write
1416            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1417            return f"{int(this, 16)}"
1418
1419        if not is_integer_type:
1420            # Read dialect treats the hex value as BINARY/BLOB
1421            if binary_function_repr:
1422                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1423                return self.func(binary_function_repr, exp.Literal.string(this))
1424            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1425                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1426                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1427
1428        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1429
1430    def bytestring_sql(self, expression: exp.ByteString) -> str:
1431        this = self.sql(expression, "this")
1432        if self.dialect.BYTE_START:
1433            escaped_byte_string = self.escape_str(
1434                this,
1435                escape_backslash=False,
1436                delimiter=self.dialect.BYTE_END,
1437                escaped_delimiter=self._escaped_byte_quote_end,
1438                is_byte_string=True,
1439            )
1440            is_bytes = expression.args.get("is_bytes", False)
1441            delimited_byte_string = (
1442                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1443            )
1444            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1445                return self.sql(
1446                    exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect)
1447                )
1448            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1449                return self.sql(
1450                    exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect)
1451                )
1452
1453            return delimited_byte_string
1454        return this
1455
1456    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1457        this = self.sql(expression, "this")
1458        escape = expression.args.get("escape")
1459
1460        if self.dialect.UNICODE_START:
1461            escape_substitute = r"\\\1"
1462            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1463        else:
1464            escape_substitute = r"\\u\1"
1465            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1466
1467        if escape:
1468            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1469            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1470        else:
1471            escape_pattern = ESCAPED_UNICODE_RE
1472            escape_sql = ""
1473
1474        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1475            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1476
1477        return f"{left_quote}{this}{right_quote}{escape_sql}"
1478
1479    def rawstring_sql(self, expression: exp.RawString) -> str:
1480        string = expression.this
1481        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1482            string = string.replace("\\", "\\\\")
1483
1484        string = self.escape_str(string, escape_backslash=False)
1485        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1486
1487    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1488        this = self.sql(expression, "this")
1489        specifier = self.sql(expression, "expression")
1490        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1491        return f"{this}{specifier}"
1492
1493    def datatype_sql(self, expression: exp.DataType) -> str:
1494        nested = ""
1495        values = ""
1496
1497        expr_nested = expression.args.get("nested")
1498        interior = (
1499            self.expressions(
1500                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1501            )
1502            if expr_nested and self.pretty
1503            else self.expressions(expression, flat=True)
1504        )
1505
1506        type_value = expression.this
1507        if type_value in self.UNSUPPORTED_TYPES:
1508            self.unsupported(
1509                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1510            )
1511
1512        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1513            type_sql = self.sql(expression, "kind")
1514        else:
1515            type_sql = (
1516                self.TYPE_MAPPING.get(type_value, type_value.value)
1517                if isinstance(type_value, exp.DataType.Type)
1518                else type_value
1519            )
1520
1521        if interior:
1522            if expr_nested:
1523                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1524                if expression.args.get("values") is not None:
1525                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1526                    values = self.expressions(expression, key="values", flat=True)
1527                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1528            elif type_value == exp.DataType.Type.INTERVAL:
1529                nested = f" {interior}"
1530            else:
1531                nested = f"({interior})"
1532
1533        type_sql = f"{type_sql}{nested}{values}"
1534        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1535            exp.DataType.Type.TIMETZ,
1536            exp.DataType.Type.TIMESTAMPTZ,
1537        ):
1538            type_sql = f"{type_sql} WITH TIME ZONE"
1539
1540        return type_sql
1541
1542    def directory_sql(self, expression: exp.Directory) -> str:
1543        local = "LOCAL " if expression.args.get("local") else ""
1544        row_format = self.sql(expression, "row_format")
1545        row_format = f" {row_format}" if row_format else ""
1546        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1547
1548    def delete_sql(self, expression: exp.Delete) -> str:
1549        this = self.sql(expression, "this")
1550        this = f" FROM {this}" if this else ""
1551        using = self.expressions(expression, key="using")
1552        using = f" USING {using}" if using else ""
1553        cluster = self.sql(expression, "cluster")
1554        cluster = f" {cluster}" if cluster else ""
1555        where = self.sql(expression, "where")
1556        returning = self.sql(expression, "returning")
1557        order = self.sql(expression, "order")
1558        limit = self.sql(expression, "limit")
1559        tables = self.expressions(expression, key="tables")
1560        tables = f" {tables}" if tables else ""
1561        if self.RETURNING_END:
1562            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1563        else:
1564            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1565        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1566
1567    def drop_sql(self, expression: exp.Drop) -> str:
1568        this = self.sql(expression, "this")
1569        expressions = self.expressions(expression, flat=True)
1570        expressions = f" ({expressions})" if expressions else ""
1571        kind = expression.args["kind"]
1572        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1573        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1574        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1575        on_cluster = self.sql(expression, "cluster")
1576        on_cluster = f" {on_cluster}" if on_cluster else ""
1577        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1578        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1579        cascade = " CASCADE" if expression.args.get("cascade") else ""
1580        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1581        purge = " PURGE" if expression.args.get("purge") else ""
1582        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1583
1584    def set_operation(self, expression: exp.SetOperation) -> str:
1585        op_type = type(expression)
1586        op_name = op_type.key.upper()
1587
1588        distinct = expression.args.get("distinct")
1589        if (
1590            distinct is False
1591            and op_type in (exp.Except, exp.Intersect)
1592            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1593        ):
1594            self.unsupported(f"{op_name} ALL is not supported")
1595
1596        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1597
1598        if distinct is None:
1599            distinct = default_distinct
1600            if distinct is None:
1601                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1602
1603        if distinct is default_distinct:
1604            distinct_or_all = ""
1605        else:
1606            distinct_or_all = " DISTINCT" if distinct else " ALL"
1607
1608        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1609        side_kind = f"{side_kind} " if side_kind else ""
1610
1611        by_name = " BY NAME" if expression.args.get("by_name") else ""
1612        on = self.expressions(expression, key="on", flat=True)
1613        on = f" ON ({on})" if on else ""
1614
1615        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1616
1617    def set_operations(self, expression: exp.SetOperation) -> str:
1618        if not self.SET_OP_MODIFIERS:
1619            limit = expression.args.get("limit")
1620            order = expression.args.get("order")
1621
1622            if limit or order:
1623                select = self._move_ctes_to_top_level(
1624                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1625                )
1626
1627                if limit:
1628                    select = select.limit(limit.pop(), copy=False)
1629                if order:
1630                    select = select.order_by(order.pop(), copy=False)
1631                return self.sql(select)
1632
1633        sqls: t.List[str] = []
1634        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1635
1636        while stack:
1637            node = stack.pop()
1638
1639            if isinstance(node, exp.SetOperation):
1640                stack.append(node.expression)
1641                stack.append(
1642                    self.maybe_comment(
1643                        self.set_operation(node), comments=node.comments, separated=True
1644                    )
1645                )
1646                stack.append(node.this)
1647            else:
1648                sqls.append(self.sql(node))
1649
1650        this = self.sep().join(sqls)
1651        this = self.query_modifiers(expression, this)
1652        return self.prepend_ctes(expression, this)
1653
1654    def fetch_sql(self, expression: exp.Fetch) -> str:
1655        direction = expression.args.get("direction")
1656        direction = f" {direction}" if direction else ""
1657        count = self.sql(expression, "count")
1658        count = f" {count}" if count else ""
1659        limit_options = self.sql(expression, "limit_options")
1660        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1661        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1662
1663    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1664        percent = " PERCENT" if expression.args.get("percent") else ""
1665        rows = " ROWS" if expression.args.get("rows") else ""
1666        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1667        if not with_ties and rows:
1668            with_ties = " ONLY"
1669        return f"{percent}{rows}{with_ties}"
1670
1671    def filter_sql(self, expression: exp.Filter) -> str:
1672        if self.AGGREGATE_FILTER_SUPPORTED:
1673            this = self.sql(expression, "this")
1674            where = self.sql(expression, "expression").strip()
1675            return f"{this} FILTER({where})"
1676
1677        agg = expression.this
1678        agg_arg = agg.this
1679        cond = expression.expression.this
1680        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1681        return self.sql(agg)
1682
1683    def hint_sql(self, expression: exp.Hint) -> str:
1684        if not self.QUERY_HINTS:
1685            self.unsupported("Hints are not supported")
1686            return ""
1687
1688        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1689
1690    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1691        using = self.sql(expression, "using")
1692        using = f" USING {using}" if using else ""
1693        columns = self.expressions(expression, key="columns", flat=True)
1694        columns = f"({columns})" if columns else ""
1695        partition_by = self.expressions(expression, key="partition_by", flat=True)
1696        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1697        where = self.sql(expression, "where")
1698        include = self.expressions(expression, key="include", flat=True)
1699        if include:
1700            include = f" INCLUDE ({include})"
1701        with_storage = self.expressions(expression, key="with_storage", flat=True)
1702        with_storage = f" WITH ({with_storage})" if with_storage else ""
1703        tablespace = self.sql(expression, "tablespace")
1704        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1705        on = self.sql(expression, "on")
1706        on = f" ON {on}" if on else ""
1707
1708        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1709
1710    def index_sql(self, expression: exp.Index) -> str:
1711        unique = "UNIQUE " if expression.args.get("unique") else ""
1712        primary = "PRIMARY " if expression.args.get("primary") else ""
1713        amp = "AMP " if expression.args.get("amp") else ""
1714        name = self.sql(expression, "this")
1715        name = f"{name} " if name else ""
1716        table = self.sql(expression, "table")
1717        table = f"{self.INDEX_ON} {table}" if table else ""
1718
1719        index = "INDEX " if not table else ""
1720
1721        params = self.sql(expression, "params")
1722        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1723
1724    def identifier_sql(self, expression: exp.Identifier) -> str:
1725        text = expression.name
1726        lower = text.lower()
1727        text = lower if self.normalize and not expression.quoted else text
1728        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1729        if (
1730            expression.quoted
1731            or self.dialect.can_quote(expression, self.identify)
1732            or lower in self.RESERVED_KEYWORDS
1733            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1734        ):
1735            text = f"{self._identifier_start}{text}{self._identifier_end}"
1736        return text
1737
1738    def hex_sql(self, expression: exp.Hex) -> str:
1739        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1740        if self.dialect.HEX_LOWERCASE:
1741            text = self.func("LOWER", text)
1742
1743        return text
1744
1745    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1746        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1747        if not self.dialect.HEX_LOWERCASE:
1748            text = self.func("LOWER", text)
1749        return text
1750
1751    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1752        input_format = self.sql(expression, "input_format")
1753        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1754        output_format = self.sql(expression, "output_format")
1755        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1756        return self.sep().join((input_format, output_format))
1757
1758    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1759        string = self.sql(exp.Literal.string(expression.name))
1760        return f"{prefix}{string}"
1761
1762    def partition_sql(self, expression: exp.Partition) -> str:
1763        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1764        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1765
1766    def properties_sql(self, expression: exp.Properties) -> str:
1767        root_properties = []
1768        with_properties = []
1769
1770        for p in expression.expressions:
1771            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1772            if p_loc == exp.Properties.Location.POST_WITH:
1773                with_properties.append(p)
1774            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1775                root_properties.append(p)
1776
1777        root_props_ast = exp.Properties(expressions=root_properties)
1778        root_props_ast.parent = expression.parent
1779
1780        with_props_ast = exp.Properties(expressions=with_properties)
1781        with_props_ast.parent = expression.parent
1782
1783        root_props = self.root_properties(root_props_ast)
1784        with_props = self.with_properties(with_props_ast)
1785
1786        if root_props and with_props and not self.pretty:
1787            with_props = " " + with_props
1788
1789        return root_props + with_props
1790
1791    def root_properties(self, properties: exp.Properties) -> str:
1792        if properties.expressions:
1793            return self.expressions(properties, indent=False, sep=" ")
1794        return ""
1795
1796    def properties(
1797        self,
1798        properties: exp.Properties,
1799        prefix: str = "",
1800        sep: str = ", ",
1801        suffix: str = "",
1802        wrapped: bool = True,
1803    ) -> str:
1804        if properties.expressions:
1805            expressions = self.expressions(properties, sep=sep, indent=False)
1806            if expressions:
1807                expressions = self.wrap(expressions) if wrapped else expressions
1808                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1809        return ""
1810
1811    def with_properties(self, properties: exp.Properties) -> str:
1812        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1813
1814    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1815        properties_locs = defaultdict(list)
1816        for p in properties.expressions:
1817            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1818            if p_loc != exp.Properties.Location.UNSUPPORTED:
1819                properties_locs[p_loc].append(p)
1820            else:
1821                self.unsupported(f"Unsupported property {p.key}")
1822
1823        return properties_locs
1824
1825    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1826        if isinstance(expression.this, exp.Dot):
1827            return self.sql(expression, "this")
1828        return f"'{expression.name}'" if string_key else expression.name
1829
1830    def property_sql(self, expression: exp.Property) -> str:
1831        property_cls = expression.__class__
1832        if property_cls == exp.Property:
1833            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1834
1835        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1836        if not property_name:
1837            self.unsupported(f"Unsupported property {expression.key}")
1838
1839        return f"{property_name}={self.sql(expression, 'this')}"
1840
1841    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1842        if self.SUPPORTS_CREATE_TABLE_LIKE:
1843            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1844            options = f" {options}" if options else ""
1845
1846            like = f"LIKE {self.sql(expression, 'this')}{options}"
1847            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1848                like = f"({like})"
1849
1850            return like
1851
1852        if expression.expressions:
1853            self.unsupported("Transpilation of LIKE property options is unsupported")
1854
1855        select = exp.select("*").from_(expression.this).limit(0)
1856        return f"AS {self.sql(select)}"
1857
1858    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1859        no = "NO " if expression.args.get("no") else ""
1860        protection = " PROTECTION" if expression.args.get("protection") else ""
1861        return f"{no}FALLBACK{protection}"
1862
1863    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1864        no = "NO " if expression.args.get("no") else ""
1865        local = expression.args.get("local")
1866        local = f"{local} " if local else ""
1867        dual = "DUAL " if expression.args.get("dual") else ""
1868        before = "BEFORE " if expression.args.get("before") else ""
1869        after = "AFTER " if expression.args.get("after") else ""
1870        return f"{no}{local}{dual}{before}{after}JOURNAL"
1871
1872    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1873        freespace = self.sql(expression, "this")
1874        percent = " PERCENT" if expression.args.get("percent") else ""
1875        return f"FREESPACE={freespace}{percent}"
1876
1877    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1878        if expression.args.get("default"):
1879            property = "DEFAULT"
1880        elif expression.args.get("on"):
1881            property = "ON"
1882        else:
1883            property = "OFF"
1884        return f"CHECKSUM={property}"
1885
1886    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1887        if expression.args.get("no"):
1888            return "NO MERGEBLOCKRATIO"
1889        if expression.args.get("default"):
1890            return "DEFAULT MERGEBLOCKRATIO"
1891
1892        percent = " PERCENT" if expression.args.get("percent") else ""
1893        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1894
1895    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1896        default = expression.args.get("default")
1897        minimum = expression.args.get("minimum")
1898        maximum = expression.args.get("maximum")
1899        if default or minimum or maximum:
1900            if default:
1901                prop = "DEFAULT"
1902            elif minimum:
1903                prop = "MINIMUM"
1904            else:
1905                prop = "MAXIMUM"
1906            return f"{prop} DATABLOCKSIZE"
1907        units = expression.args.get("units")
1908        units = f" {units}" if units else ""
1909        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
1910
1911    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1912        autotemp = expression.args.get("autotemp")
1913        always = expression.args.get("always")
1914        default = expression.args.get("default")
1915        manual = expression.args.get("manual")
1916        never = expression.args.get("never")
1917
1918        if autotemp is not None:
1919            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1920        elif always:
1921            prop = "ALWAYS"
1922        elif default:
1923            prop = "DEFAULT"
1924        elif manual:
1925            prop = "MANUAL"
1926        elif never:
1927            prop = "NEVER"
1928        return f"BLOCKCOMPRESSION={prop}"
1929
1930    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1931        no = expression.args.get("no")
1932        no = " NO" if no else ""
1933        concurrent = expression.args.get("concurrent")
1934        concurrent = " CONCURRENT" if concurrent else ""
1935        target = self.sql(expression, "target")
1936        target = f" {target}" if target else ""
1937        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1938
1939    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1940        if isinstance(expression.this, list):
1941            return f"IN ({self.expressions(expression, key='this', flat=True)})"
1942        if expression.this:
1943            modulus = self.sql(expression, "this")
1944            remainder = self.sql(expression, "expression")
1945            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1946
1947        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1948        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1949        return f"FROM ({from_expressions}) TO ({to_expressions})"
1950
1951    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1952        this = self.sql(expression, "this")
1953
1954        for_values_or_default = expression.expression
1955        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1956            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1957        else:
1958            for_values_or_default = " DEFAULT"
1959
1960        return f"PARTITION OF {this}{for_values_or_default}"
1961
1962    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1963        kind = expression.args.get("kind")
1964        this = f" {self.sql(expression, 'this')}" if expression.this else ""
1965        for_or_in = expression.args.get("for_or_in")
1966        for_or_in = f" {for_or_in}" if for_or_in else ""
1967        lock_type = expression.args.get("lock_type")
1968        override = " OVERRIDE" if expression.args.get("override") else ""
1969        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1970
1971    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1972        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1973        statistics = expression.args.get("statistics")
1974        statistics_sql = ""
1975        if statistics is not None:
1976            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1977        return f"{data_sql}{statistics_sql}"
1978
1979    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1980        this = self.sql(expression, "this")
1981        this = f"HISTORY_TABLE={this}" if this else ""
1982        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
1983        data_consistency = (
1984            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
1985        )
1986        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
1987        retention_period = (
1988            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
1989        )
1990
1991        if this:
1992            on_sql = self.func("ON", this, data_consistency, retention_period)
1993        else:
1994            on_sql = "ON" if expression.args.get("on") else "OFF"
1995
1996        sql = f"SYSTEM_VERSIONING={on_sql}"
1997
1998        return f"WITH({sql})" if expression.args.get("with_") else sql
1999
2000    def insert_sql(self, expression: exp.Insert) -> str:
2001        hint = self.sql(expression, "hint")
2002        overwrite = expression.args.get("overwrite")
2003
2004        if isinstance(expression.this, exp.Directory):
2005            this = " OVERWRITE" if overwrite else " INTO"
2006        else:
2007            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2008
2009        stored = self.sql(expression, "stored")
2010        stored = f" {stored}" if stored else ""
2011        alternative = expression.args.get("alternative")
2012        alternative = f" OR {alternative}" if alternative else ""
2013        ignore = " IGNORE" if expression.args.get("ignore") else ""
2014        is_function = expression.args.get("is_function")
2015        if is_function:
2016            this = f"{this} FUNCTION"
2017        this = f"{this} {self.sql(expression, 'this')}"
2018
2019        exists = " IF EXISTS" if expression.args.get("exists") else ""
2020        where = self.sql(expression, "where")
2021        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2022        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2023        on_conflict = self.sql(expression, "conflict")
2024        on_conflict = f" {on_conflict}" if on_conflict else ""
2025        by_name = " BY NAME" if expression.args.get("by_name") else ""
2026        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2027        returning = self.sql(expression, "returning")
2028
2029        if self.RETURNING_END:
2030            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2031        else:
2032            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2033
2034        partition_by = self.sql(expression, "partition")
2035        partition_by = f" {partition_by}" if partition_by else ""
2036        settings = self.sql(expression, "settings")
2037        settings = f" {settings}" if settings else ""
2038
2039        source = self.sql(expression, "source")
2040        source = f"TABLE {source}" if source else ""
2041
2042        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2043        return self.prepend_ctes(expression, sql)
2044
2045    def introducer_sql(self, expression: exp.Introducer) -> str:
2046        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2047
2048    def kill_sql(self, expression: exp.Kill) -> str:
2049        kind = self.sql(expression, "kind")
2050        kind = f" {kind}" if kind else ""
2051        this = self.sql(expression, "this")
2052        this = f" {this}" if this else ""
2053        return f"KILL{kind}{this}"
2054
2055    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2056        return expression.name
2057
2058    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2059        return expression.name
2060
2061    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2062        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2063
2064        constraint = self.sql(expression, "constraint")
2065        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2066
2067        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2068        if conflict_keys:
2069            conflict_keys = f"({conflict_keys})"
2070
2071        index_predicate = self.sql(expression, "index_predicate")
2072        conflict_keys = f"{conflict_keys}{index_predicate} "
2073
2074        action = self.sql(expression, "action")
2075
2076        expressions = self.expressions(expression, flat=True)
2077        if expressions:
2078            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2079            expressions = f" {set_keyword}{expressions}"
2080
2081        where = self.sql(expression, "where")
2082        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2083
2084    def returning_sql(self, expression: exp.Returning) -> str:
2085        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2086
2087    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2088        fields = self.sql(expression, "fields")
2089        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2090        escaped = self.sql(expression, "escaped")
2091        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2092        items = self.sql(expression, "collection_items")
2093        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2094        keys = self.sql(expression, "map_keys")
2095        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2096        lines = self.sql(expression, "lines")
2097        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2098        null = self.sql(expression, "null")
2099        null = f" NULL DEFINED AS {null}" if null else ""
2100        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2101
2102    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2103        return f"WITH ({self.expressions(expression, flat=True)})"
2104
2105    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2106        this = f"{self.sql(expression, 'this')} INDEX"
2107        target = self.sql(expression, "target")
2108        target = f" FOR {target}" if target else ""
2109        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2110
2111    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2112        this = self.sql(expression, "this")
2113        kind = self.sql(expression, "kind")
2114        expr = self.sql(expression, "expression")
2115        return f"{this} ({kind} => {expr})"
2116
2117    def table_parts(self, expression: exp.Table) -> str:
2118        return ".".join(
2119            self.sql(part)
2120            for part in (
2121                expression.args.get("catalog"),
2122                expression.args.get("db"),
2123                expression.args.get("this"),
2124            )
2125            if part is not None
2126        )
2127
2128    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2129        table = self.table_parts(expression)
2130        only = "ONLY " if expression.args.get("only") else ""
2131        partition = self.sql(expression, "partition")
2132        partition = f" {partition}" if partition else ""
2133        version = self.sql(expression, "version")
2134        version = f" {version}" if version else ""
2135        alias = self.sql(expression, "alias")
2136        alias = f"{sep}{alias}" if alias else ""
2137
2138        sample = self.sql(expression, "sample")
2139        if self.dialect.ALIAS_POST_TABLESAMPLE:
2140            sample_pre_alias = sample
2141            sample_post_alias = ""
2142        else:
2143            sample_pre_alias = ""
2144            sample_post_alias = sample
2145
2146        hints = self.expressions(expression, key="hints", sep=" ")
2147        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2148        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2149        joins = self.indent(
2150            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2151        )
2152        laterals = self.expressions(expression, key="laterals", sep="")
2153
2154        file_format = self.sql(expression, "format")
2155        if file_format:
2156            pattern = self.sql(expression, "pattern")
2157            pattern = f", PATTERN => {pattern}" if pattern else ""
2158            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2159
2160        ordinality = expression.args.get("ordinality") or ""
2161        if ordinality:
2162            ordinality = f" WITH ORDINALITY{alias}"
2163            alias = ""
2164
2165        when = self.sql(expression, "when")
2166        if when:
2167            table = f"{table} {when}"
2168
2169        changes = self.sql(expression, "changes")
2170        changes = f" {changes}" if changes else ""
2171
2172        rows_from = self.expressions(expression, key="rows_from")
2173        if rows_from:
2174            table = f"ROWS FROM {self.wrap(rows_from)}"
2175
2176        indexed = expression.args.get("indexed")
2177        if indexed is not None:
2178            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2179        else:
2180            indexed = ""
2181
2182        return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{indexed}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2183
2184    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2185        table = self.func("TABLE", expression.this)
2186        alias = self.sql(expression, "alias")
2187        alias = f" AS {alias}" if alias else ""
2188        sample = self.sql(expression, "sample")
2189        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2190        joins = self.indent(
2191            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2192        )
2193        return f"{table}{alias}{pivots}{sample}{joins}"
2194
2195    def tablesample_sql(
2196        self,
2197        expression: exp.TableSample,
2198        tablesample_keyword: t.Optional[str] = None,
2199    ) -> str:
2200        method = self.sql(expression, "method")
2201        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2202        numerator = self.sql(expression, "bucket_numerator")
2203        denominator = self.sql(expression, "bucket_denominator")
2204        field = self.sql(expression, "bucket_field")
2205        field = f" ON {field}" if field else ""
2206        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2207        seed = self.sql(expression, "seed")
2208        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2209
2210        size = self.sql(expression, "size")
2211        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2212            size = f"{size} ROWS"
2213
2214        percent = self.sql(expression, "percent")
2215        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2216            percent = f"{percent} PERCENT"
2217
2218        expr = f"{bucket}{percent}{size}"
2219        if self.TABLESAMPLE_REQUIRES_PARENS:
2220            expr = f"({expr})"
2221
2222        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2223
2224    def pivot_sql(self, expression: exp.Pivot) -> str:
2225        expressions = self.expressions(expression, flat=True)
2226        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2227
2228        group = self.sql(expression, "group")
2229
2230        if expression.this:
2231            this = self.sql(expression, "this")
2232            if not expressions:
2233                sql = f"UNPIVOT {this}"
2234            else:
2235                on = f"{self.seg('ON')} {expressions}"
2236                into = self.sql(expression, "into")
2237                into = f"{self.seg('INTO')} {into}" if into else ""
2238                using = self.expressions(expression, key="using", flat=True)
2239                using = f"{self.seg('USING')} {using}" if using else ""
2240                sql = f"{direction} {this}{on}{into}{using}{group}"
2241            return self.prepend_ctes(expression, sql)
2242
2243        alias = self.sql(expression, "alias")
2244        alias = f" AS {alias}" if alias else ""
2245
2246        fields = self.expressions(
2247            expression,
2248            "fields",
2249            sep=" ",
2250            dynamic=True,
2251            new_line=True,
2252            skip_first=True,
2253            skip_last=True,
2254        )
2255
2256        include_nulls = expression.args.get("include_nulls")
2257        if include_nulls is not None:
2258            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2259        else:
2260            nulls = ""
2261
2262        default_on_null = self.sql(expression, "default_on_null")
2263        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2264        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2265        return self.prepend_ctes(expression, sql)
2266
2267    def version_sql(self, expression: exp.Version) -> str:
2268        this = f"FOR {expression.name}"
2269        kind = expression.text("kind")
2270        expr = self.sql(expression, "expression")
2271        return f"{this} {kind} {expr}"
2272
2273    def tuple_sql(self, expression: exp.Tuple) -> str:
2274        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2275
2276    def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]:
2277        """
2278        Returns (join_sql, from_sql) for UPDATE statements.
2279        - join_sql: placed after UPDATE table, before SET
2280        - from_sql: placed after SET clause (standard position)
2281        Dialects like MySQL need to convert FROM to JOIN syntax.
2282        """
2283        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2284            return ("", self.sql(expression, "from_"))
2285
2286        # Qualify unqualified columns in SET clause with the target table
2287        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2288        target_table = expression.this
2289        if isinstance(target_table, exp.Table):
2290            target_name = exp.to_identifier(target_table.alias_or_name)
2291            for eq in expression.expressions:
2292                col = eq.this
2293                if isinstance(col, exp.Column) and not col.table:
2294                    col.set("table", target_name)
2295
2296        table = from_expr.this
2297        if nested_joins := table.args.get("joins", []):
2298            table.set("joins", None)
2299
2300        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2301        for nested in nested_joins:
2302            if not nested.args.get("on") and not nested.args.get("using"):
2303                nested.set("on", exp.true())
2304            join_sql += self.sql(nested)
2305
2306        return (join_sql, "")
2307
2308    def update_sql(self, expression: exp.Update) -> str:
2309        this = self.sql(expression, "this")
2310        join_sql, from_sql = self._update_from_joins_sql(expression)
2311        set_sql = self.expressions(expression, flat=True)
2312        where_sql = self.sql(expression, "where")
2313        returning = self.sql(expression, "returning")
2314        order = self.sql(expression, "order")
2315        limit = self.sql(expression, "limit")
2316        if self.RETURNING_END:
2317            expression_sql = f"{from_sql}{where_sql}{returning}"
2318        else:
2319            expression_sql = f"{returning}{from_sql}{where_sql}"
2320        options = self.expressions(expression, key="options")
2321        options = f" OPTION({options})" if options else ""
2322        sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2323        return self.prepend_ctes(expression, sql)
2324
2325    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2326        values_as_table = values_as_table and self.VALUES_AS_TABLE
2327
2328        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2329        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2330            args = self.expressions(expression)
2331            alias = self.sql(expression, "alias")
2332            values = f"VALUES{self.seg('')}{args}"
2333            values = (
2334                f"({values})"
2335                if self.WRAP_DERIVED_VALUES
2336                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2337                else values
2338            )
2339            values = self.query_modifiers(expression, values)
2340            return f"{values} AS {alias}" if alias else values
2341
2342        # Converts `VALUES...` expression into a series of select unions.
2343        alias_node = expression.args.get("alias")
2344        column_names = alias_node and alias_node.columns
2345
2346        selects: t.List[exp.Query] = []
2347
2348        for i, tup in enumerate(expression.expressions):
2349            row = tup.expressions
2350
2351            if i == 0 and column_names:
2352                row = [
2353                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2354                ]
2355
2356            selects.append(exp.Select(expressions=row))
2357
2358        if self.pretty:
2359            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2360            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2361            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2362            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2363            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2364
2365        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2366        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2367        return f"({unions}){alias}"
2368
2369    def var_sql(self, expression: exp.Var) -> str:
2370        return self.sql(expression, "this")
2371
2372    @unsupported_args("expressions")
2373    def into_sql(self, expression: exp.Into) -> str:
2374        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2375        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2376        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2377
2378    def from_sql(self, expression: exp.From) -> str:
2379        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2380
2381    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2382        grouping_sets = self.expressions(expression, indent=False)
2383        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2384
2385    def rollup_sql(self, expression: exp.Rollup) -> str:
2386        expressions = self.expressions(expression, indent=False)
2387        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2388
2389    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2390        this = self.sql(expression, "this")
2391
2392        columns = self.expressions(expression, flat=True)
2393
2394        from_sql = self.sql(expression, "from_index")
2395        from_sql = f" FROM {from_sql}" if from_sql else ""
2396
2397        properties = expression.args.get("properties")
2398        properties_sql = (
2399            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2400        )
2401
2402        return f"{this}({columns}){from_sql}{properties_sql}"
2403
2404    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2405        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2406
2407    def cube_sql(self, expression: exp.Cube) -> str:
2408        expressions = self.expressions(expression, indent=False)
2409        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2410
2411    def group_sql(self, expression: exp.Group) -> str:
2412        group_by_all = expression.args.get("all")
2413        if group_by_all is True:
2414            modifier = " ALL"
2415        elif group_by_all is False:
2416            modifier = " DISTINCT"
2417        else:
2418            modifier = ""
2419
2420        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2421
2422        grouping_sets = self.expressions(expression, key="grouping_sets")
2423        cube = self.expressions(expression, key="cube")
2424        rollup = self.expressions(expression, key="rollup")
2425
2426        groupings = csv(
2427            self.seg(grouping_sets) if grouping_sets else "",
2428            self.seg(cube) if cube else "",
2429            self.seg(rollup) if rollup else "",
2430            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2431            sep=self.GROUPINGS_SEP,
2432        )
2433
2434        if (
2435            expression.expressions
2436            and groupings
2437            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2438        ):
2439            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2440
2441        return f"{group_by}{groupings}"
2442
2443    def having_sql(self, expression: exp.Having) -> str:
2444        this = self.indent(self.sql(expression, "this"))
2445        return f"{self.seg('HAVING')}{self.sep()}{this}"
2446
2447    def connect_sql(self, expression: exp.Connect) -> str:
2448        start = self.sql(expression, "start")
2449        start = self.seg(f"START WITH {start}") if start else ""
2450        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2451        connect = self.sql(expression, "connect")
2452        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2453        return start + connect
2454
2455    def prior_sql(self, expression: exp.Prior) -> str:
2456        return f"PRIOR {self.sql(expression, 'this')}"
2457
2458    def join_sql(self, expression: exp.Join) -> str:
2459        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2460            side = None
2461        else:
2462            side = expression.side
2463
2464        op_sql = " ".join(
2465            op
2466            for op in (
2467                expression.method,
2468                "GLOBAL" if expression.args.get("global_") else None,
2469                side,
2470                expression.kind,
2471                expression.hint if self.JOIN_HINTS else None,
2472                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2473            )
2474            if op
2475        )
2476        match_cond = self.sql(expression, "match_condition")
2477        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2478        on_sql = self.sql(expression, "on")
2479        using = expression.args.get("using")
2480
2481        if not on_sql and using:
2482            on_sql = csv(*(self.sql(column) for column in using))
2483
2484        this = expression.this
2485        this_sql = self.sql(this)
2486
2487        exprs = self.expressions(expression)
2488        if exprs:
2489            this_sql = f"{this_sql},{self.seg(exprs)}"
2490
2491        if on_sql:
2492            on_sql = self.indent(on_sql, skip_first=True)
2493            space = self.seg(" " * self.pad) if self.pretty else " "
2494            if using:
2495                on_sql = f"{space}USING ({on_sql})"
2496            else:
2497                on_sql = f"{space}ON {on_sql}"
2498        elif not op_sql:
2499            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2500                return f" {this_sql}"
2501
2502            return f", {this_sql}"
2503
2504        if op_sql != "STRAIGHT_JOIN":
2505            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2506
2507        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2508        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2509
2510    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2511        args = self.expressions(expression, flat=True)
2512        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2513        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2514
2515    def lateral_op(self, expression: exp.Lateral) -> str:
2516        cross_apply = expression.args.get("cross_apply")
2517
2518        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2519        if cross_apply is True:
2520            op = "INNER JOIN "
2521        elif cross_apply is False:
2522            op = "LEFT JOIN "
2523        else:
2524            op = ""
2525
2526        return f"{op}LATERAL"
2527
2528    def lateral_sql(self, expression: exp.Lateral) -> str:
2529        this = self.sql(expression, "this")
2530
2531        if expression.args.get("view"):
2532            alias = expression.args["alias"]
2533            columns = self.expressions(alias, key="columns", flat=True)
2534            table = f" {alias.name}" if alias.name else ""
2535            columns = f" AS {columns}" if columns else ""
2536            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2537            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2538
2539        alias = self.sql(expression, "alias")
2540        alias = f" AS {alias}" if alias else ""
2541
2542        ordinality = expression.args.get("ordinality") or ""
2543        if ordinality:
2544            ordinality = f" WITH ORDINALITY{alias}"
2545            alias = ""
2546
2547        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2548
2549    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2550        this = self.sql(expression, "this")
2551
2552        args = [
2553            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2554            for e in (expression.args.get(k) for k in ("offset", "expression"))
2555            if e
2556        ]
2557
2558        args_sql = ", ".join(self.sql(e) for e in args)
2559        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2560        expressions = self.expressions(expression, flat=True)
2561        limit_options = self.sql(expression, "limit_options")
2562        expressions = f" BY {expressions}" if expressions else ""
2563
2564        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2565
2566    def offset_sql(self, expression: exp.Offset) -> str:
2567        this = self.sql(expression, "this")
2568        value = expression.expression
2569        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2570        expressions = self.expressions(expression, flat=True)
2571        expressions = f" BY {expressions}" if expressions else ""
2572        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2573
2574    def setitem_sql(self, expression: exp.SetItem) -> str:
2575        kind = self.sql(expression, "kind")
2576        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2577            kind = ""
2578        else:
2579            kind = f"{kind} " if kind else ""
2580        this = self.sql(expression, "this")
2581        expressions = self.expressions(expression)
2582        collate = self.sql(expression, "collate")
2583        collate = f" COLLATE {collate}" if collate else ""
2584        global_ = "GLOBAL " if expression.args.get("global_") else ""
2585        return f"{global_}{kind}{this}{expressions}{collate}"
2586
2587    def set_sql(self, expression: exp.Set) -> str:
2588        expressions = f" {self.expressions(expression, flat=True)}"
2589        tag = " TAG" if expression.args.get("tag") else ""
2590        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2591
2592    def queryband_sql(self, expression: exp.QueryBand) -> str:
2593        this = self.sql(expression, "this")
2594        update = " UPDATE" if expression.args.get("update") else ""
2595        scope = self.sql(expression, "scope")
2596        scope = f" FOR {scope}" if scope else ""
2597
2598        return f"QUERY_BAND = {this}{update}{scope}"
2599
2600    def pragma_sql(self, expression: exp.Pragma) -> str:
2601        return f"PRAGMA {self.sql(expression, 'this')}"
2602
2603    def lock_sql(self, expression: exp.Lock) -> str:
2604        if not self.LOCKING_READS_SUPPORTED:
2605            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2606            return ""
2607
2608        update = expression.args["update"]
2609        key = expression.args.get("key")
2610        if update:
2611            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2612        else:
2613            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2614        expressions = self.expressions(expression, flat=True)
2615        expressions = f" OF {expressions}" if expressions else ""
2616        wait = expression.args.get("wait")
2617
2618        if wait is not None:
2619            if isinstance(wait, exp.Literal):
2620                wait = f" WAIT {self.sql(wait)}"
2621            else:
2622                wait = " NOWAIT" if wait else " SKIP LOCKED"
2623
2624        return f"{lock_type}{expressions}{wait or ''}"
2625
2626    def literal_sql(self, expression: exp.Literal) -> str:
2627        text = expression.this or ""
2628        if expression.is_string:
2629            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2630        return text
2631
2632    def escape_str(
2633        self,
2634        text: str,
2635        escape_backslash: bool = True,
2636        delimiter: t.Optional[str] = None,
2637        escaped_delimiter: t.Optional[str] = None,
2638        is_byte_string: bool = False,
2639    ) -> str:
2640        if is_byte_string:
2641            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
2642        else:
2643            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
2644
2645        if supports_escape_sequences:
2646            text = "".join(
2647                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
2648                for ch in text
2649            )
2650
2651        delimiter = delimiter or self.dialect.QUOTE_END
2652        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2653
2654        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2655
2656    def loaddata_sql(self, expression: exp.LoadData) -> str:
2657        local = " LOCAL" if expression.args.get("local") else ""
2658        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2659        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2660        this = f" INTO TABLE {self.sql(expression, 'this')}"
2661        partition = self.sql(expression, "partition")
2662        partition = f" {partition}" if partition else ""
2663        input_format = self.sql(expression, "input_format")
2664        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2665        serde = self.sql(expression, "serde")
2666        serde = f" SERDE {serde}" if serde else ""
2667        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2668
2669    def null_sql(self, *_) -> str:
2670        return "NULL"
2671
2672    def boolean_sql(self, expression: exp.Boolean) -> str:
2673        return "TRUE" if expression.this else "FALSE"
2674
2675    def booland_sql(self, expression: exp.Booland) -> str:
2676        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
2677
2678    def boolor_sql(self, expression: exp.Boolor) -> str:
2679        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
2680
2681    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2682        this = self.sql(expression, "this")
2683        this = f"{this} " if this else this
2684        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2685        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
2686
2687    def withfill_sql(self, expression: exp.WithFill) -> str:
2688        from_sql = self.sql(expression, "from_")
2689        from_sql = f" FROM {from_sql}" if from_sql else ""
2690        to_sql = self.sql(expression, "to")
2691        to_sql = f" TO {to_sql}" if to_sql else ""
2692        step_sql = self.sql(expression, "step")
2693        step_sql = f" STEP {step_sql}" if step_sql else ""
2694        interpolated_values = [
2695            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2696            if isinstance(e, exp.Alias)
2697            else self.sql(e, "this")
2698            for e in expression.args.get("interpolate") or []
2699        ]
2700        interpolate = (
2701            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2702        )
2703        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2704
2705    def cluster_sql(self, expression: exp.Cluster) -> str:
2706        return self.op_expressions("CLUSTER BY", expression)
2707
2708    def distribute_sql(self, expression: exp.Distribute) -> str:
2709        return self.op_expressions("DISTRIBUTE BY", expression)
2710
2711    def sort_sql(self, expression: exp.Sort) -> str:
2712        return self.op_expressions("SORT BY", expression)
2713
2714    def ordered_sql(self, expression: exp.Ordered) -> str:
2715        desc = expression.args.get("desc")
2716        asc = not desc
2717
2718        nulls_first = expression.args.get("nulls_first")
2719        nulls_last = not nulls_first
2720        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2721        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2722        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2723
2724        this = self.sql(expression, "this")
2725
2726        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2727        nulls_sort_change = ""
2728        if nulls_first and (
2729            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2730        ):
2731            nulls_sort_change = " NULLS FIRST"
2732        elif (
2733            nulls_last
2734            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2735            and not nulls_are_last
2736        ):
2737            nulls_sort_change = " NULLS LAST"
2738
2739        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2740        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2741            window = expression.find_ancestor(exp.Window, exp.Select)
2742            if isinstance(window, exp.Window) and window.args.get("spec"):
2743                self.unsupported(
2744                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2745                )
2746                nulls_sort_change = ""
2747            elif self.NULL_ORDERING_SUPPORTED is False and (
2748                (asc and nulls_sort_change == " NULLS LAST")
2749                or (desc and nulls_sort_change == " NULLS FIRST")
2750            ):
2751                # BigQuery does not allow these ordering/nulls combinations when used under
2752                # an aggregation func or under a window containing one
2753                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2754
2755                if isinstance(ancestor, exp.Window):
2756                    ancestor = ancestor.this
2757                if isinstance(ancestor, exp.AggFunc):
2758                    self.unsupported(
2759                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2760                    )
2761                    nulls_sort_change = ""
2762            elif self.NULL_ORDERING_SUPPORTED is None:
2763                if expression.this.is_int:
2764                    self.unsupported(
2765                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2766                    )
2767                elif not isinstance(expression.this, exp.Rand):
2768                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2769                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2770                nulls_sort_change = ""
2771
2772        with_fill = self.sql(expression, "with_fill")
2773        with_fill = f" {with_fill}" if with_fill else ""
2774
2775        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2776
2777    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2778        window_frame = self.sql(expression, "window_frame")
2779        window_frame = f"{window_frame} " if window_frame else ""
2780
2781        this = self.sql(expression, "this")
2782
2783        return f"{window_frame}{this}"
2784
2785    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2786        partition = self.partition_by_sql(expression)
2787        order = self.sql(expression, "order")
2788        measures = self.expressions(expression, key="measures")
2789        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2790        rows = self.sql(expression, "rows")
2791        rows = self.seg(rows) if rows else ""
2792        after = self.sql(expression, "after")
2793        after = self.seg(after) if after else ""
2794        pattern = self.sql(expression, "pattern")
2795        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2796        definition_sqls = [
2797            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2798            for definition in expression.args.get("define", [])
2799        ]
2800        definitions = self.expressions(sqls=definition_sqls)
2801        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2802        body = "".join(
2803            (
2804                partition,
2805                order,
2806                measures,
2807                rows,
2808                after,
2809                pattern,
2810                define,
2811            )
2812        )
2813        alias = self.sql(expression, "alias")
2814        alias = f" {alias}" if alias else ""
2815        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2816
2817    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2818        limit = expression.args.get("limit")
2819
2820        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2821            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2822        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2823            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2824
2825        return csv(
2826            *sqls,
2827            *[self.sql(join) for join in expression.args.get("joins") or []],
2828            self.sql(expression, "match"),
2829            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2830            self.sql(expression, "prewhere"),
2831            self.sql(expression, "where"),
2832            self.sql(expression, "connect"),
2833            self.sql(expression, "group"),
2834            self.sql(expression, "having"),
2835            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2836            self.sql(expression, "order"),
2837            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2838            *self.after_limit_modifiers(expression),
2839            self.options_modifier(expression),
2840            self.for_modifiers(expression),
2841            sep="",
2842        )
2843
2844    def options_modifier(self, expression: exp.Expression) -> str:
2845        options = self.expressions(expression, key="options")
2846        return f" {options}" if options else ""
2847
2848    def for_modifiers(self, expression: exp.Expression) -> str:
2849        for_modifiers = self.expressions(expression, key="for_")
2850        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
2851
2852    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2853        self.unsupported("Unsupported query option.")
2854        return ""
2855
2856    def offset_limit_modifiers(
2857        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2858    ) -> t.List[str]:
2859        return [
2860            self.sql(expression, "offset") if fetch else self.sql(limit),
2861            self.sql(limit) if fetch else self.sql(expression, "offset"),
2862        ]
2863
2864    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2865        locks = self.expressions(expression, key="locks", sep=" ")
2866        locks = f" {locks}" if locks else ""
2867        return [locks, self.sql(expression, "sample")]
2868
2869    def select_sql(self, expression: exp.Select) -> str:
2870        into = expression.args.get("into")
2871        if not self.SUPPORTS_SELECT_INTO and into:
2872            into.pop()
2873
2874        hint = self.sql(expression, "hint")
2875        distinct = self.sql(expression, "distinct")
2876        distinct = f" {distinct}" if distinct else ""
2877        kind = self.sql(expression, "kind")
2878
2879        limit = expression.args.get("limit")
2880        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2881            top = self.limit_sql(limit, top=True)
2882            limit.pop()
2883        else:
2884            top = ""
2885
2886        expressions = self.expressions(expression)
2887
2888        if kind:
2889            if kind in self.SELECT_KINDS:
2890                kind = f" AS {kind}"
2891            else:
2892                if kind == "STRUCT":
2893                    expressions = self.expressions(
2894                        sqls=[
2895                            self.sql(
2896                                exp.Struct(
2897                                    expressions=[
2898                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2899                                        if isinstance(e, exp.Alias)
2900                                        else e
2901                                        for e in expression.expressions
2902                                    ]
2903                                )
2904                            )
2905                        ]
2906                    )
2907                kind = ""
2908
2909        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2910        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2911
2912        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2913        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2914        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2915        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2916        sql = self.query_modifiers(
2917            expression,
2918            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2919            self.sql(expression, "into", comment=False),
2920            self.sql(expression, "from_", comment=False),
2921        )
2922
2923        # If both the CTE and SELECT clauses have comments, generate the latter earlier
2924        if expression.args.get("with_"):
2925            sql = self.maybe_comment(sql, expression)
2926            expression.pop_comments()
2927
2928        sql = self.prepend_ctes(expression, sql)
2929
2930        if not self.SUPPORTS_SELECT_INTO and into:
2931            if into.args.get("temporary"):
2932                table_kind = " TEMPORARY"
2933            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
2934                table_kind = " UNLOGGED"
2935            else:
2936                table_kind = ""
2937            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
2938
2939        return sql
2940
2941    def schema_sql(self, expression: exp.Schema) -> str:
2942        this = self.sql(expression, "this")
2943        sql = self.schema_columns_sql(expression)
2944        return f"{this} {sql}" if this and sql else this or sql
2945
2946    def schema_columns_sql(self, expression: exp.Schema) -> str:
2947        if expression.expressions:
2948            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2949        return ""
2950
2951    def star_sql(self, expression: exp.Star) -> str:
2952        except_ = self.expressions(expression, key="except_", flat=True)
2953        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
2954        replace = self.expressions(expression, key="replace", flat=True)
2955        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
2956        rename = self.expressions(expression, key="rename", flat=True)
2957        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
2958        return f"*{except_}{replace}{rename}"
2959
2960    def parameter_sql(self, expression: exp.Parameter) -> str:
2961        this = self.sql(expression, "this")
2962        return f"{self.PARAMETER_TOKEN}{this}"
2963
2964    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2965        this = self.sql(expression, "this")
2966        kind = expression.text("kind")
2967        if kind:
2968            kind = f"{kind}."
2969        return f"@@{kind}{this}"
2970
2971    def placeholder_sql(self, expression: exp.Placeholder) -> str:
2972        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
2973
2974    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2975        alias = self.sql(expression, "alias")
2976        alias = f"{sep}{alias}" if alias else ""
2977        sample = self.sql(expression, "sample")
2978        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
2979            alias = f"{sample}{alias}"
2980
2981            # Set to None so it's not generated again by self.query_modifiers()
2982            expression.set("sample", None)
2983
2984        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2985        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2986        return self.prepend_ctes(expression, sql)
2987
2988    def qualify_sql(self, expression: exp.Qualify) -> str:
2989        this = self.indent(self.sql(expression, "this"))
2990        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
2991
2992    def unnest_sql(self, expression: exp.Unnest) -> str:
2993        args = self.expressions(expression, flat=True)
2994
2995        alias = expression.args.get("alias")
2996        offset = expression.args.get("offset")
2997
2998        if self.UNNEST_WITH_ORDINALITY:
2999            if alias and isinstance(offset, exp.Expression):
3000                alias.append("columns", offset)
3001
3002        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3003            columns = alias.columns
3004            alias = self.sql(columns[0]) if columns else ""
3005        else:
3006            alias = self.sql(alias)
3007
3008        alias = f" AS {alias}" if alias else alias
3009        if self.UNNEST_WITH_ORDINALITY:
3010            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3011        else:
3012            if isinstance(offset, exp.Expression):
3013                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3014            elif offset:
3015                suffix = f"{alias} WITH OFFSET"
3016            else:
3017                suffix = alias
3018
3019        return f"UNNEST({args}){suffix}"
3020
3021    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3022        return ""
3023
3024    def where_sql(self, expression: exp.Where) -> str:
3025        this = self.indent(self.sql(expression, "this"))
3026        return f"{self.seg('WHERE')}{self.sep()}{this}"
3027
3028    def window_sql(self, expression: exp.Window) -> str:
3029        this = self.sql(expression, "this")
3030        partition = self.partition_by_sql(expression)
3031        order = expression.args.get("order")
3032        order = self.order_sql(order, flat=True) if order else ""
3033        spec = self.sql(expression, "spec")
3034        alias = self.sql(expression, "alias")
3035        over = self.sql(expression, "over") or "OVER"
3036
3037        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3038
3039        first = expression.args.get("first")
3040        if first is None:
3041            first = ""
3042        else:
3043            first = "FIRST" if first else "LAST"
3044
3045        if not partition and not order and not spec and alias:
3046            return f"{this} {alias}"
3047
3048        args = self.format_args(
3049            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3050        )
3051        return f"{this} ({args})"
3052
3053    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3054        partition = self.expressions(expression, key="partition_by", flat=True)
3055        return f"PARTITION BY {partition}" if partition else ""
3056
3057    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3058        kind = self.sql(expression, "kind")
3059        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3060        end = (
3061            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3062            or "CURRENT ROW"
3063        )
3064
3065        window_spec = f"{kind} BETWEEN {start} AND {end}"
3066
3067        exclude = self.sql(expression, "exclude")
3068        if exclude:
3069            if self.SUPPORTS_WINDOW_EXCLUDE:
3070                window_spec += f" EXCLUDE {exclude}"
3071            else:
3072                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3073
3074        return window_spec
3075
3076    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3077        this = self.sql(expression, "this")
3078        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3079        return f"{this} WITHIN GROUP ({expression_sql})"
3080
3081    def between_sql(self, expression: exp.Between) -> str:
3082        this = self.sql(expression, "this")
3083        low = self.sql(expression, "low")
3084        high = self.sql(expression, "high")
3085        symmetric = expression.args.get("symmetric")
3086
3087        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3088            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3089
3090        flag = (
3091            " SYMMETRIC"
3092            if symmetric
3093            else " ASYMMETRIC"
3094            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3095            else ""  # silently drop ASYMMETRIC – semantics identical
3096        )
3097        return f"{this} BETWEEN{flag} {low} AND {high}"
3098
3099    def bracket_offset_expressions(
3100        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
3101    ) -> t.List[exp.Expression]:
3102        return apply_index_offset(
3103            expression.this,
3104            expression.expressions,
3105            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3106            dialect=self.dialect,
3107        )
3108
3109    def bracket_sql(self, expression: exp.Bracket) -> str:
3110        expressions = self.bracket_offset_expressions(expression)
3111        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3112        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3113
3114    def all_sql(self, expression: exp.All) -> str:
3115        this = self.sql(expression, "this")
3116        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3117            this = self.wrap(this)
3118        return f"ALL {this}"
3119
3120    def any_sql(self, expression: exp.Any) -> str:
3121        this = self.sql(expression, "this")
3122        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3123            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3124                this = self.wrap(this)
3125            return f"ANY{this}"
3126        return f"ANY {this}"
3127
3128    def exists_sql(self, expression: exp.Exists) -> str:
3129        return f"EXISTS{self.wrap(expression)}"
3130
3131    def case_sql(self, expression: exp.Case) -> str:
3132        this = self.sql(expression, "this")
3133        statements = [f"CASE {this}" if this else "CASE"]
3134
3135        for e in expression.args["ifs"]:
3136            statements.append(f"WHEN {self.sql(e, 'this')}")
3137            statements.append(f"THEN {self.sql(e, 'true')}")
3138
3139        default = self.sql(expression, "default")
3140
3141        if default:
3142            statements.append(f"ELSE {default}")
3143
3144        statements.append("END")
3145
3146        if self.pretty and self.too_wide(statements):
3147            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3148
3149        return " ".join(statements)
3150
3151    def constraint_sql(self, expression: exp.Constraint) -> str:
3152        this = self.sql(expression, "this")
3153        expressions = self.expressions(expression, flat=True)
3154        return f"CONSTRAINT {this} {expressions}"
3155
3156    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3157        order = expression.args.get("order")
3158        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3159        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3160
3161    def extract_sql(self, expression: exp.Extract) -> str:
3162        from sqlglot.dialects.dialect import map_date_part
3163
3164        this = (
3165            map_date_part(expression.this, self.dialect)
3166            if self.NORMALIZE_EXTRACT_DATE_PARTS
3167            else expression.this
3168        )
3169        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3170        expression_sql = self.sql(expression, "expression")
3171
3172        return f"EXTRACT({this_sql} FROM {expression_sql})"
3173
3174    def trim_sql(self, expression: exp.Trim) -> str:
3175        trim_type = self.sql(expression, "position")
3176
3177        if trim_type == "LEADING":
3178            func_name = "LTRIM"
3179        elif trim_type == "TRAILING":
3180            func_name = "RTRIM"
3181        else:
3182            func_name = "TRIM"
3183
3184        return self.func(func_name, expression.this, expression.expression)
3185
3186    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
3187        args = expression.expressions
3188        if isinstance(expression, exp.ConcatWs):
3189            args = args[1:]  # Skip the delimiter
3190
3191        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3192            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
3193
3194        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
3195
3196            def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression:
3197                if not e.type:
3198                    from sqlglot.optimizer.annotate_types import annotate_types
3199
3200                    e = annotate_types(e, dialect=self.dialect)
3201
3202                if e.is_string or e.is_type(exp.DataType.Type.ARRAY):
3203                    return e
3204
3205                return exp.func("coalesce", e, exp.Literal.string(""))
3206
3207            args = [_wrap_with_coalesce(e) for e in args]
3208
3209        return args
3210
3211    def concat_sql(self, expression: exp.Concat) -> str:
3212        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3213            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3214            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3215            # instead of coalescing them to empty string.
3216            from sqlglot.dialects.dialect import concat_to_dpipe_sql
3217
3218            return concat_to_dpipe_sql(self, expression)
3219
3220        expressions = self.convert_concat_args(expression)
3221
3222        # Some dialects don't allow a single-argument CONCAT call
3223        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3224            return self.sql(expressions[0])
3225
3226        return self.func("CONCAT", *expressions)
3227
3228    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3229        return self.func(
3230            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3231        )
3232
3233    def check_sql(self, expression: exp.Check) -> str:
3234        this = self.sql(expression, key="this")
3235        return f"CHECK ({this})"
3236
3237    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3238        expressions = self.expressions(expression, flat=True)
3239        expressions = f" ({expressions})" if expressions else ""
3240        reference = self.sql(expression, "reference")
3241        reference = f" {reference}" if reference else ""
3242        delete = self.sql(expression, "delete")
3243        delete = f" ON DELETE {delete}" if delete else ""
3244        update = self.sql(expression, "update")
3245        update = f" ON UPDATE {update}" if update else ""
3246        options = self.expressions(expression, key="options", flat=True, sep=" ")
3247        options = f" {options}" if options else ""
3248        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3249
3250    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3251        this = self.sql(expression, "this")
3252        this = f" {this}" if this else ""
3253        expressions = self.expressions(expression, flat=True)
3254        include = self.sql(expression, "include")
3255        options = self.expressions(expression, key="options", flat=True, sep=" ")
3256        options = f" {options}" if options else ""
3257        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3258
3259    def if_sql(self, expression: exp.If) -> str:
3260        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3261
3262    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3263        if self.MATCH_AGAINST_TABLE_PREFIX:
3264            expressions = []
3265            for expr in expression.expressions:
3266                if isinstance(expr, exp.Table):
3267                    expressions.append(f"TABLE {self.sql(expr)}")
3268                else:
3269                    expressions.append(expr)
3270        else:
3271            expressions = expression.expressions
3272
3273        modifier = expression.args.get("modifier")
3274        modifier = f" {modifier}" if modifier else ""
3275        return (
3276            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3277        )
3278
3279    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3280        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3281
3282    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3283        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3284
3285        if expression.args.get("escape"):
3286            path = self.escape_str(path)
3287
3288        if self.QUOTE_JSON_PATH:
3289            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3290
3291        return path
3292
3293    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3294        if isinstance(expression, exp.JSONPathPart):
3295            transform = self.TRANSFORMS.get(expression.__class__)
3296            if not callable(transform):
3297                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3298                return ""
3299
3300            return transform(self, expression)
3301
3302        if isinstance(expression, int):
3303            return str(expression)
3304
3305        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3306            escaped = expression.replace("'", "\\'")
3307            escaped = f"\\'{expression}\\'"
3308        else:
3309            escaped = expression.replace('"', '\\"')
3310            escaped = f'"{escaped}"'
3311
3312        return escaped
3313
3314    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3315        return f"{self.sql(expression, 'this')} FORMAT JSON"
3316
3317    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3318        # Output the Teradata column FORMAT override.
3319        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3320        this = self.sql(expression, "this")
3321        fmt = self.sql(expression, "format")
3322        return f"{this} (FORMAT {fmt})"
3323
3324    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3325        null_handling = expression.args.get("null_handling")
3326        null_handling = f" {null_handling}" if null_handling else ""
3327
3328        unique_keys = expression.args.get("unique_keys")
3329        if unique_keys is not None:
3330            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3331        else:
3332            unique_keys = ""
3333
3334        return_type = self.sql(expression, "return_type")
3335        return_type = f" RETURNING {return_type}" if return_type else ""
3336        encoding = self.sql(expression, "encoding")
3337        encoding = f" ENCODING {encoding}" if encoding else ""
3338
3339        return self.func(
3340            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3341            *expression.expressions,
3342            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3343        )
3344
3345    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3346        return self.jsonobject_sql(expression)
3347
3348    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3349        null_handling = expression.args.get("null_handling")
3350        null_handling = f" {null_handling}" if null_handling else ""
3351        return_type = self.sql(expression, "return_type")
3352        return_type = f" RETURNING {return_type}" if return_type else ""
3353        strict = " STRICT" if expression.args.get("strict") else ""
3354        return self.func(
3355            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3356        )
3357
3358    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3359        this = self.sql(expression, "this")
3360        order = self.sql(expression, "order")
3361        null_handling = expression.args.get("null_handling")
3362        null_handling = f" {null_handling}" if null_handling else ""
3363        return_type = self.sql(expression, "return_type")
3364        return_type = f" RETURNING {return_type}" if return_type else ""
3365        strict = " STRICT" if expression.args.get("strict") else ""
3366        return self.func(
3367            "JSON_ARRAYAGG",
3368            this,
3369            suffix=f"{order}{null_handling}{return_type}{strict})",
3370        )
3371
3372    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3373        path = self.sql(expression, "path")
3374        path = f" PATH {path}" if path else ""
3375        nested_schema = self.sql(expression, "nested_schema")
3376
3377        if nested_schema:
3378            return f"NESTED{path} {nested_schema}"
3379
3380        this = self.sql(expression, "this")
3381        kind = self.sql(expression, "kind")
3382        kind = f" {kind}" if kind else ""
3383
3384        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3385        return f"{this}{kind}{path}{ordinality}"
3386
3387    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3388        return self.func("COLUMNS", *expression.expressions)
3389
3390    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3391        this = self.sql(expression, "this")
3392        path = self.sql(expression, "path")
3393        path = f", {path}" if path else ""
3394        error_handling = expression.args.get("error_handling")
3395        error_handling = f" {error_handling}" if error_handling else ""
3396        empty_handling = expression.args.get("empty_handling")
3397        empty_handling = f" {empty_handling}" if empty_handling else ""
3398        schema = self.sql(expression, "schema")
3399        return self.func(
3400            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3401        )
3402
3403    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3404        this = self.sql(expression, "this")
3405        kind = self.sql(expression, "kind")
3406        path = self.sql(expression, "path")
3407        path = f" {path}" if path else ""
3408        as_json = " AS JSON" if expression.args.get("as_json") else ""
3409        return f"{this} {kind}{path}{as_json}"
3410
3411    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3412        this = self.sql(expression, "this")
3413        path = self.sql(expression, "path")
3414        path = f", {path}" if path else ""
3415        expressions = self.expressions(expression)
3416        with_ = (
3417            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3418            if expressions
3419            else ""
3420        )
3421        return f"OPENJSON({this}{path}){with_}"
3422
3423    def in_sql(self, expression: exp.In) -> str:
3424        query = expression.args.get("query")
3425        unnest = expression.args.get("unnest")
3426        field = expression.args.get("field")
3427        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3428
3429        if query:
3430            in_sql = self.sql(query)
3431        elif unnest:
3432            in_sql = self.in_unnest_op(unnest)
3433        elif field:
3434            in_sql = self.sql(field)
3435        else:
3436            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3437
3438        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3439
3440    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3441        return f"(SELECT {self.sql(unnest)})"
3442
3443    def interval_sql(self, expression: exp.Interval) -> str:
3444        unit_expression = expression.args.get("unit")
3445        unit = self.sql(unit_expression) if unit_expression else ""
3446        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3447            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3448        unit = f" {unit}" if unit else ""
3449
3450        if self.SINGLE_STRING_INTERVAL:
3451            this = expression.this.name if expression.this else ""
3452            if this:
3453                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3454                    return f"INTERVAL '{this}'{unit}"
3455                return f"INTERVAL '{this}{unit}'"
3456            return f"INTERVAL{unit}"
3457
3458        this = self.sql(expression, "this")
3459        if this:
3460            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3461            this = f" {this}" if unwrapped else f" ({this})"
3462
3463        return f"INTERVAL{this}{unit}"
3464
3465    def return_sql(self, expression: exp.Return) -> str:
3466        return f"RETURN {self.sql(expression, 'this')}"
3467
3468    def reference_sql(self, expression: exp.Reference) -> str:
3469        this = self.sql(expression, "this")
3470        expressions = self.expressions(expression, flat=True)
3471        expressions = f"({expressions})" if expressions else ""
3472        options = self.expressions(expression, key="options", flat=True, sep=" ")
3473        options = f" {options}" if options else ""
3474        return f"REFERENCES {this}{expressions}{options}"
3475
3476    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3477        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3478        parent = expression.parent
3479        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3480        return self.func(
3481            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3482        )
3483
3484    def paren_sql(self, expression: exp.Paren) -> str:
3485        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3486        return f"({sql}{self.seg(')', sep='')}"
3487
3488    def neg_sql(self, expression: exp.Neg) -> str:
3489        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3490        this_sql = self.sql(expression, "this")
3491        sep = " " if this_sql[0] == "-" else ""
3492        return f"-{sep}{this_sql}"
3493
3494    def not_sql(self, expression: exp.Not) -> str:
3495        return f"NOT {self.sql(expression, 'this')}"
3496
3497    def alias_sql(self, expression: exp.Alias) -> str:
3498        alias = self.sql(expression, "alias")
3499        alias = f" AS {alias}" if alias else ""
3500        return f"{self.sql(expression, 'this')}{alias}"
3501
3502    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3503        alias = expression.args["alias"]
3504
3505        parent = expression.parent
3506        pivot = parent and parent.parent
3507
3508        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3509            identifier_alias = isinstance(alias, exp.Identifier)
3510            literal_alias = isinstance(alias, exp.Literal)
3511
3512            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3513                alias.replace(exp.Literal.string(alias.output_name))
3514            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3515                alias.replace(exp.to_identifier(alias.output_name))
3516
3517        return self.alias_sql(expression)
3518
3519    def aliases_sql(self, expression: exp.Aliases) -> str:
3520        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3521
3522    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3523        this = self.sql(expression, "this")
3524        index = self.sql(expression, "expression")
3525        return f"{this} AT {index}"
3526
3527    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3528        this = self.sql(expression, "this")
3529        zone = self.sql(expression, "zone")
3530        return f"{this} AT TIME ZONE {zone}"
3531
3532    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3533        this = self.sql(expression, "this")
3534        zone = self.sql(expression, "zone")
3535        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3536
3537    def add_sql(self, expression: exp.Add) -> str:
3538        return self.binary(expression, "+")
3539
3540    def and_sql(
3541        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3542    ) -> str:
3543        return self.connector_sql(expression, "AND", stack)
3544
3545    def or_sql(
3546        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3547    ) -> str:
3548        return self.connector_sql(expression, "OR", stack)
3549
3550    def xor_sql(
3551        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3552    ) -> str:
3553        return self.connector_sql(expression, "XOR", stack)
3554
3555    def connector_sql(
3556        self,
3557        expression: exp.Connector,
3558        op: str,
3559        stack: t.Optional[t.List[str | exp.Expression]] = None,
3560    ) -> str:
3561        if stack is not None:
3562            if expression.expressions:
3563                stack.append(self.expressions(expression, sep=f" {op} "))
3564            else:
3565                stack.append(expression.right)
3566                if expression.comments and self.comments:
3567                    for comment in expression.comments:
3568                        if comment:
3569                            op += f" /*{self.sanitize_comment(comment)}*/"
3570                stack.extend((op, expression.left))
3571            return op
3572
3573        stack = [expression]
3574        sqls: t.List[str] = []
3575        ops = set()
3576
3577        while stack:
3578            node = stack.pop()
3579            if isinstance(node, exp.Connector):
3580                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3581            else:
3582                sql = self.sql(node)
3583                if sqls and sqls[-1] in ops:
3584                    sqls[-1] += f" {sql}"
3585                else:
3586                    sqls.append(sql)
3587
3588        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3589        return sep.join(sqls)
3590
3591    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3592        return self.binary(expression, "&")
3593
3594    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3595        return self.binary(expression, "<<")
3596
3597    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3598        return f"~{self.sql(expression, 'this')}"
3599
3600    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3601        return self.binary(expression, "|")
3602
3603    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3604        return self.binary(expression, ">>")
3605
3606    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3607        return self.binary(expression, "^")
3608
3609    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3610        format_sql = self.sql(expression, "format")
3611        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3612        to_sql = self.sql(expression, "to")
3613        to_sql = f" {to_sql}" if to_sql else ""
3614        action = self.sql(expression, "action")
3615        action = f" {action}" if action else ""
3616        default = self.sql(expression, "default")
3617        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3618        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3619
3620    # Base implementation that excludes safe, zone, and target_type metadata args
3621    def strtotime_sql(self, expression: exp.StrToTime) -> str:
3622        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
3623
3624    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3625        zone = self.sql(expression, "this")
3626        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3627
3628    def collate_sql(self, expression: exp.Collate) -> str:
3629        if self.COLLATE_IS_FUNC:
3630            return self.function_fallback_sql(expression)
3631        return self.binary(expression, "COLLATE")
3632
3633    def command_sql(self, expression: exp.Command) -> str:
3634        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3635
3636    def comment_sql(self, expression: exp.Comment) -> str:
3637        this = self.sql(expression, "this")
3638        kind = expression.args["kind"]
3639        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3640        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3641        expression_sql = self.sql(expression, "expression")
3642        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3643
3644    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3645        this = self.sql(expression, "this")
3646        delete = " DELETE" if expression.args.get("delete") else ""
3647        recompress = self.sql(expression, "recompress")
3648        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3649        to_disk = self.sql(expression, "to_disk")
3650        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3651        to_volume = self.sql(expression, "to_volume")
3652        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3653        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3654
3655    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3656        where = self.sql(expression, "where")
3657        group = self.sql(expression, "group")
3658        aggregates = self.expressions(expression, key="aggregates")
3659        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3660
3661        if not (where or group or aggregates) and len(expression.expressions) == 1:
3662            return f"TTL {self.expressions(expression, flat=True)}"
3663
3664        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3665
3666    def transaction_sql(self, expression: exp.Transaction) -> str:
3667        modes = self.expressions(expression, key="modes")
3668        modes = f" {modes}" if modes else ""
3669        return f"BEGIN{modes}"
3670
3671    def commit_sql(self, expression: exp.Commit) -> str:
3672        chain = expression.args.get("chain")
3673        if chain is not None:
3674            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3675
3676        return f"COMMIT{chain or ''}"
3677
3678    def rollback_sql(self, expression: exp.Rollback) -> str:
3679        savepoint = expression.args.get("savepoint")
3680        savepoint = f" TO {savepoint}" if savepoint else ""
3681        return f"ROLLBACK{savepoint}"
3682
3683    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3684        this = self.sql(expression, "this")
3685
3686        dtype = self.sql(expression, "dtype")
3687        if dtype:
3688            collate = self.sql(expression, "collate")
3689            collate = f" COLLATE {collate}" if collate else ""
3690            using = self.sql(expression, "using")
3691            using = f" USING {using}" if using else ""
3692            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3693            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3694
3695        default = self.sql(expression, "default")
3696        if default:
3697            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3698
3699        comment = self.sql(expression, "comment")
3700        if comment:
3701            return f"ALTER COLUMN {this} COMMENT {comment}"
3702
3703        visible = expression.args.get("visible")
3704        if visible:
3705            return f"ALTER COLUMN {this} SET {visible}"
3706
3707        allow_null = expression.args.get("allow_null")
3708        drop = expression.args.get("drop")
3709
3710        if not drop and not allow_null:
3711            self.unsupported("Unsupported ALTER COLUMN syntax")
3712
3713        if allow_null is not None:
3714            keyword = "DROP" if drop else "SET"
3715            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3716
3717        return f"ALTER COLUMN {this} DROP DEFAULT"
3718
3719    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3720        this = self.sql(expression, "this")
3721
3722        visible = expression.args.get("visible")
3723        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3724
3725        return f"ALTER INDEX {this} {visible_sql}"
3726
3727    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3728        this = self.sql(expression, "this")
3729        if not isinstance(expression.this, exp.Var):
3730            this = f"KEY DISTKEY {this}"
3731        return f"ALTER DISTSTYLE {this}"
3732
3733    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3734        compound = " COMPOUND" if expression.args.get("compound") else ""
3735        this = self.sql(expression, "this")
3736        expressions = self.expressions(expression, flat=True)
3737        expressions = f"({expressions})" if expressions else ""
3738        return f"ALTER{compound} SORTKEY {this or expressions}"
3739
3740    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
3741        if not self.RENAME_TABLE_WITH_DB:
3742            # Remove db from tables
3743            expression = expression.transform(
3744                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3745            ).assert_is(exp.AlterRename)
3746        this = self.sql(expression, "this")
3747        to_kw = " TO" if include_to else ""
3748        return f"RENAME{to_kw} {this}"
3749
3750    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3751        exists = " IF EXISTS" if expression.args.get("exists") else ""
3752        old_column = self.sql(expression, "this")
3753        new_column = self.sql(expression, "to")
3754        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3755
3756    def alterset_sql(self, expression: exp.AlterSet) -> str:
3757        exprs = self.expressions(expression, flat=True)
3758        if self.ALTER_SET_WRAPPED:
3759            exprs = f"({exprs})"
3760
3761        return f"SET {exprs}"
3762
3763    def alter_sql(self, expression: exp.Alter) -> str:
3764        actions = expression.args["actions"]
3765
3766        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3767            actions[0], exp.ColumnDef
3768        ):
3769            actions_sql = self.expressions(expression, key="actions", flat=True)
3770            actions_sql = f"ADD {actions_sql}"
3771        else:
3772            actions_list = []
3773            for action in actions:
3774                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3775                    action_sql = self.add_column_sql(action)
3776                else:
3777                    action_sql = self.sql(action)
3778                    if isinstance(action, exp.Query):
3779                        action_sql = f"AS {action_sql}"
3780
3781                actions_list.append(action_sql)
3782
3783            actions_sql = self.format_args(*actions_list).lstrip("\n")
3784
3785        exists = " IF EXISTS" if expression.args.get("exists") else ""
3786        on_cluster = self.sql(expression, "cluster")
3787        on_cluster = f" {on_cluster}" if on_cluster else ""
3788        only = " ONLY" if expression.args.get("only") else ""
3789        options = self.expressions(expression, key="options")
3790        options = f", {options}" if options else ""
3791        kind = self.sql(expression, "kind")
3792        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3793        check = " WITH CHECK" if expression.args.get("check") else ""
3794        cascade = (
3795            " CASCADE"
3796            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
3797            else ""
3798        )
3799        this = self.sql(expression, "this")
3800        this = f" {this}" if this else ""
3801
3802        return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
3803
3804    def altersession_sql(self, expression: exp.AlterSession) -> str:
3805        items_sql = self.expressions(expression, flat=True)
3806        keyword = "UNSET" if expression.args.get("unset") else "SET"
3807        return f"{keyword} {items_sql}"
3808
3809    def add_column_sql(self, expression: exp.Expression) -> str:
3810        sql = self.sql(expression)
3811        if isinstance(expression, exp.Schema):
3812            column_text = " COLUMNS"
3813        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3814            column_text = " COLUMN"
3815        else:
3816            column_text = ""
3817
3818        return f"ADD{column_text} {sql}"
3819
3820    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3821        expressions = self.expressions(expression)
3822        exists = " IF EXISTS " if expression.args.get("exists") else " "
3823        return f"DROP{exists}{expressions}"
3824
3825    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3826        return f"ADD {self.expressions(expression, indent=False)}"
3827
3828    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3829        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3830        location = self.sql(expression, "location")
3831        location = f" {location}" if location else ""
3832        return f"ADD {exists}{self.sql(expression.this)}{location}"
3833
3834    def distinct_sql(self, expression: exp.Distinct) -> str:
3835        this = self.expressions(expression, flat=True)
3836
3837        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3838            case = exp.case()
3839            for arg in expression.expressions:
3840                case = case.when(arg.is_(exp.null()), exp.null())
3841            this = self.sql(case.else_(f"({this})"))
3842
3843        this = f" {this}" if this else ""
3844
3845        on = self.sql(expression, "on")
3846        on = f" ON {on}" if on else ""
3847        return f"DISTINCT{this}{on}"
3848
3849    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3850        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
3851
3852    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3853        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
3854
3855    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3856        this_sql = self.sql(expression, "this")
3857        expression_sql = self.sql(expression, "expression")
3858        kind = "MAX" if expression.args.get("max") else "MIN"
3859        return f"{this_sql} HAVING {kind} {expression_sql}"
3860
3861    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3862        return self.sql(
3863            exp.Cast(
3864                this=exp.Div(this=expression.this, expression=expression.expression),
3865                to=exp.DataType(this=exp.DataType.Type.INT),
3866            )
3867        )
3868
3869    def dpipe_sql(self, expression: exp.DPipe) -> str:
3870        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3871            return self.func(
3872                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3873            )
3874        return self.binary(expression, "||")
3875
3876    def div_sql(self, expression: exp.Div) -> str:
3877        l, r = expression.left, expression.right
3878
3879        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3880            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3881
3882        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3883            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3884                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3885
3886        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3887            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3888                return self.sql(
3889                    exp.cast(
3890                        l / r,
3891                        to=exp.DataType.Type.BIGINT,
3892                    )
3893                )
3894
3895        return self.binary(expression, "/")
3896
3897    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3898        n = exp._wrap(expression.this, exp.Binary)
3899        d = exp._wrap(expression.expression, exp.Binary)
3900        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
3901
3902    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3903        return self.binary(expression, "OVERLAPS")
3904
3905    def distance_sql(self, expression: exp.Distance) -> str:
3906        return self.binary(expression, "<->")
3907
3908    def dot_sql(self, expression: exp.Dot) -> str:
3909        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
3910
3911    def eq_sql(self, expression: exp.EQ) -> str:
3912        return self.binary(expression, "=")
3913
3914    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3915        return self.binary(expression, ":=")
3916
3917    def escape_sql(self, expression: exp.Escape) -> str:
3918        return self.binary(expression, "ESCAPE")
3919
3920    def glob_sql(self, expression: exp.Glob) -> str:
3921        return self.binary(expression, "GLOB")
3922
3923    def gt_sql(self, expression: exp.GT) -> str:
3924        return self.binary(expression, ">")
3925
3926    def gte_sql(self, expression: exp.GTE) -> str:
3927        return self.binary(expression, ">=")
3928
3929    def is_sql(self, expression: exp.Is) -> str:
3930        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
3931            return self.sql(
3932                expression.this if expression.expression.this else exp.not_(expression.this)
3933            )
3934        return self.binary(expression, "IS")
3935
3936    def _like_sql(self, expression: exp.Like | exp.ILike) -> str:
3937        this = expression.this
3938        rhs = expression.expression
3939
3940        if isinstance(expression, exp.Like):
3941            exp_class: t.Type[exp.Like | exp.ILike] = exp.Like
3942            op = "LIKE"
3943        else:
3944            exp_class = exp.ILike
3945            op = "ILIKE"
3946
3947        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
3948            exprs = rhs.this.unnest()
3949
3950            if isinstance(exprs, exp.Tuple):
3951                exprs = exprs.expressions
3952
3953            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
3954
3955            like_expr: exp.Expression = exp_class(this=this, expression=exprs[0])
3956            for expr in exprs[1:]:
3957                like_expr = connective(like_expr, exp_class(this=this, expression=expr))
3958
3959            parent = expression.parent
3960            if not isinstance(parent, type(like_expr)) and isinstance(parent, exp.Condition):
3961                like_expr = exp.paren(like_expr, copy=False)
3962
3963            return self.sql(like_expr)
3964
3965        return self.binary(expression, op)
3966
3967    def like_sql(self, expression: exp.Like) -> str:
3968        return self._like_sql(expression)
3969
3970    def ilike_sql(self, expression: exp.ILike) -> str:
3971        return self._like_sql(expression)
3972
3973    def match_sql(self, expression: exp.Match) -> str:
3974        return self.binary(expression, "MATCH")
3975
3976    def similarto_sql(self, expression: exp.SimilarTo) -> str:
3977        return self.binary(expression, "SIMILAR TO")
3978
3979    def lt_sql(self, expression: exp.LT) -> str:
3980        return self.binary(expression, "<")
3981
3982    def lte_sql(self, expression: exp.LTE) -> str:
3983        return self.binary(expression, "<=")
3984
3985    def mod_sql(self, expression: exp.Mod) -> str:
3986        return self.binary(expression, "%")
3987
3988    def mul_sql(self, expression: exp.Mul) -> str:
3989        return self.binary(expression, "*")
3990
3991    def neq_sql(self, expression: exp.NEQ) -> str:
3992        return self.binary(expression, "<>")
3993
3994    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
3995        return self.binary(expression, "IS NOT DISTINCT FROM")
3996
3997    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
3998        return self.binary(expression, "IS DISTINCT FROM")
3999
4000    def sub_sql(self, expression: exp.Sub) -> str:
4001        return self.binary(expression, "-")
4002
4003    def trycast_sql(self, expression: exp.TryCast) -> str:
4004        return self.cast_sql(expression, safe_prefix="TRY_")
4005
4006    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4007        return self.cast_sql(expression)
4008
4009    def try_sql(self, expression: exp.Try) -> str:
4010        if not self.TRY_SUPPORTED:
4011            self.unsupported("Unsupported TRY function")
4012            return self.sql(expression, "this")
4013
4014        return self.func("TRY", expression.this)
4015
4016    def log_sql(self, expression: exp.Log) -> str:
4017        this = expression.this
4018        expr = expression.expression
4019
4020        if self.dialect.LOG_BASE_FIRST is False:
4021            this, expr = expr, this
4022        elif self.dialect.LOG_BASE_FIRST is None and expr:
4023            if this.name in ("2", "10"):
4024                return self.func(f"LOG{this.name}", expr)
4025
4026            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4027
4028        return self.func("LOG", this, expr)
4029
4030    def use_sql(self, expression: exp.Use) -> str:
4031        kind = self.sql(expression, "kind")
4032        kind = f" {kind}" if kind else ""
4033        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4034        this = f" {this}" if this else ""
4035        return f"USE{kind}{this}"
4036
4037    def binary(self, expression: exp.Binary, op: str) -> str:
4038        sqls: t.List[str] = []
4039        stack: t.List[t.Union[str, exp.Expression]] = [expression]
4040        binary_type = type(expression)
4041
4042        while stack:
4043            node = stack.pop()
4044
4045            if type(node) is binary_type:
4046                op_func = node.args.get("operator")
4047                if op_func:
4048                    op = f"OPERATOR({self.sql(op_func)})"
4049
4050                stack.append(node.right)
4051                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4052                stack.append(node.left)
4053            else:
4054                sqls.append(self.sql(node))
4055
4056        return "".join(sqls)
4057
4058    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4059        to_clause = self.sql(expression, "to")
4060        if to_clause:
4061            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4062
4063        return self.function_fallback_sql(expression)
4064
4065    def function_fallback_sql(self, expression: exp.Func) -> str:
4066        args = []
4067
4068        for key in expression.arg_types:
4069            arg_value = expression.args.get(key)
4070
4071            if isinstance(arg_value, list):
4072                for value in arg_value:
4073                    args.append(value)
4074            elif arg_value is not None:
4075                args.append(arg_value)
4076
4077        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4078            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
4079        else:
4080            name = expression.sql_name()
4081
4082        return self.func(name, *args)
4083
4084    def func(
4085        self,
4086        name: str,
4087        *args: t.Optional[exp.Expression | str],
4088        prefix: str = "(",
4089        suffix: str = ")",
4090        normalize: bool = True,
4091    ) -> str:
4092        name = self.normalize_func(name) if normalize else name
4093        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4094
4095    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
4096        arg_sqls = tuple(
4097            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4098        )
4099        if self.pretty and self.too_wide(arg_sqls):
4100            return self.indent(
4101                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4102            )
4103        return sep.join(arg_sqls)
4104
4105    def too_wide(self, args: t.Iterable) -> bool:
4106        return sum(len(arg) for arg in args) > self.max_text_width
4107
4108    def format_time(
4109        self,
4110        expression: exp.Expression,
4111        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
4112        inverse_time_trie: t.Optional[t.Dict] = None,
4113    ) -> t.Optional[str]:
4114        return format_time(
4115            self.sql(expression, "format"),
4116            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4117            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4118        )
4119
4120    def expressions(
4121        self,
4122        expression: t.Optional[exp.Expression] = None,
4123        key: t.Optional[str] = None,
4124        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
4125        flat: bool = False,
4126        indent: bool = True,
4127        skip_first: bool = False,
4128        skip_last: bool = False,
4129        sep: str = ", ",
4130        prefix: str = "",
4131        dynamic: bool = False,
4132        new_line: bool = False,
4133    ) -> str:
4134        expressions = expression.args.get(key or "expressions") if expression else sqls
4135
4136        if not expressions:
4137            return ""
4138
4139        if flat:
4140            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4141
4142        num_sqls = len(expressions)
4143        result_sqls = []
4144
4145        for i, e in enumerate(expressions):
4146            sql = self.sql(e, comment=False)
4147            if not sql:
4148                continue
4149
4150            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
4151
4152            if self.pretty:
4153                if self.leading_comma:
4154                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4155                else:
4156                    result_sqls.append(
4157                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4158                    )
4159            else:
4160                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4161
4162        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4163            if new_line:
4164                result_sqls.insert(0, "")
4165                result_sqls.append("")
4166            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4167        else:
4168            result_sql = "".join(result_sqls)
4169
4170        return (
4171            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4172            if indent
4173            else result_sql
4174        )
4175
4176    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
4177        flat = flat or isinstance(expression.parent, exp.Properties)
4178        expressions_sql = self.expressions(expression, flat=flat)
4179        if flat:
4180            return f"{op} {expressions_sql}"
4181        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4182
4183    def naked_property(self, expression: exp.Property) -> str:
4184        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4185        if not property_name:
4186            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4187        return f"{property_name} {self.sql(expression, 'this')}"
4188
4189    def tag_sql(self, expression: exp.Tag) -> str:
4190        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4191
4192    def token_sql(self, token_type: TokenType) -> str:
4193        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4194
4195    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4196        this = self.sql(expression, "this")
4197        expressions = self.no_identify(self.expressions, expression)
4198        expressions = (
4199            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4200        )
4201        return f"{this}{expressions}" if expressions.strip() != "" else this
4202
4203    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4204        this = self.sql(expression, "this")
4205        expressions = self.expressions(expression, flat=True)
4206        return f"{this}({expressions})"
4207
4208    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4209        return self.binary(expression, "=>")
4210
4211    def when_sql(self, expression: exp.When) -> str:
4212        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4213        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4214        condition = self.sql(expression, "condition")
4215        condition = f" AND {condition}" if condition else ""
4216
4217        then_expression = expression.args.get("then")
4218        if isinstance(then_expression, exp.Insert):
4219            this = self.sql(then_expression, "this")
4220            this = f"INSERT {this}" if this else "INSERT"
4221            then = self.sql(then_expression, "expression")
4222            then = f"{this} VALUES {then}" if then else this
4223        elif isinstance(then_expression, exp.Update):
4224            if isinstance(then_expression.args.get("expressions"), exp.Star):
4225                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4226            else:
4227                expressions_sql = self.expressions(then_expression)
4228                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4229
4230        else:
4231            then = self.sql(then_expression)
4232        return f"WHEN {matched}{source}{condition} THEN {then}"
4233
4234    def whens_sql(self, expression: exp.Whens) -> str:
4235        return self.expressions(expression, sep=" ", indent=False)
4236
4237    def merge_sql(self, expression: exp.Merge) -> str:
4238        table = expression.this
4239        table_alias = ""
4240
4241        hints = table.args.get("hints")
4242        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4243            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4244            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4245
4246        this = self.sql(table)
4247        using = f"USING {self.sql(expression, 'using')}"
4248        whens = self.sql(expression, "whens")
4249
4250        on = self.sql(expression, "on")
4251        on = f"ON {on}" if on else ""
4252
4253        if not on:
4254            on = self.expressions(expression, key="using_cond")
4255            on = f"USING ({on})" if on else ""
4256
4257        returning = self.sql(expression, "returning")
4258        if returning:
4259            whens = f"{whens}{returning}"
4260
4261        sep = self.sep()
4262
4263        return self.prepend_ctes(
4264            expression,
4265            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4266        )
4267
4268    @unsupported_args("format")
4269    def tochar_sql(self, expression: exp.ToChar) -> str:
4270        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
4271
4272    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4273        if not self.SUPPORTS_TO_NUMBER:
4274            self.unsupported("Unsupported TO_NUMBER function")
4275            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4276
4277        fmt = expression.args.get("format")
4278        if not fmt:
4279            self.unsupported("Conversion format is required for TO_NUMBER")
4280            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4281
4282        return self.func("TO_NUMBER", expression.this, fmt)
4283
4284    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4285        this = self.sql(expression, "this")
4286        kind = self.sql(expression, "kind")
4287        settings_sql = self.expressions(expression, key="settings", sep=" ")
4288        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4289        return f"{this}({kind}{args})"
4290
4291    def dictrange_sql(self, expression: exp.DictRange) -> str:
4292        this = self.sql(expression, "this")
4293        max = self.sql(expression, "max")
4294        min = self.sql(expression, "min")
4295        return f"{this}(MIN {min} MAX {max})"
4296
4297    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4298        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4299
4300    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4301        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4302
4303    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4304    def uniquekeyproperty_sql(
4305        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4306    ) -> str:
4307        return f"{prefix} ({self.expressions(expression, flat=True)})"
4308
4309    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4310    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4311        expressions = self.expressions(expression, flat=True)
4312        expressions = f" {self.wrap(expressions)}" if expressions else ""
4313        buckets = self.sql(expression, "buckets")
4314        kind = self.sql(expression, "kind")
4315        buckets = f" BUCKETS {buckets}" if buckets else ""
4316        order = self.sql(expression, "order")
4317        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4318
4319    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4320        return ""
4321
4322    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4323        expressions = self.expressions(expression, key="expressions", flat=True)
4324        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4325        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4326        buckets = self.sql(expression, "buckets")
4327        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4328
4329    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4330        this = self.sql(expression, "this")
4331        having = self.sql(expression, "having")
4332
4333        if having:
4334            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4335
4336        return self.func("ANY_VALUE", this)
4337
4338    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4339        transform = self.func("TRANSFORM", *expression.expressions)
4340        row_format_before = self.sql(expression, "row_format_before")
4341        row_format_before = f" {row_format_before}" if row_format_before else ""
4342        record_writer = self.sql(expression, "record_writer")
4343        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4344        using = f" USING {self.sql(expression, 'command_script')}"
4345        schema = self.sql(expression, "schema")
4346        schema = f" AS {schema}" if schema else ""
4347        row_format_after = self.sql(expression, "row_format_after")
4348        row_format_after = f" {row_format_after}" if row_format_after else ""
4349        record_reader = self.sql(expression, "record_reader")
4350        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4351        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4352
4353    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4354        key_block_size = self.sql(expression, "key_block_size")
4355        if key_block_size:
4356            return f"KEY_BLOCK_SIZE = {key_block_size}"
4357
4358        using = self.sql(expression, "using")
4359        if using:
4360            return f"USING {using}"
4361
4362        parser = self.sql(expression, "parser")
4363        if parser:
4364            return f"WITH PARSER {parser}"
4365
4366        comment = self.sql(expression, "comment")
4367        if comment:
4368            return f"COMMENT {comment}"
4369
4370        visible = expression.args.get("visible")
4371        if visible is not None:
4372            return "VISIBLE" if visible else "INVISIBLE"
4373
4374        engine_attr = self.sql(expression, "engine_attr")
4375        if engine_attr:
4376            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4377
4378        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4379        if secondary_engine_attr:
4380            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4381
4382        self.unsupported("Unsupported index constraint option.")
4383        return ""
4384
4385    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4386        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4387        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4388
4389    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4390        kind = self.sql(expression, "kind")
4391        kind = f"{kind} INDEX" if kind else "INDEX"
4392        this = self.sql(expression, "this")
4393        this = f" {this}" if this else ""
4394        index_type = self.sql(expression, "index_type")
4395        index_type = f" USING {index_type}" if index_type else ""
4396        expressions = self.expressions(expression, flat=True)
4397        expressions = f" ({expressions})" if expressions else ""
4398        options = self.expressions(expression, key="options", sep=" ")
4399        options = f" {options}" if options else ""
4400        return f"{kind}{this}{index_type}{expressions}{options}"
4401
4402    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4403        if self.NVL2_SUPPORTED:
4404            return self.function_fallback_sql(expression)
4405
4406        case = exp.Case().when(
4407            expression.this.is_(exp.null()).not_(copy=False),
4408            expression.args["true"],
4409            copy=False,
4410        )
4411        else_cond = expression.args.get("false")
4412        if else_cond:
4413            case.else_(else_cond, copy=False)
4414
4415        return self.sql(case)
4416
4417    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4418        this = self.sql(expression, "this")
4419        expr = self.sql(expression, "expression")
4420        position = self.sql(expression, "position")
4421        position = f", {position}" if position else ""
4422        iterator = self.sql(expression, "iterator")
4423        condition = self.sql(expression, "condition")
4424        condition = f" IF {condition}" if condition else ""
4425        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
4426
4427    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4428        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4429
4430    def opclass_sql(self, expression: exp.Opclass) -> str:
4431        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4432
4433    def _ml_sql(self, expression: exp.Func, name: str) -> str:
4434        model = self.sql(expression, "this")
4435        model = f"MODEL {model}"
4436        expr = expression.expression
4437        if expr:
4438            expr_sql = self.sql(expression, "expression")
4439            expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql
4440        else:
4441            expr_sql = None
4442
4443        parameters = self.sql(expression, "params_struct") or None
4444
4445        return self.func(name, model, expr_sql, parameters)
4446
4447    def predict_sql(self, expression: exp.Predict) -> str:
4448        return self._ml_sql(expression, "PREDICT")
4449
4450    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4451        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4452        return self._ml_sql(expression, name)
4453
4454    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
4455        return self._ml_sql(expression, "TRANSLATE")
4456
4457    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
4458        return self._ml_sql(expression, "FORECAST")
4459
4460    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4461        this_sql = self.sql(expression, "this")
4462        if isinstance(expression.this, exp.Table):
4463            this_sql = f"TABLE {this_sql}"
4464
4465        return self.func(
4466            "FEATURES_AT_TIME",
4467            this_sql,
4468            expression.args.get("time"),
4469            expression.args.get("num_rows"),
4470            expression.args.get("ignore_feature_nulls"),
4471        )
4472
4473    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
4474        this_sql = self.sql(expression, "this")
4475        if isinstance(expression.this, exp.Table):
4476            this_sql = f"TABLE {this_sql}"
4477
4478        query_table = self.sql(expression, "query_table")
4479        if isinstance(expression.args["query_table"], exp.Table):
4480            query_table = f"TABLE {query_table}"
4481
4482        return self.func(
4483            "VECTOR_SEARCH",
4484            this_sql,
4485            expression.args.get("column_to_search"),
4486            query_table,
4487            expression.args.get("query_column_to_search"),
4488            expression.args.get("top_k"),
4489            expression.args.get("distance_type"),
4490            expression.args.get("options"),
4491        )
4492
4493    def forin_sql(self, expression: exp.ForIn) -> str:
4494        this = self.sql(expression, "this")
4495        expression_sql = self.sql(expression, "expression")
4496        return f"FOR {this} DO {expression_sql}"
4497
4498    def refresh_sql(self, expression: exp.Refresh) -> str:
4499        this = self.sql(expression, "this")
4500        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
4501        return f"REFRESH {kind}{this}"
4502
4503    def toarray_sql(self, expression: exp.ToArray) -> str:
4504        arg = expression.this
4505        if not arg.type:
4506            from sqlglot.optimizer.annotate_types import annotate_types
4507
4508            arg = annotate_types(arg, dialect=self.dialect)
4509
4510        if arg.is_type(exp.DataType.Type.ARRAY):
4511            return self.sql(arg)
4512
4513        cond_for_null = arg.is_(exp.null())
4514        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4515
4516    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4517        this = expression.this
4518        time_format = self.format_time(expression)
4519
4520        if time_format:
4521            return self.sql(
4522                exp.cast(
4523                    exp.StrToTime(this=this, format=expression.args["format"]),
4524                    exp.DataType.Type.TIME,
4525                )
4526            )
4527
4528        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4529            return self.sql(this)
4530
4531        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4532
4533    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4534        this = expression.this
4535        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4536            return self.sql(this)
4537
4538        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4539
4540    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4541        this = expression.this
4542        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4543            return self.sql(this)
4544
4545        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4546
4547    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4548        this = expression.this
4549        time_format = self.format_time(expression)
4550        safe = expression.args.get("safe")
4551        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4552            return self.sql(
4553                exp.cast(
4554                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
4555                    exp.DataType.Type.DATE,
4556                )
4557            )
4558
4559        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4560            return self.sql(this)
4561
4562        if safe:
4563            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DataType.Type.DATE)))
4564
4565        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4566
4567    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4568        return self.sql(
4569            exp.func(
4570                "DATEDIFF",
4571                expression.this,
4572                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4573                "day",
4574            )
4575        )
4576
4577    def lastday_sql(self, expression: exp.LastDay) -> str:
4578        if self.LAST_DAY_SUPPORTS_DATE_PART:
4579            return self.function_fallback_sql(expression)
4580
4581        unit = expression.text("unit")
4582        if unit and unit != "MONTH":
4583            self.unsupported("Date parts are not supported in LAST_DAY.")
4584
4585        return self.func("LAST_DAY", expression.this)
4586
4587    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4588        from sqlglot.dialects.dialect import unit_to_str
4589
4590        return self.func(
4591            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4592        )
4593
4594    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4595        if self.CAN_IMPLEMENT_ARRAY_ANY:
4596            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4597            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4598            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4599            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4600
4601        from sqlglot.dialects import Dialect
4602
4603        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4604        if self.dialect.__class__ != Dialect:
4605            self.unsupported("ARRAY_ANY is unsupported")
4606
4607        return self.function_fallback_sql(expression)
4608
4609    def struct_sql(self, expression: exp.Struct) -> str:
4610        expression.set(
4611            "expressions",
4612            [
4613                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4614                if isinstance(e, exp.PropertyEQ)
4615                else e
4616                for e in expression.expressions
4617            ],
4618        )
4619
4620        return self.function_fallback_sql(expression)
4621
4622    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4623        low = self.sql(expression, "this")
4624        high = self.sql(expression, "expression")
4625
4626        return f"{low} TO {high}"
4627
4628    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4629        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4630        tables = f" {self.expressions(expression)}"
4631
4632        exists = " IF EXISTS" if expression.args.get("exists") else ""
4633
4634        on_cluster = self.sql(expression, "cluster")
4635        on_cluster = f" {on_cluster}" if on_cluster else ""
4636
4637        identity = self.sql(expression, "identity")
4638        identity = f" {identity} IDENTITY" if identity else ""
4639
4640        option = self.sql(expression, "option")
4641        option = f" {option}" if option else ""
4642
4643        partition = self.sql(expression, "partition")
4644        partition = f" {partition}" if partition else ""
4645
4646        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4647
4648    # This transpiles T-SQL's CONVERT function
4649    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4650    def convert_sql(self, expression: exp.Convert) -> str:
4651        to = expression.this
4652        value = expression.expression
4653        style = expression.args.get("style")
4654        safe = expression.args.get("safe")
4655        strict = expression.args.get("strict")
4656
4657        if not to or not value:
4658            return ""
4659
4660        # Retrieve length of datatype and override to default if not specified
4661        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4662            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4663
4664        transformed: t.Optional[exp.Expression] = None
4665        cast = exp.Cast if strict else exp.TryCast
4666
4667        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4668        if isinstance(style, exp.Literal) and style.is_int:
4669            from sqlglot.dialects.tsql import TSQL
4670
4671            style_value = style.name
4672            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4673            if not converted_style:
4674                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4675
4676            fmt = exp.Literal.string(converted_style)
4677
4678            if to.this == exp.DataType.Type.DATE:
4679                transformed = exp.StrToDate(this=value, format=fmt)
4680            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4681                transformed = exp.StrToTime(this=value, format=fmt)
4682            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4683                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4684            elif to.this == exp.DataType.Type.TEXT:
4685                transformed = exp.TimeToStr(this=value, format=fmt)
4686
4687        if not transformed:
4688            transformed = cast(this=value, to=to, safe=safe)
4689
4690        return self.sql(transformed)
4691
4692    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4693        this = expression.this
4694        if isinstance(this, exp.JSONPathWildcard):
4695            this = self.json_path_part(this)
4696            return f".{this}" if this else ""
4697
4698        if self.SAFE_JSON_PATH_KEY_RE.match(this):
4699            return f".{this}"
4700
4701        this = self.json_path_part(this)
4702        return (
4703            f"[{this}]"
4704            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
4705            else f".{this}"
4706        )
4707
4708    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
4709        this = self.json_path_part(expression.this)
4710        return f"[{this}]" if this else ""
4711
4712    def _simplify_unless_literal(self, expression: E) -> E:
4713        if not isinstance(expression, exp.Literal):
4714            from sqlglot.optimizer.simplify import simplify
4715
4716            expression = simplify(expression, dialect=self.dialect)
4717
4718        return expression
4719
4720    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
4721        this = expression.this
4722        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
4723            self.unsupported(
4724                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
4725            )
4726            return self.sql(this)
4727
4728        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
4729            # The first modifier here will be the one closest to the AggFunc's arg
4730            mods = sorted(
4731                expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
4732                key=lambda x: 0
4733                if isinstance(x, exp.HavingMax)
4734                else (1 if isinstance(x, exp.Order) else 2),
4735            )
4736
4737            if mods:
4738                mod = mods[0]
4739                this = expression.__class__(this=mod.this.copy())
4740                this.meta["inline"] = True
4741                mod.this.replace(this)
4742                return self.sql(expression.this)
4743
4744            agg_func = expression.find(exp.AggFunc)
4745
4746            if agg_func:
4747                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
4748                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
4749
4750        return f"{self.sql(expression, 'this')} {text}"
4751
4752    def _replace_line_breaks(self, string: str) -> str:
4753        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
4754        if self.pretty:
4755            return string.replace("\n", self.SENTINEL_LINE_BREAK)
4756        return string
4757
4758    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4759        option = self.sql(expression, "this")
4760
4761        if expression.expressions:
4762            upper = option.upper()
4763
4764            # Snowflake FILE_FORMAT options are separated by whitespace
4765            sep = " " if upper == "FILE_FORMAT" else ", "
4766
4767            # Databricks copy/format options do not set their list of values with EQ
4768            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4769            values = self.expressions(expression, flat=True, sep=sep)
4770            return f"{option}{op}({values})"
4771
4772        value = self.sql(expression, "expression")
4773
4774        if not value:
4775            return option
4776
4777        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4778
4779        return f"{option}{op}{value}"
4780
4781    def credentials_sql(self, expression: exp.Credentials) -> str:
4782        cred_expr = expression.args.get("credentials")
4783        if isinstance(cred_expr, exp.Literal):
4784            # Redshift case: CREDENTIALS <string>
4785            credentials = self.sql(expression, "credentials")
4786            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4787        else:
4788            # Snowflake case: CREDENTIALS = (...)
4789            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4790            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4791
4792        storage = self.sql(expression, "storage")
4793        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4794
4795        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4796        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4797
4798        iam_role = self.sql(expression, "iam_role")
4799        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4800
4801        region = self.sql(expression, "region")
4802        region = f" REGION {region}" if region else ""
4803
4804        return f"{credentials}{storage}{encryption}{iam_role}{region}"
4805
4806    def copy_sql(self, expression: exp.Copy) -> str:
4807        this = self.sql(expression, "this")
4808        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4809
4810        credentials = self.sql(expression, "credentials")
4811        credentials = self.seg(credentials) if credentials else ""
4812        files = self.expressions(expression, key="files", flat=True)
4813        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
4814
4815        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4816        params = self.expressions(
4817            expression,
4818            key="params",
4819            sep=sep,
4820            new_line=True,
4821            skip_last=True,
4822            skip_first=True,
4823            indent=self.COPY_PARAMS_ARE_WRAPPED,
4824        )
4825
4826        if params:
4827            if self.COPY_PARAMS_ARE_WRAPPED:
4828                params = f" WITH ({params})"
4829            elif not self.pretty and (files or credentials):
4830                params = f" {params}"
4831
4832        return f"COPY{this}{kind} {files}{credentials}{params}"
4833
4834    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4835        return ""
4836
4837    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4838        on_sql = "ON" if expression.args.get("on") else "OFF"
4839        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4840        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4841        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4842        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4843
4844        if filter_col or retention_period:
4845            on_sql = self.func("ON", filter_col, retention_period)
4846
4847        return f"DATA_DELETION={on_sql}"
4848
4849    def maskingpolicycolumnconstraint_sql(
4850        self, expression: exp.MaskingPolicyColumnConstraint
4851    ) -> str:
4852        this = self.sql(expression, "this")
4853        expressions = self.expressions(expression, flat=True)
4854        expressions = f" USING ({expressions})" if expressions else ""
4855        return f"MASKING POLICY {this}{expressions}"
4856
4857    def gapfill_sql(self, expression: exp.GapFill) -> str:
4858        this = self.sql(expression, "this")
4859        this = f"TABLE {this}"
4860        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
4861
4862    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4863        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
4864
4865    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4866        this = self.sql(expression, "this")
4867        expr = expression.expression
4868
4869        if isinstance(expr, exp.Func):
4870            # T-SQL's CLR functions are case sensitive
4871            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4872        else:
4873            expr = self.sql(expression, "expression")
4874
4875        return self.scope_resolution(expr, this)
4876
4877    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4878        if self.PARSE_JSON_NAME is None:
4879            return self.sql(expression.this)
4880
4881        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
4882
4883    def rand_sql(self, expression: exp.Rand) -> str:
4884        lower = self.sql(expression, "lower")
4885        upper = self.sql(expression, "upper")
4886
4887        if lower and upper:
4888            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4889        return self.func("RAND", expression.this)
4890
4891    def changes_sql(self, expression: exp.Changes) -> str:
4892        information = self.sql(expression, "information")
4893        information = f"INFORMATION => {information}"
4894        at_before = self.sql(expression, "at_before")
4895        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4896        end = self.sql(expression, "end")
4897        end = f"{self.seg('')}{end}" if end else ""
4898
4899        return f"CHANGES ({information}){at_before}{end}"
4900
4901    def pad_sql(self, expression: exp.Pad) -> str:
4902        prefix = "L" if expression.args.get("is_left") else "R"
4903
4904        fill_pattern = self.sql(expression, "fill_pattern") or None
4905        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4906            fill_pattern = "' '"
4907
4908        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
4909
4910    def summarize_sql(self, expression: exp.Summarize) -> str:
4911        table = " TABLE" if expression.args.get("table") else ""
4912        return f"SUMMARIZE{table} {self.sql(expression.this)}"
4913
4914    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4915        generate_series = exp.GenerateSeries(**expression.args)
4916
4917        parent = expression.parent
4918        if isinstance(parent, (exp.Alias, exp.TableAlias)):
4919            parent = parent.parent
4920
4921        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
4922            return self.sql(exp.Unnest(expressions=[generate_series]))
4923
4924        if isinstance(parent, exp.Select):
4925            self.unsupported("GenerateSeries projection unnesting is not supported.")
4926
4927        return self.sql(generate_series)
4928
4929    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
4930        if self.SUPPORTS_CONVERT_TIMEZONE:
4931            return self.function_fallback_sql(expression)
4932
4933        source_tz = expression.args.get("source_tz")
4934        target_tz = expression.args.get("target_tz")
4935        timestamp = expression.args.get("timestamp")
4936
4937        if source_tz and timestamp:
4938            timestamp = exp.AtTimeZone(
4939                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
4940            )
4941
4942        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
4943
4944        return self.sql(expr)
4945
4946    def json_sql(self, expression: exp.JSON) -> str:
4947        this = self.sql(expression, "this")
4948        this = f" {this}" if this else ""
4949
4950        _with = expression.args.get("with_")
4951
4952        if _with is None:
4953            with_sql = ""
4954        elif not _with:
4955            with_sql = " WITHOUT"
4956        else:
4957            with_sql = " WITH"
4958
4959        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
4960
4961        return f"JSON{this}{with_sql}{unique_sql}"
4962
4963    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
4964        def _generate_on_options(arg: t.Any) -> str:
4965            return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}"
4966
4967        path = self.sql(expression, "path")
4968        returning = self.sql(expression, "returning")
4969        returning = f" RETURNING {returning}" if returning else ""
4970
4971        on_condition = self.sql(expression, "on_condition")
4972        on_condition = f" {on_condition}" if on_condition else ""
4973
4974        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4975
4976    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
4977        else_ = "ELSE " if expression.args.get("else_") else ""
4978        condition = self.sql(expression, "expression")
4979        condition = f"WHEN {condition} THEN " if condition else else_
4980        insert = self.sql(expression, "this")[len("INSERT") :].strip()
4981        return f"{condition}{insert}"
4982
4983    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
4984        kind = self.sql(expression, "kind")
4985        expressions = self.seg(self.expressions(expression, sep=" "))
4986        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
4987        return res
4988
4989    def oncondition_sql(self, expression: exp.OnCondition) -> str:
4990        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
4991        empty = expression.args.get("empty")
4992        empty = (
4993            f"DEFAULT {empty} ON EMPTY"
4994            if isinstance(empty, exp.Expression)
4995            else self.sql(expression, "empty")
4996        )
4997
4998        error = expression.args.get("error")
4999        error = (
5000            f"DEFAULT {error} ON ERROR"
5001            if isinstance(error, exp.Expression)
5002            else self.sql(expression, "error")
5003        )
5004
5005        if error and empty:
5006            error = (
5007                f"{empty} {error}"
5008                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5009                else f"{error} {empty}"
5010            )
5011            empty = ""
5012
5013        null = self.sql(expression, "null")
5014
5015        return f"{empty}{error}{null}"
5016
5017    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5018        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5019        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5020
5021    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5022        this = self.sql(expression, "this")
5023        path = self.sql(expression, "path")
5024
5025        passing = self.expressions(expression, "passing")
5026        passing = f" PASSING {passing}" if passing else ""
5027
5028        on_condition = self.sql(expression, "on_condition")
5029        on_condition = f" {on_condition}" if on_condition else ""
5030
5031        path = f"{path}{passing}{on_condition}"
5032
5033        return self.func("JSON_EXISTS", this, path)
5034
5035    def _add_arrayagg_null_filter(
5036        self,
5037        array_agg_sql: str,
5038        array_agg_expr: exp.ArrayAgg,
5039        column_expr: exp.Expression,
5040    ) -> str:
5041        """
5042        Add NULL filter to ARRAY_AGG if dialect requires it.
5043
5044        Args:
5045            array_agg_sql: The generated ARRAY_AGG SQL string
5046            array_agg_expr: The ArrayAgg expression node
5047            column_expr: The column/expression to filter (before ORDER BY wrapping)
5048
5049        Returns:
5050            SQL string with FILTER clause added if needed
5051        """
5052        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5053        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5054        if not (
5055            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5056        ):
5057            return array_agg_sql
5058
5059        parent = array_agg_expr.parent
5060        if isinstance(parent, exp.Filter):
5061            parent_cond = parent.expression.this
5062            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5063        elif column_expr.find(exp.Column):
5064            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5065            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5066            this_sql = (
5067                self.expressions(column_expr)
5068                if isinstance(column_expr, exp.Distinct)
5069                else self.sql(column_expr)
5070            )
5071            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5072
5073        return array_agg_sql
5074
5075    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5076        array_agg = self.function_fallback_sql(expression)
5077        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
5078
5079    def slice_sql(self, expression: exp.Slice) -> str:
5080        step = self.sql(expression, "step")
5081        end = self.sql(expression.expression)
5082        begin = self.sql(expression.this)
5083
5084        sql = f"{end}:{step}" if step else end
5085        return f"{begin}:{sql}" if sql else f"{begin}:"
5086
5087    def apply_sql(self, expression: exp.Apply) -> str:
5088        this = self.sql(expression, "this")
5089        expr = self.sql(expression, "expression")
5090
5091        return f"{this} APPLY({expr})"
5092
5093    def _grant_or_revoke_sql(
5094        self,
5095        expression: exp.Grant | exp.Revoke,
5096        keyword: str,
5097        preposition: str,
5098        grant_option_prefix: str = "",
5099        grant_option_suffix: str = "",
5100    ) -> str:
5101        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5102
5103        kind = self.sql(expression, "kind")
5104        kind = f" {kind}" if kind else ""
5105
5106        securable = self.sql(expression, "securable")
5107        securable = f" {securable}" if securable else ""
5108
5109        principals = self.expressions(expression, key="principals", flat=True)
5110
5111        if not expression.args.get("grant_option"):
5112            grant_option_prefix = grant_option_suffix = ""
5113
5114        # cascade for revoke only
5115        cascade = self.sql(expression, "cascade")
5116        cascade = f" {cascade}" if cascade else ""
5117
5118        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5119
5120    def grant_sql(self, expression: exp.Grant) -> str:
5121        return self._grant_or_revoke_sql(
5122            expression,
5123            keyword="GRANT",
5124            preposition="TO",
5125            grant_option_suffix=" WITH GRANT OPTION",
5126        )
5127
5128    def revoke_sql(self, expression: exp.Revoke) -> str:
5129        return self._grant_or_revoke_sql(
5130            expression,
5131            keyword="REVOKE",
5132            preposition="FROM",
5133            grant_option_prefix="GRANT OPTION FOR ",
5134        )
5135
5136    def grantprivilege_sql(self, expression: exp.GrantPrivilege):
5137        this = self.sql(expression, "this")
5138        columns = self.expressions(expression, flat=True)
5139        columns = f"({columns})" if columns else ""
5140
5141        return f"{this}{columns}"
5142
5143    def grantprincipal_sql(self, expression: exp.GrantPrincipal):
5144        this = self.sql(expression, "this")
5145
5146        kind = self.sql(expression, "kind")
5147        kind = f"{kind} " if kind else ""
5148
5149        return f"{kind}{this}"
5150
5151    def columns_sql(self, expression: exp.Columns):
5152        func = self.function_fallback_sql(expression)
5153        if expression.args.get("unpack"):
5154            func = f"*{func}"
5155
5156        return func
5157
5158    def overlay_sql(self, expression: exp.Overlay):
5159        this = self.sql(expression, "this")
5160        expr = self.sql(expression, "expression")
5161        from_sql = self.sql(expression, "from_")
5162        for_sql = self.sql(expression, "for_")
5163        for_sql = f" FOR {for_sql}" if for_sql else ""
5164
5165        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
5166
5167    @unsupported_args("format")
5168    def todouble_sql(self, expression: exp.ToDouble) -> str:
5169        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5170        return self.sql(cast(this=expression.this, to=exp.DataType.build(exp.DataType.Type.DOUBLE)))
5171
5172    def string_sql(self, expression: exp.String) -> str:
5173        this = expression.this
5174        zone = expression.args.get("zone")
5175
5176        if zone:
5177            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5178            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5179            # set for source_tz to transpile the time conversion before the STRING cast
5180            this = exp.ConvertTimezone(
5181                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5182            )
5183
5184        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
5185
5186    def median_sql(self, expression: exp.Median):
5187        if not self.SUPPORTS_MEDIAN:
5188            return self.sql(
5189                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5190            )
5191
5192        return self.function_fallback_sql(expression)
5193
5194    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5195        filler = self.sql(expression, "this")
5196        filler = f" {filler}" if filler else ""
5197        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5198        return f"TRUNCATE{filler} {with_count}"
5199
5200    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5201        if self.SUPPORTS_UNIX_SECONDS:
5202            return self.function_fallback_sql(expression)
5203
5204        start_ts = exp.cast(
5205            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
5206        )
5207
5208        return self.sql(
5209            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5210        )
5211
5212    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5213        dim = expression.expression
5214
5215        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5216        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5217            if not (dim.is_int and dim.name == "1"):
5218                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5219            dim = None
5220
5221        # If dimension is required but not specified, default initialize it
5222        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5223            dim = exp.Literal.number(1)
5224
5225        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5226
5227    def attach_sql(self, expression: exp.Attach) -> str:
5228        this = self.sql(expression, "this")
5229        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5230        expressions = self.expressions(expression)
5231        expressions = f" ({expressions})" if expressions else ""
5232
5233        return f"ATTACH{exists_sql} {this}{expressions}"
5234
5235    def detach_sql(self, expression: exp.Detach) -> str:
5236        this = self.sql(expression, "this")
5237        # the DATABASE keyword is required if IF EXISTS is set
5238        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
5239        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5240        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
5241
5242        return f"DETACH{exists_sql} {this}"
5243
5244    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5245        this = self.sql(expression, "this")
5246        value = self.sql(expression, "expression")
5247        value = f" {value}" if value else ""
5248        return f"{this}{value}"
5249
5250    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5251        return (
5252            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5253        )
5254
5255    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5256        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5257        encode = f"{encode} {self.sql(expression, 'this')}"
5258
5259        properties = expression.args.get("properties")
5260        if properties:
5261            encode = f"{encode} {self.properties(properties)}"
5262
5263        return encode
5264
5265    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5266        this = self.sql(expression, "this")
5267        include = f"INCLUDE {this}"
5268
5269        column_def = self.sql(expression, "column_def")
5270        if column_def:
5271            include = f"{include} {column_def}"
5272
5273        alias = self.sql(expression, "alias")
5274        if alias:
5275            include = f"{include} AS {alias}"
5276
5277        return include
5278
5279    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5280        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
5281        name = f"{prefix} {self.sql(expression, 'this')}"
5282        return self.func("XMLELEMENT", name, *expression.expressions)
5283
5284    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5285        this = self.sql(expression, "this")
5286        expr = self.sql(expression, "expression")
5287        expr = f"({expr})" if expr else ""
5288        return f"{this}{expr}"
5289
5290    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5291        partitions = self.expressions(expression, "partition_expressions")
5292        create = self.expressions(expression, "create_expressions")
5293        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
5294
5295    def partitionbyrangepropertydynamic_sql(
5296        self, expression: exp.PartitionByRangePropertyDynamic
5297    ) -> str:
5298        start = self.sql(expression, "start")
5299        end = self.sql(expression, "end")
5300
5301        every = expression.args["every"]
5302        if isinstance(every, exp.Interval) and every.this.is_string:
5303            every.this.replace(exp.Literal.number(every.name))
5304
5305        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5306
5307    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5308        name = self.sql(expression, "this")
5309        values = self.expressions(expression, flat=True)
5310
5311        return f"NAME {name} VALUE {values}"
5312
5313    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5314        kind = self.sql(expression, "kind")
5315        sample = self.sql(expression, "sample")
5316        return f"SAMPLE {sample} {kind}"
5317
5318    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5319        kind = self.sql(expression, "kind")
5320        option = self.sql(expression, "option")
5321        option = f" {option}" if option else ""
5322        this = self.sql(expression, "this")
5323        this = f" {this}" if this else ""
5324        columns = self.expressions(expression)
5325        columns = f" {columns}" if columns else ""
5326        return f"{kind}{option} STATISTICS{this}{columns}"
5327
5328    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5329        this = self.sql(expression, "this")
5330        columns = self.expressions(expression)
5331        inner_expression = self.sql(expression, "expression")
5332        inner_expression = f" {inner_expression}" if inner_expression else ""
5333        update_options = self.sql(expression, "update_options")
5334        update_options = f" {update_options} UPDATE" if update_options else ""
5335        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
5336
5337    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5338        kind = self.sql(expression, "kind")
5339        kind = f" {kind}" if kind else ""
5340        return f"DELETE{kind} STATISTICS"
5341
5342    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5343        inner_expression = self.sql(expression, "expression")
5344        return f"LIST CHAINED ROWS{inner_expression}"
5345
5346    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5347        kind = self.sql(expression, "kind")
5348        this = self.sql(expression, "this")
5349        this = f" {this}" if this else ""
5350        inner_expression = self.sql(expression, "expression")
5351        return f"VALIDATE {kind}{this}{inner_expression}"
5352
5353    def analyze_sql(self, expression: exp.Analyze) -> str:
5354        options = self.expressions(expression, key="options", sep=" ")
5355        options = f" {options}" if options else ""
5356        kind = self.sql(expression, "kind")
5357        kind = f" {kind}" if kind else ""
5358        this = self.sql(expression, "this")
5359        this = f" {this}" if this else ""
5360        mode = self.sql(expression, "mode")
5361        mode = f" {mode}" if mode else ""
5362        properties = self.sql(expression, "properties")
5363        properties = f" {properties}" if properties else ""
5364        partition = self.sql(expression, "partition")
5365        partition = f" {partition}" if partition else ""
5366        inner_expression = self.sql(expression, "expression")
5367        inner_expression = f" {inner_expression}" if inner_expression else ""
5368        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5369
5370    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5371        this = self.sql(expression, "this")
5372        namespaces = self.expressions(expression, key="namespaces")
5373        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5374        passing = self.expressions(expression, key="passing")
5375        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5376        columns = self.expressions(expression, key="columns")
5377        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5378        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5379        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5380
5381    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5382        this = self.sql(expression, "this")
5383        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
5384
5385    def export_sql(self, expression: exp.Export) -> str:
5386        this = self.sql(expression, "this")
5387        connection = self.sql(expression, "connection")
5388        connection = f"WITH CONNECTION {connection} " if connection else ""
5389        options = self.sql(expression, "options")
5390        return f"EXPORT DATA {connection}{options} AS {this}"
5391
5392    def declare_sql(self, expression: exp.Declare) -> str:
5393        return f"DECLARE {self.expressions(expression, flat=True)}"
5394
5395    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5396        variable = self.sql(expression, "this")
5397        default = self.sql(expression, "default")
5398        default = f" = {default}" if default else ""
5399
5400        kind = self.sql(expression, "kind")
5401        if isinstance(expression.args.get("kind"), exp.Schema):
5402            kind = f"TABLE {kind}"
5403
5404        return f"{variable} AS {kind}{default}"
5405
5406    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5407        kind = self.sql(expression, "kind")
5408        this = self.sql(expression, "this")
5409        set = self.sql(expression, "expression")
5410        using = self.sql(expression, "using")
5411        using = f" USING {using}" if using else ""
5412
5413        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5414
5415        return f"{kind_sql} {this} SET {set}{using}"
5416
5417    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5418        params = self.expressions(expression, key="params", flat=True)
5419        return self.func(expression.name, *expression.expressions) + f"({params})"
5420
5421    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5422        return self.func(expression.name, *expression.expressions)
5423
5424    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5425        return self.anonymousaggfunc_sql(expression)
5426
5427    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5428        return self.parameterizedagg_sql(expression)
5429
5430    def show_sql(self, expression: exp.Show) -> str:
5431        self.unsupported("Unsupported SHOW statement")
5432        return ""
5433
5434    def install_sql(self, expression: exp.Install) -> str:
5435        self.unsupported("Unsupported INSTALL statement")
5436        return ""
5437
5438    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5439        # Snowflake GET/PUT statements:
5440        #   PUT <file> <internalStage> <properties>
5441        #   GET <internalStage> <file> <properties>
5442        props = expression.args.get("properties")
5443        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5444        this = self.sql(expression, "this")
5445        target = self.sql(expression, "target")
5446
5447        if isinstance(expression, exp.Put):
5448            return f"PUT {this} {target}{props_sql}"
5449        else:
5450            return f"GET {target} {this}{props_sql}"
5451
5452    def translatecharacters_sql(self, expression: exp.TranslateCharacters):
5453        this = self.sql(expression, "this")
5454        expr = self.sql(expression, "expression")
5455        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5456        return f"TRANSLATE({this} USING {expr}{with_error})"
5457
5458    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5459        if self.SUPPORTS_DECODE_CASE:
5460            return self.func("DECODE", *expression.expressions)
5461
5462        expression, *expressions = expression.expressions
5463
5464        ifs = []
5465        for search, result in zip(expressions[::2], expressions[1::2]):
5466            if isinstance(search, exp.Literal):
5467                ifs.append(exp.If(this=expression.eq(search), true=result))
5468            elif isinstance(search, exp.Null):
5469                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5470            else:
5471                if isinstance(search, exp.Binary):
5472                    search = exp.paren(search)
5473
5474                cond = exp.or_(
5475                    expression.eq(search),
5476                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5477                    copy=False,
5478                )
5479                ifs.append(exp.If(this=cond, true=result))
5480
5481        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5482        return self.sql(case)
5483
5484    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5485        this = self.sql(expression, "this")
5486        this = self.seg(this, sep="")
5487        dimensions = self.expressions(
5488            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5489        )
5490        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5491        metrics = self.expressions(
5492            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5493        )
5494        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5495        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
5496        facts = self.seg(f"FACTS {facts}") if facts else ""
5497        where = self.sql(expression, "where")
5498        where = self.seg(f"WHERE {where}") if where else ""
5499        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
5500        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5501
5502    def getextract_sql(self, expression: exp.GetExtract) -> str:
5503        this = expression.this
5504        expr = expression.expression
5505
5506        if not this.type or not expression.type:
5507            from sqlglot.optimizer.annotate_types import annotate_types
5508
5509            this = annotate_types(this, dialect=self.dialect)
5510
5511        if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)):
5512            return self.sql(exp.Bracket(this=this, expressions=[expr]))
5513
5514        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
5515
5516    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
5517        return self.sql(
5518            exp.DateAdd(
5519                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
5520                expression=expression.this,
5521                unit=exp.var("DAY"),
5522            )
5523        )
5524
5525    def space_sql(self: Generator, expression: exp.Space) -> str:
5526        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
5527
5528    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
5529        return f"BUILD {self.sql(expression, 'this')}"
5530
5531    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
5532        method = self.sql(expression, "method")
5533        kind = expression.args.get("kind")
5534        if not kind:
5535            return f"REFRESH {method}"
5536
5537        every = self.sql(expression, "every")
5538        unit = self.sql(expression, "unit")
5539        every = f" EVERY {every} {unit}" if every else ""
5540        starts = self.sql(expression, "starts")
5541        starts = f" STARTS {starts}" if starts else ""
5542
5543        return f"REFRESH {method} ON {kind}{every}{starts}"
5544
5545    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
5546        self.unsupported("The model!attribute syntax is not supported")
5547        return ""
5548
5549    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
5550        return self.func("DIRECTORY", expression.this)
5551
5552    def uuid_sql(self, expression: exp.Uuid) -> str:
5553        is_string = expression.args.get("is_string", False)
5554        uuid_func_sql = self.func("UUID")
5555
5556        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
5557            return self.sql(
5558                exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect)
5559            )
5560
5561        return uuid_func_sql
5562
5563    def initcap_sql(self, expression: exp.Initcap) -> str:
5564        delimiters = expression.expression
5565
5566        if delimiters:
5567            # do not generate delimiters arg if we are round-tripping from default delimiters
5568            if (
5569                delimiters.is_string
5570                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
5571            ):
5572                delimiters = None
5573            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
5574                self.unsupported("INITCAP does not support custom delimiters")
5575                delimiters = None
5576
5577        return self.func("INITCAP", expression.this, delimiters)
5578
5579    def localtime_sql(self, expression: exp.Localtime) -> str:
5580        this = expression.this
5581        return self.func("LOCALTIME", this) if this else "LOCALTIME"
5582
5583    def localtimestamp_sql(self, expression: exp.Localtime) -> str:
5584        this = expression.this
5585        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
5586
5587    def weekstart_sql(self, expression: exp.WeekStart) -> str:
5588        this = expression.this.name.upper()
5589        if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
5590            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
5591            return "WEEK"
5592
5593        return self.func("WEEK", expression.this)
5594
5595    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
5596        this = self.expressions(expression)
5597        charset = self.sql(expression, "charset")
5598        using = f" USING {charset}" if charset else ""
5599        return self.func(name, this + using)

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: Always quote except for specials cases. '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)
761    def __init__(
762        self,
763        pretty: t.Optional[bool] = None,
764        identify: str | bool = False,
765        normalize: bool = False,
766        pad: int = 2,
767        indent: int = 2,
768        normalize_functions: t.Optional[str | bool] = None,
769        unsupported_level: ErrorLevel = ErrorLevel.WARN,
770        max_unsupported: int = 3,
771        leading_comma: bool = False,
772        max_text_width: int = 80,
773        comments: bool = True,
774        dialect: DialectType = None,
775    ):
776        import sqlglot
777        from sqlglot.dialects import Dialect
778
779        self.pretty = pretty if pretty is not None else sqlglot.pretty
780        self.identify = identify
781        self.normalize = normalize
782        self.pad = pad
783        self._indent = indent
784        self.unsupported_level = unsupported_level
785        self.max_unsupported = max_unsupported
786        self.leading_comma = leading_comma
787        self.max_text_width = max_text_width
788        self.comments = comments
789        self.dialect = Dialect.get_or_raise(dialect)
790
791        # This is both a Dialect property and a Generator argument, so we prioritize the latter
792        self.normalize_functions = (
793            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
794        )
795
796        self.unsupported_messages: t.List[str] = []
797        self._escaped_quote_end: str = (
798            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
799        )
800        self._escaped_byte_quote_end: str = (
801            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
802            if self.dialect.BYTE_END
803            else ""
804        )
805        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
806
807        self._next_name = name_sequence("_t")
808
809        self._identifier_start = self.dialect.IDENTIFIER_START
810        self._identifier_end = self.dialect.IDENTIFIER_END
811
812        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.Adjacent'>: <function Generator.<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.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SessionUser'>: <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.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.JSONBDeleteAtPath'>: <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.NetFunc'>: <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.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExtendsRight'>: <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.ZeroFillColumnConstraint'>: <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.SafeFunc'>: <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.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.Variadic'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ForceProperty'>: <function Generator.<lambda>>}
NULL_ORDERING_SUPPORTED: Optional[bool] = True
IGNORE_NULLS_IN_FUNC = False
LOCKING_READS_SUPPORTED = False
EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
WRAP_DERIVED_VALUES = True
CREATE_FUNCTION_RETURN_AS = True
MATCHED_BY_SOURCE = True
SINGLE_STRING_INTERVAL = False
INTERVAL_ALLOWS_PLURAL_FORM = True
LIMIT_FETCH = 'ALL'
LIMIT_ONLY_LITERALS = False
RENAME_TABLE_WITH_DB = True
GROUPINGS_SEP = ','
INDEX_ON = 'ON'
INOUT_SEPARATOR = ' '
JOIN_HINTS = True
DIRECTED_JOINS = False
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
SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
UPDATE_STATEMENT_SUPPORTS_FROM = True
TYPE_MAPPING = {<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
UNSUPPORTED_TYPES: set[sqlglot.expressions.DataType.Type] = set()
TIME_PART_SINGULARS = {'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS = {'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
TOKEN_MAPPING: Dict[sqlglot.tokens.TokenType, str] = {}
STRUCT_DELIMITER = ('<', '>')
PARAMETER_TOKEN = '@'
NAMED_PLACEHOLDER_TOKEN = ':'
EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: Set[str] = set()
PROPERTIES_LOCATION = {<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RefreshTriggerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RollupProperty'>: <Location.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
RESERVED_KEYWORDS: Set[str] = set()
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES = {<Type.NCHAR: 'NCHAR'>, <Type.VARCHAR: 'VARCHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.CHAR: 'CHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: Set[Type[sqlglot.expressions.Expression]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: Tuple[Type[sqlglot.expressions.Expression], ...] = ()
SAFE_JSON_PATH_KEY_RE = re.compile('^[_a-zA-Z][\\w]*$')
SENTINEL_LINE_BREAK = '__SQLGLOT__LB__'
pretty
identify
normalize
pad
unsupported_level
max_unsupported
leading_comma
max_text_width
comments
dialect
normalize_functions
unsupported_messages: List[str]
def generate( self, expression: sqlglot.expressions.Expression, copy: bool = True) -> str:
814    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
815        """
816        Generates the SQL string corresponding to the given syntax tree.
817
818        Args:
819            expression: The syntax tree.
820            copy: Whether to copy the expression. The generator performs mutations so
821                it is safer to copy.
822
823        Returns:
824            The SQL string corresponding to `expression`.
825        """
826        if copy:
827            expression = expression.copy()
828
829        expression = self.preprocess(expression)
830
831        self.unsupported_messages = []
832        sql = self.sql(expression).strip()
833
834        if self.pretty:
835            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
836
837        if self.unsupported_level == ErrorLevel.IGNORE:
838            return sql
839
840        if self.unsupported_level == ErrorLevel.WARN:
841            for msg in self.unsupported_messages:
842                logger.warning(msg)
843        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
844            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
845
846        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:
848    def preprocess(self, expression: exp.Expression) -> exp.Expression:
849        """Apply generic preprocessing transformations to a given expression."""
850        expression = self._move_ctes_to_top_level(expression)
851
852        if self.ENSURE_BOOLS:
853            from sqlglot.transforms import ensure_bools
854
855            expression = ensure_bools(expression)
856
857        return expression

Apply generic preprocessing transformations to a given expression.

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