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.expressions import apply_index_offset
  12from sqlglot.helper import csv, name_sequence, seq_get
  13from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS
  14from sqlglot.time import format_time
  15from sqlglot.tokens import TokenType
  16
  17if t.TYPE_CHECKING:
  18    from sqlglot._typing import E
  19    from sqlglot.dialects.dialect import DialectType
  20
  21    G = t.TypeVar("G", bound="Generator")
  22    GeneratorMethod = t.Callable[[G, E], str]
  23
  24logger = logging.getLogger("sqlglot")
  25
  26ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)")
  27UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}."
  28
  29
  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
  62
  63
  64class _Generator(type):
  65    def __new__(cls, clsname, bases, attrs):
  66        klass = super().__new__(cls, clsname, bases, attrs)
  67
  68        # Remove transforms that correspond to unsupported JSONPathPart expressions
  69        for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS:
  70            klass.TRANSFORMS.pop(part, None)
  71
  72        return klass
  73
  74
  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.EndStatement: lambda *_: "END",
 150        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 151        exp.EphemeralColumnConstraint: lambda self,
 152        e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}",
 153        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 154        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 155        exp.Except: lambda self, e: self.set_operations(e),
 156        exp.ExternalProperty: lambda *_: "EXTERNAL",
 157        exp.Floor: lambda self, e: self.ceil_floor(e),
 158        exp.Get: lambda self, e: self.get_put_sql(e),
 159        exp.GlobalProperty: lambda *_: "GLOBAL",
 160        exp.HeapProperty: lambda *_: "HEAP",
 161        exp.IcebergProperty: lambda *_: "ICEBERG",
 162        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 163        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 164        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 165        exp.Intersect: lambda self, e: self.set_operations(e),
 166        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 167        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)),
 168        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 169        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 170        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 171        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 172        exp.LocationProperty: lambda self, e: self.naked_property(e),
 173        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 174        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 175        exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
 176        exp.NonClusteredColumnConstraint: lambda self,
 177        e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})",
 178        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 179        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 180        exp.OnCommitProperty: lambda _,
 181        e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS",
 182        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 183        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 184        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 185        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 186        exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
 187        exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
 188        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 189        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 190        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 191        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 192        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 193        exp.ProjectionPolicyColumnConstraint: lambda self,
 194        e: f"PROJECTION POLICY {self.sql(e, 'this')}",
 195        exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
 196        exp.Put: lambda self, e: self.get_put_sql(e),
 197        exp.RemoteWithConnectionModelProperty: lambda self,
 198        e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
 199        exp.ReturnsProperty: lambda self, e: (
 200            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 201        ),
 202        exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
 203        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 204        exp.SecureProperty: lambda *_: "SECURE",
 205        exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}",
 206        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 207        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 208        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 209        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 210        exp.SqlReadWriteProperty: lambda _, e: e.name,
 211        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
 212        exp.StabilityProperty: lambda _, e: e.name,
 213        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 214        exp.StreamingTableProperty: lambda *_: "STREAMING",
 215        exp.StrictProperty: lambda *_: "STRICT",
 216        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 217        exp.TableColumn: lambda self, e: self.sql(e.this),
 218        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 219        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 220        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 221        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 222        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 223        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 224        exp.TransientProperty: lambda *_: "TRANSIENT",
 225        exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}",
 226        exp.Union: lambda self, e: self.set_operations(e),
 227        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 228        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 229        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 230        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 231        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 232        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 233        exp.UtcTimestamp: lambda self, e: self.sql(
 234            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 235        ),
 236        exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
 237        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 238        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 239        exp.VolatileProperty: lambda *_: "VOLATILE",
 240        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 241        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 242        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 243        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 244        exp.ForceProperty: lambda *_: "FORCE",
 245    }
 246
 247    # Whether null ordering is supported in order by
 248    # True: Full Support, None: No support, False: No support for certain cases
 249    # such as window specifications, aggregate functions etc
 250    NULL_ORDERING_SUPPORTED: t.Optional[bool] = True
 251
 252    # Whether ignore nulls is inside the agg or outside.
 253    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 254    IGNORE_NULLS_IN_FUNC = False
 255
 256    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 257    LOCKING_READS_SUPPORTED = False
 258
 259    # Whether the EXCEPT and INTERSECT operations can return duplicates
 260    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 261
 262    # Wrap derived values in parens, usually standard but spark doesn't support it
 263    WRAP_DERIVED_VALUES = True
 264
 265    # Whether create function uses an AS before the RETURN
 266    CREATE_FUNCTION_RETURN_AS = True
 267
 268    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 269    MATCHED_BY_SOURCE = True
 270
 271    # Whether the INTERVAL expression works only with values like '1 day'
 272    SINGLE_STRING_INTERVAL = False
 273
 274    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 275    INTERVAL_ALLOWS_PLURAL_FORM = True
 276
 277    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 278    LIMIT_FETCH = "ALL"
 279
 280    # Whether limit and fetch allows expresions or just limits
 281    LIMIT_ONLY_LITERALS = False
 282
 283    # Whether a table is allowed to be renamed with a db
 284    RENAME_TABLE_WITH_DB = True
 285
 286    # The separator for grouping sets and rollups
 287    GROUPINGS_SEP = ","
 288
 289    # The string used for creating an index on a table
 290    INDEX_ON = "ON"
 291
 292    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 293    INOUT_SEPARATOR = " "
 294
 295    # Whether join hints should be generated
 296    JOIN_HINTS = True
 297
 298    # Whether directed joins are supported
 299    DIRECTED_JOINS = False
 300
 301    # Whether table hints should be generated
 302    TABLE_HINTS = True
 303
 304    # Whether query hints should be generated
 305    QUERY_HINTS = True
 306
 307    # What kind of separator to use for query hints
 308    QUERY_HINT_SEP = ", "
 309
 310    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 311    IS_BOOL_ALLOWED = True
 312
 313    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 314    DUPLICATE_KEY_UPDATE_WITH_SET = True
 315
 316    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 317    LIMIT_IS_TOP = False
 318
 319    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 320    RETURNING_END = True
 321
 322    # Whether to generate an unquoted value for EXTRACT's date part argument
 323    EXTRACT_ALLOWS_QUOTES = True
 324
 325    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 326    TZ_TO_WITH_TIME_ZONE = False
 327
 328    # Whether the NVL2 function is supported
 329    NVL2_SUPPORTED = True
 330
 331    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 332    SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE")
 333
 334    # Whether VALUES statements can be used as derived tables.
 335    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 336    # SELECT * VALUES into SELECT UNION
 337    VALUES_AS_TABLE = True
 338
 339    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 340    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 341
 342    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 343    UNNEST_WITH_ORDINALITY = True
 344
 345    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 346    AGGREGATE_FILTER_SUPPORTED = True
 347
 348    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 349    SEMI_ANTI_JOIN_WITH_SIDE = True
 350
 351    # Whether to include the type of a computed column in the CREATE DDL
 352    COMPUTED_COLUMN_WITH_TYPE = True
 353
 354    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 355    SUPPORTS_TABLE_COPY = True
 356
 357    # Whether parentheses are required around the table sample's expression
 358    TABLESAMPLE_REQUIRES_PARENS = True
 359
 360    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 361    TABLESAMPLE_SIZE_IS_ROWS = True
 362
 363    # The keyword(s) to use when generating a sample clause
 364    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 365
 366    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 367    TABLESAMPLE_WITH_METHOD = True
 368
 369    # The keyword to use when specifying the seed of a sample clause
 370    TABLESAMPLE_SEED_KEYWORD = "SEED"
 371
 372    # Whether COLLATE is a function instead of a binary operator
 373    COLLATE_IS_FUNC = False
 374
 375    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 376    DATA_TYPE_SPECIFIERS_ALLOWED = False
 377
 378    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 379    ENSURE_BOOLS = False
 380
 381    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 382    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 383
 384    # Whether CONCAT requires >1 arguments
 385    SUPPORTS_SINGLE_ARG_CONCAT = True
 386
 387    # Whether LAST_DAY function supports a date part argument
 388    LAST_DAY_SUPPORTS_DATE_PART = True
 389
 390    # Whether named columns are allowed in table aliases
 391    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 392
 393    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 394    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 395
 396    # What delimiter to use for separating JSON key/value pairs
 397    JSON_KEY_VALUE_PAIR_SEP = ":"
 398
 399    # INSERT OVERWRITE TABLE x override
 400    INSERT_OVERWRITE = " OVERWRITE TABLE"
 401
 402    # Whether the SELECT .. INTO syntax is used instead of CTAS
 403    SUPPORTS_SELECT_INTO = False
 404
 405    # Whether UNLOGGED tables can be created
 406    SUPPORTS_UNLOGGED_TABLES = False
 407
 408    # Whether the CREATE TABLE LIKE statement is supported
 409    SUPPORTS_CREATE_TABLE_LIKE = True
 410
 411    # Whether the LikeProperty needs to be specified inside of the schema clause
 412    LIKE_PROPERTY_INSIDE_SCHEMA = False
 413
 414    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 415    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 416    MULTI_ARG_DISTINCT = True
 417
 418    # Whether the JSON extraction operators expect a value of type JSON
 419    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 420
 421    # Whether bracketed keys like ["foo"] are supported in JSON paths
 422    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 423
 424    # Whether to escape keys using single quotes in JSON paths
 425    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 426
 427    # The JSONPathPart expressions supported by this dialect
 428    SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy()
 429
 430    # Whether any(f(x) for x in array) can be implemented by this dialect
 431    CAN_IMPLEMENT_ARRAY_ANY = False
 432
 433    # Whether the function TO_NUMBER is supported
 434    SUPPORTS_TO_NUMBER = True
 435
 436    # Whether EXCLUDE in window specification is supported
 437    SUPPORTS_WINDOW_EXCLUDE = False
 438
 439    # Whether or not set op modifiers apply to the outer set op or select.
 440    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 441    # True means limit 1 happens after the set op, False means it it happens on y.
 442    SET_OP_MODIFIERS = True
 443
 444    # Whether parameters from COPY statement are wrapped in parentheses
 445    COPY_PARAMS_ARE_WRAPPED = True
 446
 447    # Whether values of params are set with "=" token or empty space
 448    COPY_PARAMS_EQ_REQUIRED = False
 449
 450    # Whether COPY statement has INTO keyword
 451    COPY_HAS_INTO_KEYWORD = True
 452
 453    # Whether the conditional TRY(expression) function is supported
 454    TRY_SUPPORTED = True
 455
 456    # Whether the UESCAPE syntax in unicode strings is supported
 457    SUPPORTS_UESCAPE = True
 458
 459    # Function used to replace escaped unicode codes in unicode strings
 460    UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None
 461
 462    # The keyword to use when generating a star projection with excluded columns
 463    STAR_EXCEPT = "EXCEPT"
 464
 465    # The HEX function name
 466    HEX_FUNC = "HEX"
 467
 468    # The keywords to use when prefixing & separating WITH based properties
 469    WITH_PROPERTIES_PREFIX = "WITH"
 470
 471    # Whether to quote the generated expression of exp.JsonPath
 472    QUOTE_JSON_PATH = True
 473
 474    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 475    PAD_FILL_PATTERN_IS_REQUIRED = False
 476
 477    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 478    SUPPORTS_EXPLODING_PROJECTIONS = True
 479
 480    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 481    ARRAY_CONCAT_IS_VAR_LEN = True
 482
 483    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 484    SUPPORTS_CONVERT_TIMEZONE = False
 485
 486    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 487    SUPPORTS_MEDIAN = True
 488
 489    # Whether UNIX_SECONDS(timestamp) is supported
 490    SUPPORTS_UNIX_SECONDS = False
 491
 492    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 493    ALTER_SET_WRAPPED = False
 494
 495    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 496    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 497    # TODO: The normalization should be done by default once we've tested it across all dialects.
 498    NORMALIZE_EXTRACT_DATE_PARTS = False
 499
 500    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 501    PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
 502
 503    # The function name of the exp.ArraySize expression
 504    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 505
 506    # The syntax to use when altering the type of a column
 507    ALTER_SET_TYPE = "SET DATA TYPE"
 508
 509    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 510    # None -> Doesn't support it at all
 511    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 512    # True (Postgres) -> Explicitly requires it
 513    ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None
 514
 515    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 516    SUPPORTS_DECODE_CASE = True
 517
 518    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 519    SUPPORTS_BETWEEN_FLAGS = False
 520
 521    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 522    SUPPORTS_LIKE_QUANTIFIERS = True
 523
 524    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 525    MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None
 526
 527    # Whether to include the VARIABLE keyword for SET assignments
 528    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 529
 530    # The keyword to use for default value assignment in DECLARE statements
 531    DECLARE_DEFAULT_ASSIGNMENT = "="
 532
 533    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 534    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 535    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 536    UPDATE_STATEMENT_SUPPORTS_FROM = True
 537
 538    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 539    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 540
 541    TYPE_MAPPING = {
 542        exp.DataType.Type.DATETIME2: "TIMESTAMP",
 543        exp.DataType.Type.NCHAR: "CHAR",
 544        exp.DataType.Type.NVARCHAR: "VARCHAR",
 545        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 546        exp.DataType.Type.LONGTEXT: "TEXT",
 547        exp.DataType.Type.TINYTEXT: "TEXT",
 548        exp.DataType.Type.BLOB: "VARBINARY",
 549        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 550        exp.DataType.Type.LONGBLOB: "BLOB",
 551        exp.DataType.Type.TINYBLOB: "BLOB",
 552        exp.DataType.Type.INET: "INET",
 553        exp.DataType.Type.ROWVERSION: "VARBINARY",
 554        exp.DataType.Type.SMALLDATETIME: "TIMESTAMP",
 555    }
 556
 557    UNSUPPORTED_TYPES: set[exp.DataType.Type] = set()
 558
 559    TIME_PART_SINGULARS = {
 560        "MICROSECONDS": "MICROSECOND",
 561        "SECONDS": "SECOND",
 562        "MINUTES": "MINUTE",
 563        "HOURS": "HOUR",
 564        "DAYS": "DAY",
 565        "WEEKS": "WEEK",
 566        "MONTHS": "MONTH",
 567        "QUARTERS": "QUARTER",
 568        "YEARS": "YEAR",
 569    }
 570
 571    AFTER_HAVING_MODIFIER_TRANSFORMS = {
 572        "cluster": lambda self, e: self.sql(e, "cluster"),
 573        "distribute": lambda self, e: self.sql(e, "distribute"),
 574        "sort": lambda self, e: self.sql(e, "sort"),
 575        "windows": lambda self, e: (
 576            self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
 577            if e.args.get("windows")
 578            else ""
 579        ),
 580        "qualify": lambda self, e: self.sql(e, "qualify"),
 581    }
 582
 583    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 584
 585    STRUCT_DELIMITER = ("<", ">")
 586
 587    PARAMETER_TOKEN = "@"
 588    NAMED_PLACEHOLDER_TOKEN = ":"
 589
 590    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set()
 591
 592    PROPERTIES_LOCATION = {
 593        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 594        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 595        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 596        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 597        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 598        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 599        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 600        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 601        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 602        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 603        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 604        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 605        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 606        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 607        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 608        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 609        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 610        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 611        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 612        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 613        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 614        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 615        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 616        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 617        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 618        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 619        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 620        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 621        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 622        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 623        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 624        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 625        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 626        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 627        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 628        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 629        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 630        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 631        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 632        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 633        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 634        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 635        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 636        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 637        exp.LogProperty: exp.Properties.Location.POST_NAME,
 638        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 639        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 640        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 641        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 642        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 643        exp.Order: exp.Properties.Location.POST_SCHEMA,
 644        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 645        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 646        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 647        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 648        exp.Property: exp.Properties.Location.POST_WITH,
 649        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 650        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 651        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 652        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 653        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 654        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 655        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 656        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 657        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 658        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 659        exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA,
 660        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 661        exp.Set: exp.Properties.Location.POST_SCHEMA,
 662        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 663        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 664        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 665        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 666        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 667        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 668        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 669        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 670        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 671        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 672        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 673        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 674        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 675        exp.Tags: exp.Properties.Location.POST_WITH,
 676        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 677        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 678        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 679        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 680        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 681        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 682        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 683        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 684        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 685        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 686        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 687        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 688        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 689        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 690        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 691    }
 692
 693    # Keywords that can't be used as unquoted identifier names
 694    RESERVED_KEYWORDS: t.Set[str] = set()
 695
 696    # Expressions whose comments are separated from them for better formatting
 697    WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 698        exp.Command,
 699        exp.Create,
 700        exp.Describe,
 701        exp.Delete,
 702        exp.Drop,
 703        exp.From,
 704        exp.Insert,
 705        exp.Join,
 706        exp.MultitableInserts,
 707        exp.Order,
 708        exp.Group,
 709        exp.Having,
 710        exp.Select,
 711        exp.SetOperation,
 712        exp.Update,
 713        exp.Where,
 714        exp.With,
 715    )
 716
 717    # Expressions that should not have their comments generated in maybe_comment
 718    EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 719        exp.Binary,
 720        exp.SetOperation,
 721    )
 722
 723    # Expressions that can remain unwrapped when appearing in the context of an INTERVAL
 724    UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = (
 725        exp.Column,
 726        exp.Literal,
 727        exp.Neg,
 728        exp.Paren,
 729    )
 730
 731    PARAMETERIZABLE_TEXT_TYPES = {
 732        exp.DataType.Type.NVARCHAR,
 733        exp.DataType.Type.VARCHAR,
 734        exp.DataType.Type.CHAR,
 735        exp.DataType.Type.NCHAR,
 736    }
 737
 738    # Expressions that need to have all CTEs under them bubbled up to them
 739    EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set()
 740
 741    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = ()
 742
 743    SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE
 744
 745    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 746
 747    __slots__ = (
 748        "pretty",
 749        "identify",
 750        "normalize",
 751        "pad",
 752        "_indent",
 753        "normalize_functions",
 754        "unsupported_level",
 755        "max_unsupported",
 756        "leading_comma",
 757        "max_text_width",
 758        "comments",
 759        "dialect",
 760        "unsupported_messages",
 761        "_escaped_quote_end",
 762        "_escaped_byte_quote_end",
 763        "_escaped_identifier_end",
 764        "_next_name",
 765        "_identifier_start",
 766        "_identifier_end",
 767        "_quote_json_path_key_using_brackets",
 768    )
 769
 770    def __init__(
 771        self,
 772        pretty: t.Optional[bool] = None,
 773        identify: str | bool = False,
 774        normalize: bool = False,
 775        pad: int = 2,
 776        indent: int = 2,
 777        normalize_functions: t.Optional[str | bool] = None,
 778        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 779        max_unsupported: int = 3,
 780        leading_comma: bool = False,
 781        max_text_width: int = 80,
 782        comments: bool = True,
 783        dialect: DialectType = None,
 784    ):
 785        import sqlglot
 786        from sqlglot.dialects import Dialect
 787
 788        self.pretty = pretty if pretty is not None else sqlglot.pretty
 789        self.identify = identify
 790        self.normalize = normalize
 791        self.pad = pad
 792        self._indent = indent
 793        self.unsupported_level = unsupported_level
 794        self.max_unsupported = max_unsupported
 795        self.leading_comma = leading_comma
 796        self.max_text_width = max_text_width
 797        self.comments = comments
 798        self.dialect = Dialect.get_or_raise(dialect)
 799
 800        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 801        self.normalize_functions = (
 802            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 803        )
 804
 805        self.unsupported_messages: t.List[str] = []
 806        self._escaped_quote_end: str = (
 807            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 808        )
 809        self._escaped_byte_quote_end: str = (
 810            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 811            if self.dialect.BYTE_END
 812            else ""
 813        )
 814        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 815
 816        self._next_name = name_sequence("_t")
 817
 818        self._identifier_start = self.dialect.IDENTIFIER_START
 819        self._identifier_end = self.dialect.IDENTIFIER_END
 820
 821        self._quote_json_path_key_using_brackets = True
 822
 823    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
 824        """
 825        Generates the SQL string corresponding to the given syntax tree.
 826
 827        Args:
 828            expression: The syntax tree.
 829            copy: Whether to copy the expression. The generator performs mutations so
 830                it is safer to copy.
 831
 832        Returns:
 833            The SQL string corresponding to `expression`.
 834        """
 835        if copy:
 836            expression = expression.copy()
 837
 838        expression = self.preprocess(expression)
 839
 840        self.unsupported_messages = []
 841        sql = self.sql(expression).strip()
 842
 843        if self.pretty:
 844            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 845
 846        if self.unsupported_level == ErrorLevel.IGNORE:
 847            return sql
 848
 849        if self.unsupported_level == ErrorLevel.WARN:
 850            for msg in self.unsupported_messages:
 851                logger.warning(msg)
 852        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 853            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 854
 855        return sql
 856
 857    def preprocess(self, expression: exp.Expression) -> exp.Expression:
 858        """Apply generic preprocessing transformations to a given expression."""
 859        expression = self._move_ctes_to_top_level(expression)
 860
 861        if self.ENSURE_BOOLS:
 862            from sqlglot.transforms import ensure_bools
 863
 864            expression = ensure_bools(expression)
 865
 866        return expression
 867
 868    def _move_ctes_to_top_level(self, expression: E) -> E:
 869        if (
 870            not expression.parent
 871            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 872            and any(node.parent is not expression for node in expression.find_all(exp.With))
 873        ):
 874            from sqlglot.transforms import move_ctes_to_top_level
 875
 876            expression = move_ctes_to_top_level(expression)
 877        return expression
 878
 879    def unsupported(self, message: str) -> None:
 880        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 881            raise UnsupportedError(message)
 882        self.unsupported_messages.append(message)
 883
 884    def sep(self, sep: str = " ") -> str:
 885        return f"{sep.strip()}\n" if self.pretty else sep
 886
 887    def seg(self, sql: str, sep: str = " ") -> str:
 888        return f"{self.sep(sep)}{sql}"
 889
 890    def sanitize_comment(self, comment: str) -> str:
 891        comment = " " + comment if comment[0].strip() else comment
 892        comment = comment + " " if comment[-1].strip() else comment
 893
 894        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
 895            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
 896            comment = comment.replace("*/", "* /")
 897
 898        return comment
 899
 900    def maybe_comment(
 901        self,
 902        sql: str,
 903        expression: t.Optional[exp.Expression] = None,
 904        comments: t.Optional[t.List[str]] = None,
 905        separated: bool = False,
 906    ) -> str:
 907        comments = (
 908            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 909            if self.comments
 910            else None
 911        )
 912
 913        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 914            return sql
 915
 916        comments_sql = " ".join(
 917            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
 918        )
 919
 920        if not comments_sql:
 921            return sql
 922
 923        comments_sql = self._replace_line_breaks(comments_sql)
 924
 925        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 926            return (
 927                f"{self.sep()}{comments_sql}{sql}"
 928                if not sql or sql[0].isspace()
 929                else f"{comments_sql}{self.sep()}{sql}"
 930            )
 931
 932        return f"{sql} {comments_sql}"
 933
 934    def wrap(self, expression: exp.Expression | str) -> str:
 935        this_sql = (
 936            self.sql(expression)
 937            if isinstance(expression, exp.UNWRAPPED_QUERIES)
 938            else self.sql(expression, "this")
 939        )
 940        if not this_sql:
 941            return "()"
 942
 943        this_sql = self.indent(this_sql, level=1, pad=0)
 944        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 945
 946    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 947        original = self.identify
 948        self.identify = False
 949        result = func(*args, **kwargs)
 950        self.identify = original
 951        return result
 952
 953    def normalize_func(self, name: str) -> str:
 954        if self.normalize_functions == "upper" or self.normalize_functions is True:
 955            return name.upper()
 956        if self.normalize_functions == "lower":
 957            return name.lower()
 958        return name
 959
 960    def indent(
 961        self,
 962        sql: str,
 963        level: int = 0,
 964        pad: t.Optional[int] = None,
 965        skip_first: bool = False,
 966        skip_last: bool = False,
 967    ) -> str:
 968        if not self.pretty or not sql:
 969            return sql
 970
 971        pad = self.pad if pad is None else pad
 972        lines = sql.split("\n")
 973
 974        return "\n".join(
 975            (
 976                line
 977                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 978                else f"{' ' * (level * self._indent + pad)}{line}"
 979            )
 980            for i, line in enumerate(lines)
 981        )
 982
 983    def sql(
 984        self,
 985        expression: t.Optional[str | exp.Expression],
 986        key: t.Optional[str] = None,
 987        comment: bool = True,
 988    ) -> str:
 989        if not expression:
 990            return ""
 991
 992        if isinstance(expression, str):
 993            return expression
 994
 995        if key:
 996            value = expression.args.get(key)
 997            if value:
 998                return self.sql(value)
 999            return ""
1000
1001        transform = self.TRANSFORMS.get(expression.__class__)
1002
1003        if transform:
1004            sql = transform(self, expression)
1005        else:
1006            exp_handler_name = expression.key + "_sql"
1007
1008            if handler := getattr(self, exp_handler_name, None):
1009                sql = handler(expression)
1010            elif isinstance(expression, exp.Func):
1011                sql = self.function_fallback_sql(expression)
1012            elif isinstance(expression, exp.Property):
1013                sql = self.property_sql(expression)
1014            else:
1015                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1016
1017        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1018
1019    def uncache_sql(self, expression: exp.Uncache) -> str:
1020        table = self.sql(expression, "this")
1021        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1022        return f"UNCACHE TABLE{exists_sql} {table}"
1023
1024    def cache_sql(self, expression: exp.Cache) -> str:
1025        lazy = " LAZY" if expression.args.get("lazy") else ""
1026        table = self.sql(expression, "this")
1027        options = expression.args.get("options")
1028        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1029        sql = self.sql(expression, "expression")
1030        sql = f" AS{self.sep()}{sql}" if sql else ""
1031        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1032        return self.prepend_ctes(expression, sql)
1033
1034    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1035        if isinstance(expression.parent, exp.Cast):
1036            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
1037        default = "DEFAULT " if expression.args.get("default") else ""
1038        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1039
1040    def column_parts(self, expression: exp.Column) -> str:
1041        return ".".join(
1042            self.sql(part)
1043            for part in (
1044                expression.args.get("catalog"),
1045                expression.args.get("db"),
1046                expression.args.get("table"),
1047                expression.args.get("this"),
1048            )
1049            if part
1050        )
1051
1052    def column_sql(self, expression: exp.Column) -> str:
1053        join_mark = " (+)" if expression.args.get("join_mark") else ""
1054
1055        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1056            join_mark = ""
1057            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1058
1059        return f"{self.column_parts(expression)}{join_mark}"
1060
1061    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1062        return self.column_sql(expression)
1063
1064    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1065        this = self.sql(expression, "this")
1066        this = f" {this}" if this else ""
1067        position = self.sql(expression, "position")
1068        return f"{position}{this}"
1069
1070    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1071        column = self.sql(expression, "this")
1072        kind = self.sql(expression, "kind")
1073        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1074        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1075        kind = f"{sep}{kind}" if kind else ""
1076        constraints = f" {constraints}" if constraints else ""
1077        position = self.sql(expression, "position")
1078        position = f" {position}" if position else ""
1079
1080        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1081            kind = ""
1082
1083        return f"{exists}{column}{kind}{constraints}{position}"
1084
1085    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1086        this = self.sql(expression, "this")
1087        kind_sql = self.sql(expression, "kind").strip()
1088        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1089
1090    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1091        this = self.sql(expression, "this")
1092        if expression.args.get("not_null"):
1093            persisted = " PERSISTED NOT NULL"
1094        elif expression.args.get("persisted"):
1095            persisted = " PERSISTED"
1096        else:
1097            persisted = ""
1098
1099        return f"AS {this}{persisted}"
1100
1101    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1102        return self.token_sql(TokenType.AUTO_INCREMENT)
1103
1104    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1105        if isinstance(expression.this, list):
1106            this = self.wrap(self.expressions(expression, key="this", flat=True))
1107        else:
1108            this = self.sql(expression, "this")
1109
1110        return f"COMPRESS {this}"
1111
1112    def generatedasidentitycolumnconstraint_sql(
1113        self, expression: exp.GeneratedAsIdentityColumnConstraint
1114    ) -> str:
1115        this = ""
1116        if expression.this is not None:
1117            on_null = " ON NULL" if expression.args.get("on_null") else ""
1118            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1119
1120        start = expression.args.get("start")
1121        start = f"START WITH {start}" if start else ""
1122        increment = expression.args.get("increment")
1123        increment = f" INCREMENT BY {increment}" if increment else ""
1124        minvalue = expression.args.get("minvalue")
1125        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1126        maxvalue = expression.args.get("maxvalue")
1127        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1128        cycle = expression.args.get("cycle")
1129        cycle_sql = ""
1130
1131        if cycle is not None:
1132            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1133            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1134
1135        sequence_opts = ""
1136        if start or increment or cycle_sql:
1137            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1138            sequence_opts = f" ({sequence_opts.strip()})"
1139
1140        expr = self.sql(expression, "expression")
1141        expr = f"({expr})" if expr else "IDENTITY"
1142
1143        return f"GENERATED{this} AS {expr}{sequence_opts}"
1144
1145    def generatedasrowcolumnconstraint_sql(
1146        self, expression: exp.GeneratedAsRowColumnConstraint
1147    ) -> str:
1148        start = "START" if expression.args.get("start") else "END"
1149        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1150        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1151
1152    def periodforsystemtimeconstraint_sql(
1153        self, expression: exp.PeriodForSystemTimeConstraint
1154    ) -> str:
1155        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1156
1157    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1158        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1159
1160    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1161        desc = expression.args.get("desc")
1162        if desc is not None:
1163            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1164        options = self.expressions(expression, key="options", flat=True, sep=" ")
1165        options = f" {options}" if options else ""
1166        return f"PRIMARY KEY{options}"
1167
1168    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1169        this = self.sql(expression, "this")
1170        this = f" {this}" if this else ""
1171        index_type = expression.args.get("index_type")
1172        index_type = f" USING {index_type}" if index_type else ""
1173        on_conflict = self.sql(expression, "on_conflict")
1174        on_conflict = f" {on_conflict}" if on_conflict else ""
1175        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1176        options = self.expressions(expression, key="options", flat=True, sep=" ")
1177        options = f" {options}" if options else ""
1178        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1179
1180    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1181        input_ = expression.args.get("input_")
1182        output = expression.args.get("output")
1183        variadic = expression.args.get("variadic")
1184
1185        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1186        if variadic:
1187            return "VARIADIC"
1188
1189        if input_ and output:
1190            return f"IN{self.INOUT_SEPARATOR}OUT"
1191        if input_:
1192            return "IN"
1193        if output:
1194            return "OUT"
1195
1196        return ""
1197
1198    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1199        return self.sql(expression, "this")
1200
1201    def create_sql(self, expression: exp.Create) -> str:
1202        kind = self.sql(expression, "kind")
1203        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1204
1205        properties = expression.args.get("properties")
1206
1207        if (
1208            kind == "TRIGGER"
1209            and properties
1210            and properties.expressions
1211            and isinstance(properties.expressions[0], exp.TriggerProperties)
1212            and properties.expressions[0].args.get("constraint")
1213        ):
1214            kind = f"CONSTRAINT {kind}"
1215
1216        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1217
1218        this = self.createable_sql(expression, properties_locs)
1219
1220        properties_sql = ""
1221        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1222            exp.Properties.Location.POST_WITH
1223        ):
1224            props_ast = exp.Properties(
1225                expressions=[
1226                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1227                    *properties_locs[exp.Properties.Location.POST_WITH],
1228                ]
1229            )
1230            props_ast.parent = expression
1231            properties_sql = self.sql(props_ast)
1232
1233            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1234                properties_sql = self.sep() + properties_sql
1235            elif not self.pretty:
1236                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1237                properties_sql = f" {properties_sql}"
1238
1239        begin = " BEGIN" if expression.args.get("begin") else ""
1240
1241        expression_sql = self.sql(expression, "expression")
1242        if expression_sql:
1243            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1244
1245            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1246                postalias_props_sql = ""
1247                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1248                    postalias_props_sql = self.properties(
1249                        exp.Properties(
1250                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1251                        ),
1252                        wrapped=False,
1253                    )
1254                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1255                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1256
1257        postindex_props_sql = ""
1258        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1259            postindex_props_sql = self.properties(
1260                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1261                wrapped=False,
1262                prefix=" ",
1263            )
1264
1265        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1266        indexes = f" {indexes}" if indexes else ""
1267        index_sql = indexes + postindex_props_sql
1268
1269        replace = " OR REPLACE" if expression.args.get("replace") else ""
1270        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1271        unique = " UNIQUE" if expression.args.get("unique") else ""
1272
1273        clustered = expression.args.get("clustered")
1274        if clustered is None:
1275            clustered_sql = ""
1276        elif clustered:
1277            clustered_sql = " CLUSTERED COLUMNSTORE"
1278        else:
1279            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1280
1281        postcreate_props_sql = ""
1282        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1283            postcreate_props_sql = self.properties(
1284                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1285                sep=" ",
1286                prefix=" ",
1287                wrapped=False,
1288            )
1289
1290        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1291
1292        postexpression_props_sql = ""
1293        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1294            postexpression_props_sql = self.properties(
1295                exp.Properties(
1296                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1297                ),
1298                sep=" ",
1299                prefix=" ",
1300                wrapped=False,
1301            )
1302
1303        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1304        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1305        no_schema_binding = (
1306            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1307        )
1308
1309        clone = self.sql(expression, "clone")
1310        clone = f" {clone}" if clone else ""
1311
1312        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1313            properties_expression = f"{expression_sql}{properties_sql}"
1314        else:
1315            properties_expression = f"{properties_sql}{expression_sql}"
1316
1317        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1318        return self.prepend_ctes(expression, expression_sql)
1319
1320    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1321        start = self.sql(expression, "start")
1322        start = f"START WITH {start}" if start else ""
1323        increment = self.sql(expression, "increment")
1324        increment = f" INCREMENT BY {increment}" if increment else ""
1325        minvalue = self.sql(expression, "minvalue")
1326        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1327        maxvalue = self.sql(expression, "maxvalue")
1328        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1329        owned = self.sql(expression, "owned")
1330        owned = f" OWNED BY {owned}" if owned else ""
1331
1332        cache = expression.args.get("cache")
1333        if cache is None:
1334            cache_str = ""
1335        elif cache is True:
1336            cache_str = " CACHE"
1337        else:
1338            cache_str = f" CACHE {cache}"
1339
1340        options = self.expressions(expression, key="options", flat=True, sep=" ")
1341        options = f" {options}" if options else ""
1342
1343        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1344
1345    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1346        timing = expression.args.get("timing", "")
1347        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1348        timing_events = f"{timing} {events}".strip() if timing or events else ""
1349
1350        parts = [timing_events, "ON", self.sql(expression, "table")]
1351
1352        if referenced_table := expression.args.get("referenced_table"):
1353            parts.extend(["FROM", self.sql(referenced_table)])
1354
1355        if deferrable := expression.args.get("deferrable"):
1356            parts.append(deferrable)
1357
1358        if initially := expression.args.get("initially"):
1359            parts.append(f"INITIALLY {initially}")
1360
1361        if referencing := expression.args.get("referencing"):
1362            parts.append(self.sql(referencing))
1363
1364        if for_each := expression.args.get("for_each"):
1365            parts.append(f"FOR EACH {for_each}")
1366
1367        if when := expression.args.get("when"):
1368            parts.append(f"WHEN ({self.sql(when)})")
1369
1370        parts.append(self.sql(expression, "execute"))
1371
1372        return self.sep().join(parts)
1373
1374    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1375        parts = []
1376
1377        if old_alias := expression.args.get("old"):
1378            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1379
1380        if new_alias := expression.args.get("new"):
1381            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1382
1383        return f"REFERENCING {' '.join(parts)}"
1384
1385    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1386        columns = expression.args.get("columns")
1387        if columns:
1388            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1389
1390        return self.sql(expression, "this")
1391
1392    def clone_sql(self, expression: exp.Clone) -> str:
1393        this = self.sql(expression, "this")
1394        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1395        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1396        return f"{shallow}{keyword} {this}"
1397
1398    def describe_sql(self, expression: exp.Describe) -> str:
1399        style = expression.args.get("style")
1400        style = f" {style}" if style else ""
1401        partition = self.sql(expression, "partition")
1402        partition = f" {partition}" if partition else ""
1403        format = self.sql(expression, "format")
1404        format = f" {format}" if format else ""
1405        as_json = " AS JSON" if expression.args.get("as_json") else ""
1406
1407        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1408
1409    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1410        tag = self.sql(expression, "tag")
1411        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1412
1413    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1414        with_ = self.sql(expression, "with_")
1415        if with_:
1416            sql = f"{with_}{self.sep()}{sql}"
1417        return sql
1418
1419    def with_sql(self, expression: exp.With) -> str:
1420        sql = self.expressions(expression, flat=True)
1421        recursive = (
1422            "RECURSIVE "
1423            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1424            else ""
1425        )
1426        search = self.sql(expression, "search")
1427        search = f" {search}" if search else ""
1428
1429        return f"WITH {recursive}{sql}{search}"
1430
1431    def cte_sql(self, expression: exp.CTE) -> str:
1432        alias = expression.args.get("alias")
1433        if alias:
1434            alias.add_comments(expression.pop_comments())
1435
1436        alias_sql = self.sql(expression, "alias")
1437
1438        materialized = expression.args.get("materialized")
1439        if materialized is False:
1440            materialized = "NOT MATERIALIZED "
1441        elif materialized:
1442            materialized = "MATERIALIZED "
1443
1444        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1445        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1446
1447        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1448
1449    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1450        alias = self.sql(expression, "this")
1451        columns = self.expressions(expression, key="columns", flat=True)
1452        columns = f"({columns})" if columns else ""
1453
1454        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1455            columns = ""
1456            self.unsupported("Named columns are not supported in table alias.")
1457
1458        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1459            alias = self._next_name()
1460
1461        return f"{alias}{columns}"
1462
1463    def bitstring_sql(self, expression: exp.BitString) -> str:
1464        this = self.sql(expression, "this")
1465        if self.dialect.BIT_START:
1466            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1467        return f"{int(this, 2)}"
1468
1469    def hexstring_sql(
1470        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1471    ) -> str:
1472        this = self.sql(expression, "this")
1473        is_integer_type = expression.args.get("is_integer")
1474
1475        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1476            not self.dialect.HEX_START and not binary_function_repr
1477        ):
1478            # Integer representation will be returned if:
1479            # - The read dialect treats the hex value as integer literal but not the write
1480            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1481            return f"{int(this, 16)}"
1482
1483        if not is_integer_type:
1484            # Read dialect treats the hex value as BINARY/BLOB
1485            if binary_function_repr:
1486                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1487                return self.func(binary_function_repr, exp.Literal.string(this))
1488            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1489                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1490                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1491
1492        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1493
1494    def bytestring_sql(self, expression: exp.ByteString) -> str:
1495        this = self.sql(expression, "this")
1496        if self.dialect.BYTE_START:
1497            escaped_byte_string = self.escape_str(
1498                this,
1499                escape_backslash=False,
1500                delimiter=self.dialect.BYTE_END,
1501                escaped_delimiter=self._escaped_byte_quote_end,
1502                is_byte_string=True,
1503            )
1504            is_bytes = expression.args.get("is_bytes", False)
1505            delimited_byte_string = (
1506                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1507            )
1508            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1509                return self.sql(
1510                    exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect)
1511                )
1512            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1513                return self.sql(
1514                    exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect)
1515                )
1516
1517            return delimited_byte_string
1518        return this
1519
1520    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1521        this = self.sql(expression, "this")
1522        escape = expression.args.get("escape")
1523
1524        if self.dialect.UNICODE_START:
1525            escape_substitute = r"\\\1"
1526            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1527        else:
1528            escape_substitute = r"\\u\1"
1529            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1530
1531        if escape:
1532            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1533            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1534        else:
1535            escape_pattern = ESCAPED_UNICODE_RE
1536            escape_sql = ""
1537
1538        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1539            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1540
1541        return f"{left_quote}{this}{right_quote}{escape_sql}"
1542
1543    def rawstring_sql(self, expression: exp.RawString) -> str:
1544        string = expression.this
1545        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1546            string = string.replace("\\", "\\\\")
1547
1548        string = self.escape_str(string, escape_backslash=False)
1549        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1550
1551    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1552        this = self.sql(expression, "this")
1553        specifier = self.sql(expression, "expression")
1554        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1555        return f"{this}{specifier}"
1556
1557    def datatype_sql(self, expression: exp.DataType) -> str:
1558        nested = ""
1559        values = ""
1560
1561        expr_nested = expression.args.get("nested")
1562        interior = (
1563            self.expressions(
1564                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1565            )
1566            if expr_nested and self.pretty
1567            else self.expressions(expression, flat=True)
1568        )
1569
1570        type_value = expression.this
1571        if type_value in self.UNSUPPORTED_TYPES:
1572            self.unsupported(
1573                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1574            )
1575
1576        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1577            type_sql = self.sql(expression, "kind")
1578        else:
1579            type_sql = (
1580                self.TYPE_MAPPING.get(type_value, type_value.value)
1581                if isinstance(type_value, exp.DataType.Type)
1582                else type_value
1583            )
1584
1585        if interior:
1586            if expr_nested:
1587                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1588                if expression.args.get("values") is not None:
1589                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1590                    values = self.expressions(expression, key="values", flat=True)
1591                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1592            elif type_value == exp.DataType.Type.INTERVAL:
1593                nested = f" {interior}"
1594            else:
1595                nested = f"({interior})"
1596
1597        type_sql = f"{type_sql}{nested}{values}"
1598        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1599            exp.DataType.Type.TIMETZ,
1600            exp.DataType.Type.TIMESTAMPTZ,
1601        ):
1602            type_sql = f"{type_sql} WITH TIME ZONE"
1603
1604        return type_sql
1605
1606    def directory_sql(self, expression: exp.Directory) -> str:
1607        local = "LOCAL " if expression.args.get("local") else ""
1608        row_format = self.sql(expression, "row_format")
1609        row_format = f" {row_format}" if row_format else ""
1610        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1611
1612    def delete_sql(self, expression: exp.Delete) -> str:
1613        this = self.sql(expression, "this")
1614        this = f" FROM {this}" if this else ""
1615        using = self.expressions(expression, key="using")
1616        using = f" USING {using}" if using else ""
1617        cluster = self.sql(expression, "cluster")
1618        cluster = f" {cluster}" if cluster else ""
1619        where = self.sql(expression, "where")
1620        returning = self.sql(expression, "returning")
1621        order = self.sql(expression, "order")
1622        limit = self.sql(expression, "limit")
1623        tables = self.expressions(expression, key="tables")
1624        tables = f" {tables}" if tables else ""
1625        if self.RETURNING_END:
1626            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1627        else:
1628            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1629        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1630
1631    def drop_sql(self, expression: exp.Drop) -> str:
1632        this = self.sql(expression, "this")
1633        expressions = self.expressions(expression, flat=True)
1634        expressions = f" ({expressions})" if expressions else ""
1635        kind = expression.args["kind"]
1636        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1637        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1638        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1639        on_cluster = self.sql(expression, "cluster")
1640        on_cluster = f" {on_cluster}" if on_cluster else ""
1641        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1642        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1643        cascade = " CASCADE" if expression.args.get("cascade") else ""
1644        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1645        purge = " PURGE" if expression.args.get("purge") else ""
1646        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1647
1648    def set_operation(self, expression: exp.SetOperation) -> str:
1649        op_type = type(expression)
1650        op_name = op_type.key.upper()
1651
1652        distinct = expression.args.get("distinct")
1653        if (
1654            distinct is False
1655            and op_type in (exp.Except, exp.Intersect)
1656            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1657        ):
1658            self.unsupported(f"{op_name} ALL is not supported")
1659
1660        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1661
1662        if distinct is None:
1663            distinct = default_distinct
1664            if distinct is None:
1665                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1666
1667        if distinct is default_distinct:
1668            distinct_or_all = ""
1669        else:
1670            distinct_or_all = " DISTINCT" if distinct else " ALL"
1671
1672        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1673        side_kind = f"{side_kind} " if side_kind else ""
1674
1675        by_name = " BY NAME" if expression.args.get("by_name") else ""
1676        on = self.expressions(expression, key="on", flat=True)
1677        on = f" ON ({on})" if on else ""
1678
1679        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1680
1681    def set_operations(self, expression: exp.SetOperation) -> str:
1682        if not self.SET_OP_MODIFIERS:
1683            limit = expression.args.get("limit")
1684            order = expression.args.get("order")
1685
1686            if limit or order:
1687                select = self._move_ctes_to_top_level(
1688                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1689                )
1690
1691                if limit:
1692                    select = select.limit(limit.pop(), copy=False)
1693                if order:
1694                    select = select.order_by(order.pop(), copy=False)
1695                return self.sql(select)
1696
1697        sqls: t.List[str] = []
1698        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1699
1700        while stack:
1701            node = stack.pop()
1702
1703            if isinstance(node, exp.SetOperation):
1704                stack.append(node.expression)
1705                stack.append(
1706                    self.maybe_comment(
1707                        self.set_operation(node), comments=node.comments, separated=True
1708                    )
1709                )
1710                stack.append(node.this)
1711            else:
1712                sqls.append(self.sql(node))
1713
1714        this = self.sep().join(sqls)
1715        this = self.query_modifiers(expression, this)
1716        return self.prepend_ctes(expression, this)
1717
1718    def fetch_sql(self, expression: exp.Fetch) -> str:
1719        direction = expression.args.get("direction")
1720        direction = f" {direction}" if direction else ""
1721        count = self.sql(expression, "count")
1722        count = f" {count}" if count else ""
1723        limit_options = self.sql(expression, "limit_options")
1724        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1725        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1726
1727    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1728        percent = " PERCENT" if expression.args.get("percent") else ""
1729        rows = " ROWS" if expression.args.get("rows") else ""
1730        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1731        if not with_ties and rows:
1732            with_ties = " ONLY"
1733        return f"{percent}{rows}{with_ties}"
1734
1735    def filter_sql(self, expression: exp.Filter) -> str:
1736        if self.AGGREGATE_FILTER_SUPPORTED:
1737            this = self.sql(expression, "this")
1738            where = self.sql(expression, "expression").strip()
1739            return f"{this} FILTER({where})"
1740
1741        agg = expression.this
1742        agg_arg = agg.this
1743        cond = expression.expression.this
1744        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1745        return self.sql(agg)
1746
1747    def hint_sql(self, expression: exp.Hint) -> str:
1748        if not self.QUERY_HINTS:
1749            self.unsupported("Hints are not supported")
1750            return ""
1751
1752        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1753
1754    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1755        using = self.sql(expression, "using")
1756        using = f" USING {using}" if using else ""
1757        columns = self.expressions(expression, key="columns", flat=True)
1758        columns = f"({columns})" if columns else ""
1759        partition_by = self.expressions(expression, key="partition_by", flat=True)
1760        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1761        where = self.sql(expression, "where")
1762        include = self.expressions(expression, key="include", flat=True)
1763        if include:
1764            include = f" INCLUDE ({include})"
1765        with_storage = self.expressions(expression, key="with_storage", flat=True)
1766        with_storage = f" WITH ({with_storage})" if with_storage else ""
1767        tablespace = self.sql(expression, "tablespace")
1768        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1769        on = self.sql(expression, "on")
1770        on = f" ON {on}" if on else ""
1771
1772        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1773
1774    def index_sql(self, expression: exp.Index) -> str:
1775        unique = "UNIQUE " if expression.args.get("unique") else ""
1776        primary = "PRIMARY " if expression.args.get("primary") else ""
1777        amp = "AMP " if expression.args.get("amp") else ""
1778        name = self.sql(expression, "this")
1779        name = f"{name} " if name else ""
1780        table = self.sql(expression, "table")
1781        table = f"{self.INDEX_ON} {table}" if table else ""
1782
1783        index = "INDEX " if not table else ""
1784
1785        params = self.sql(expression, "params")
1786        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1787
1788    def identifier_sql(self, expression: exp.Identifier) -> str:
1789        text = expression.name
1790        lower = text.lower()
1791        quoted = expression.quoted
1792        text = lower if self.normalize and not quoted else text
1793        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1794        if (
1795            quoted
1796            or self.dialect.can_quote(expression, self.identify)
1797            or lower in self.RESERVED_KEYWORDS
1798            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1799        ):
1800            text = f"{self._identifier_start}{text}{self._identifier_end}"
1801        return text
1802
1803    def hex_sql(self, expression: exp.Hex) -> str:
1804        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1805        if self.dialect.HEX_LOWERCASE:
1806            text = self.func("LOWER", text)
1807
1808        return text
1809
1810    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1811        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1812        if not self.dialect.HEX_LOWERCASE:
1813            text = self.func("LOWER", text)
1814        return text
1815
1816    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1817        input_format = self.sql(expression, "input_format")
1818        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1819        output_format = self.sql(expression, "output_format")
1820        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1821        return self.sep().join((input_format, output_format))
1822
1823    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1824        string = self.sql(exp.Literal.string(expression.name))
1825        return f"{prefix}{string}"
1826
1827    def partition_sql(self, expression: exp.Partition) -> str:
1828        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1829        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1830
1831    def properties_sql(self, expression: exp.Properties) -> str:
1832        root_properties = []
1833        with_properties = []
1834
1835        for p in expression.expressions:
1836            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1837            if p_loc == exp.Properties.Location.POST_WITH:
1838                with_properties.append(p)
1839            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1840                root_properties.append(p)
1841
1842        root_props_ast = exp.Properties(expressions=root_properties)
1843        root_props_ast.parent = expression.parent
1844
1845        with_props_ast = exp.Properties(expressions=with_properties)
1846        with_props_ast.parent = expression.parent
1847
1848        root_props = self.root_properties(root_props_ast)
1849        with_props = self.with_properties(with_props_ast)
1850
1851        if root_props and with_props and not self.pretty:
1852            with_props = " " + with_props
1853
1854        return root_props + with_props
1855
1856    def root_properties(self, properties: exp.Properties) -> str:
1857        if properties.expressions:
1858            return self.expressions(properties, indent=False, sep=" ")
1859        return ""
1860
1861    def properties(
1862        self,
1863        properties: exp.Properties,
1864        prefix: str = "",
1865        sep: str = ", ",
1866        suffix: str = "",
1867        wrapped: bool = True,
1868    ) -> str:
1869        if properties.expressions:
1870            expressions = self.expressions(properties, sep=sep, indent=False)
1871            if expressions:
1872                expressions = self.wrap(expressions) if wrapped else expressions
1873                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1874        return ""
1875
1876    def with_properties(self, properties: exp.Properties) -> str:
1877        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1878
1879    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1880        properties_locs = defaultdict(list)
1881        for p in properties.expressions:
1882            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1883            if p_loc != exp.Properties.Location.UNSUPPORTED:
1884                properties_locs[p_loc].append(p)
1885            else:
1886                self.unsupported(f"Unsupported property {p.key}")
1887
1888        return properties_locs
1889
1890    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1891        if isinstance(expression.this, exp.Dot):
1892            return self.sql(expression, "this")
1893        return f"'{expression.name}'" if string_key else expression.name
1894
1895    def property_sql(self, expression: exp.Property) -> str:
1896        property_cls = expression.__class__
1897        if property_cls == exp.Property:
1898            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1899
1900        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1901        if not property_name:
1902            self.unsupported(f"Unsupported property {expression.key}")
1903
1904        return f"{property_name}={self.sql(expression, 'this')}"
1905
1906    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1907        if self.SUPPORTS_CREATE_TABLE_LIKE:
1908            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1909            options = f" {options}" if options else ""
1910
1911            like = f"LIKE {self.sql(expression, 'this')}{options}"
1912            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1913                like = f"({like})"
1914
1915            return like
1916
1917        if expression.expressions:
1918            self.unsupported("Transpilation of LIKE property options is unsupported")
1919
1920        select = exp.select("*").from_(expression.this).limit(0)
1921        return f"AS {self.sql(select)}"
1922
1923    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1924        no = "NO " if expression.args.get("no") else ""
1925        protection = " PROTECTION" if expression.args.get("protection") else ""
1926        return f"{no}FALLBACK{protection}"
1927
1928    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1929        no = "NO " if expression.args.get("no") else ""
1930        local = expression.args.get("local")
1931        local = f"{local} " if local else ""
1932        dual = "DUAL " if expression.args.get("dual") else ""
1933        before = "BEFORE " if expression.args.get("before") else ""
1934        after = "AFTER " if expression.args.get("after") else ""
1935        return f"{no}{local}{dual}{before}{after}JOURNAL"
1936
1937    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1938        freespace = self.sql(expression, "this")
1939        percent = " PERCENT" if expression.args.get("percent") else ""
1940        return f"FREESPACE={freespace}{percent}"
1941
1942    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1943        if expression.args.get("default"):
1944            property = "DEFAULT"
1945        elif expression.args.get("on"):
1946            property = "ON"
1947        else:
1948            property = "OFF"
1949        return f"CHECKSUM={property}"
1950
1951    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1952        if expression.args.get("no"):
1953            return "NO MERGEBLOCKRATIO"
1954        if expression.args.get("default"):
1955            return "DEFAULT MERGEBLOCKRATIO"
1956
1957        percent = " PERCENT" if expression.args.get("percent") else ""
1958        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1959
1960    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1961        default = expression.args.get("default")
1962        minimum = expression.args.get("minimum")
1963        maximum = expression.args.get("maximum")
1964        if default or minimum or maximum:
1965            if default:
1966                prop = "DEFAULT"
1967            elif minimum:
1968                prop = "MINIMUM"
1969            else:
1970                prop = "MAXIMUM"
1971            return f"{prop} DATABLOCKSIZE"
1972        units = expression.args.get("units")
1973        units = f" {units}" if units else ""
1974        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
1975
1976    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1977        autotemp = expression.args.get("autotemp")
1978        always = expression.args.get("always")
1979        default = expression.args.get("default")
1980        manual = expression.args.get("manual")
1981        never = expression.args.get("never")
1982
1983        if autotemp is not None:
1984            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1985        elif always:
1986            prop = "ALWAYS"
1987        elif default:
1988            prop = "DEFAULT"
1989        elif manual:
1990            prop = "MANUAL"
1991        elif never:
1992            prop = "NEVER"
1993        return f"BLOCKCOMPRESSION={prop}"
1994
1995    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1996        no = expression.args.get("no")
1997        no = " NO" if no else ""
1998        concurrent = expression.args.get("concurrent")
1999        concurrent = " CONCURRENT" if concurrent else ""
2000        target = self.sql(expression, "target")
2001        target = f" {target}" if target else ""
2002        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2003
2004    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2005        if isinstance(expression.this, list):
2006            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2007        if expression.this:
2008            modulus = self.sql(expression, "this")
2009            remainder = self.sql(expression, "expression")
2010            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2011
2012        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2013        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2014        return f"FROM ({from_expressions}) TO ({to_expressions})"
2015
2016    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2017        this = self.sql(expression, "this")
2018
2019        for_values_or_default = expression.expression
2020        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2021            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2022        else:
2023            for_values_or_default = " DEFAULT"
2024
2025        return f"PARTITION OF {this}{for_values_or_default}"
2026
2027    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2028        kind = expression.args.get("kind")
2029        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2030        for_or_in = expression.args.get("for_or_in")
2031        for_or_in = f" {for_or_in}" if for_or_in else ""
2032        lock_type = expression.args.get("lock_type")
2033        override = " OVERRIDE" if expression.args.get("override") else ""
2034        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2035
2036    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2037        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2038        statistics = expression.args.get("statistics")
2039        statistics_sql = ""
2040        if statistics is not None:
2041            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2042        return f"{data_sql}{statistics_sql}"
2043
2044    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2045        this = self.sql(expression, "this")
2046        this = f"HISTORY_TABLE={this}" if this else ""
2047        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
2048        data_consistency = (
2049            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2050        )
2051        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
2052        retention_period = (
2053            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2054        )
2055
2056        if this:
2057            on_sql = self.func("ON", this, data_consistency, retention_period)
2058        else:
2059            on_sql = "ON" if expression.args.get("on") else "OFF"
2060
2061        sql = f"SYSTEM_VERSIONING={on_sql}"
2062
2063        return f"WITH({sql})" if expression.args.get("with_") else sql
2064
2065    def insert_sql(self, expression: exp.Insert) -> str:
2066        hint = self.sql(expression, "hint")
2067        overwrite = expression.args.get("overwrite")
2068
2069        if isinstance(expression.this, exp.Directory):
2070            this = " OVERWRITE" if overwrite else " INTO"
2071        else:
2072            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2073
2074        stored = self.sql(expression, "stored")
2075        stored = f" {stored}" if stored else ""
2076        alternative = expression.args.get("alternative")
2077        alternative = f" OR {alternative}" if alternative else ""
2078        ignore = " IGNORE" if expression.args.get("ignore") else ""
2079        is_function = expression.args.get("is_function")
2080        if is_function:
2081            this = f"{this} FUNCTION"
2082        this = f"{this} {self.sql(expression, 'this')}"
2083
2084        exists = " IF EXISTS" if expression.args.get("exists") else ""
2085        where = self.sql(expression, "where")
2086        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2087        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2088        on_conflict = self.sql(expression, "conflict")
2089        on_conflict = f" {on_conflict}" if on_conflict else ""
2090        by_name = " BY NAME" if expression.args.get("by_name") else ""
2091        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2092        returning = self.sql(expression, "returning")
2093
2094        if self.RETURNING_END:
2095            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2096        else:
2097            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2098
2099        partition_by = self.sql(expression, "partition")
2100        partition_by = f" {partition_by}" if partition_by else ""
2101        settings = self.sql(expression, "settings")
2102        settings = f" {settings}" if settings else ""
2103
2104        source = self.sql(expression, "source")
2105        source = f"TABLE {source}" if source else ""
2106
2107        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2108        return self.prepend_ctes(expression, sql)
2109
2110    def introducer_sql(self, expression: exp.Introducer) -> str:
2111        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2112
2113    def kill_sql(self, expression: exp.Kill) -> str:
2114        kind = self.sql(expression, "kind")
2115        kind = f" {kind}" if kind else ""
2116        this = self.sql(expression, "this")
2117        this = f" {this}" if this else ""
2118        return f"KILL{kind}{this}"
2119
2120    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2121        return expression.name
2122
2123    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2124        return expression.name
2125
2126    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2127        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2128
2129        constraint = self.sql(expression, "constraint")
2130        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2131
2132        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2133        if conflict_keys:
2134            conflict_keys = f"({conflict_keys})"
2135
2136        index_predicate = self.sql(expression, "index_predicate")
2137        conflict_keys = f"{conflict_keys}{index_predicate} "
2138
2139        action = self.sql(expression, "action")
2140
2141        expressions = self.expressions(expression, flat=True)
2142        if expressions:
2143            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2144            expressions = f" {set_keyword}{expressions}"
2145
2146        where = self.sql(expression, "where")
2147        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2148
2149    def returning_sql(self, expression: exp.Returning) -> str:
2150        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2151
2152    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2153        fields = self.sql(expression, "fields")
2154        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2155        escaped = self.sql(expression, "escaped")
2156        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2157        items = self.sql(expression, "collection_items")
2158        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2159        keys = self.sql(expression, "map_keys")
2160        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2161        lines = self.sql(expression, "lines")
2162        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2163        null = self.sql(expression, "null")
2164        null = f" NULL DEFINED AS {null}" if null else ""
2165        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2166
2167    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2168        return f"WITH ({self.expressions(expression, flat=True)})"
2169
2170    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2171        this = f"{self.sql(expression, 'this')} INDEX"
2172        target = self.sql(expression, "target")
2173        target = f" FOR {target}" if target else ""
2174        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2175
2176    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2177        this = self.sql(expression, "this")
2178        kind = self.sql(expression, "kind")
2179        expr = self.sql(expression, "expression")
2180        return f"{this} ({kind} => {expr})"
2181
2182    def table_parts(self, expression: exp.Table) -> str:
2183        return ".".join(
2184            self.sql(part)
2185            for part in (
2186                expression.args.get("catalog"),
2187                expression.args.get("db"),
2188                expression.args.get("this"),
2189            )
2190            if part is not None
2191        )
2192
2193    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2194        table = self.table_parts(expression)
2195        only = "ONLY " if expression.args.get("only") else ""
2196        partition = self.sql(expression, "partition")
2197        partition = f" {partition}" if partition else ""
2198        version = self.sql(expression, "version")
2199        version = f" {version}" if version else ""
2200        alias = self.sql(expression, "alias")
2201        alias = f"{sep}{alias}" if alias else ""
2202
2203        sample = self.sql(expression, "sample")
2204        post_alias = ""
2205        pre_alias = ""
2206
2207        if self.dialect.ALIAS_POST_TABLESAMPLE:
2208            pre_alias = sample
2209        else:
2210            post_alias = sample
2211
2212        if self.dialect.ALIAS_POST_VERSION:
2213            pre_alias = f"{pre_alias}{version}"
2214        else:
2215            post_alias = f"{post_alias}{version}"
2216
2217        hints = self.expressions(expression, key="hints", sep=" ")
2218        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2219        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2220        joins = self.indent(
2221            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2222        )
2223        laterals = self.expressions(expression, key="laterals", sep="")
2224
2225        file_format = self.sql(expression, "format")
2226        if file_format:
2227            pattern = self.sql(expression, "pattern")
2228            pattern = f", PATTERN => {pattern}" if pattern else ""
2229            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2230
2231        ordinality = expression.args.get("ordinality") or ""
2232        if ordinality:
2233            ordinality = f" WITH ORDINALITY{alias}"
2234            alias = ""
2235
2236        when = self.sql(expression, "when")
2237        if when:
2238            table = f"{table} {when}"
2239
2240        changes = self.sql(expression, "changes")
2241        changes = f" {changes}" if changes else ""
2242
2243        rows_from = self.expressions(expression, key="rows_from")
2244        if rows_from:
2245            table = f"ROWS FROM {self.wrap(rows_from)}"
2246
2247        indexed = expression.args.get("indexed")
2248        if indexed is not None:
2249            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2250        else:
2251            indexed = ""
2252
2253        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2254
2255    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2256        table = self.func("TABLE", expression.this)
2257        alias = self.sql(expression, "alias")
2258        alias = f" AS {alias}" if alias else ""
2259        sample = self.sql(expression, "sample")
2260        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2261        joins = self.indent(
2262            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2263        )
2264        return f"{table}{alias}{pivots}{sample}{joins}"
2265
2266    def tablesample_sql(
2267        self,
2268        expression: exp.TableSample,
2269        tablesample_keyword: t.Optional[str] = None,
2270    ) -> str:
2271        method = self.sql(expression, "method")
2272        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2273        numerator = self.sql(expression, "bucket_numerator")
2274        denominator = self.sql(expression, "bucket_denominator")
2275        field = self.sql(expression, "bucket_field")
2276        field = f" ON {field}" if field else ""
2277        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2278        seed = self.sql(expression, "seed")
2279        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2280
2281        size = self.sql(expression, "size")
2282        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2283            size = f"{size} ROWS"
2284
2285        percent = self.sql(expression, "percent")
2286        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2287            percent = f"{percent} PERCENT"
2288
2289        expr = f"{bucket}{percent}{size}"
2290        if self.TABLESAMPLE_REQUIRES_PARENS:
2291            expr = f"({expr})"
2292
2293        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2294
2295    def pivot_sql(self, expression: exp.Pivot) -> str:
2296        expressions = self.expressions(expression, flat=True)
2297        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2298
2299        group = self.sql(expression, "group")
2300
2301        if expression.this:
2302            this = self.sql(expression, "this")
2303            if not expressions:
2304                sql = f"UNPIVOT {this}"
2305            else:
2306                on = f"{self.seg('ON')} {expressions}"
2307                into = self.sql(expression, "into")
2308                into = f"{self.seg('INTO')} {into}" if into else ""
2309                using = self.expressions(expression, key="using", flat=True)
2310                using = f"{self.seg('USING')} {using}" if using else ""
2311                sql = f"{direction} {this}{on}{into}{using}{group}"
2312            return self.prepend_ctes(expression, sql)
2313
2314        alias = self.sql(expression, "alias")
2315        alias = f" AS {alias}" if alias else ""
2316
2317        fields = self.expressions(
2318            expression,
2319            "fields",
2320            sep=" ",
2321            dynamic=True,
2322            new_line=True,
2323            skip_first=True,
2324            skip_last=True,
2325        )
2326
2327        include_nulls = expression.args.get("include_nulls")
2328        if include_nulls is not None:
2329            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2330        else:
2331            nulls = ""
2332
2333        default_on_null = self.sql(expression, "default_on_null")
2334        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2335        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2336        return self.prepend_ctes(expression, sql)
2337
2338    def version_sql(self, expression: exp.Version) -> str:
2339        this = f"FOR {expression.name}"
2340        kind = expression.text("kind")
2341        expr = self.sql(expression, "expression")
2342        return f"{this} {kind} {expr}"
2343
2344    def tuple_sql(self, expression: exp.Tuple) -> str:
2345        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2346
2347    def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]:
2348        """
2349        Returns (join_sql, from_sql) for UPDATE statements.
2350        - join_sql: placed after UPDATE table, before SET
2351        - from_sql: placed after SET clause (standard position)
2352        Dialects like MySQL need to convert FROM to JOIN syntax.
2353        """
2354        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2355            return ("", self.sql(expression, "from_"))
2356
2357        # Qualify unqualified columns in SET clause with the target table
2358        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2359        target_table = expression.this
2360        if isinstance(target_table, exp.Table):
2361            target_name = exp.to_identifier(target_table.alias_or_name)
2362            for eq in expression.expressions:
2363                col = eq.this
2364                if isinstance(col, exp.Column) and not col.table:
2365                    col.set("table", target_name)
2366
2367        table = from_expr.this
2368        if nested_joins := table.args.get("joins", []):
2369            table.set("joins", None)
2370
2371        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2372        for nested in nested_joins:
2373            if not nested.args.get("on") and not nested.args.get("using"):
2374                nested.set("on", exp.true())
2375            join_sql += self.sql(nested)
2376
2377        return (join_sql, "")
2378
2379    def update_sql(self, expression: exp.Update) -> str:
2380        this = self.sql(expression, "this")
2381        join_sql, from_sql = self._update_from_joins_sql(expression)
2382        set_sql = self.expressions(expression, flat=True)
2383        where_sql = self.sql(expression, "where")
2384        returning = self.sql(expression, "returning")
2385        order = self.sql(expression, "order")
2386        limit = self.sql(expression, "limit")
2387        if self.RETURNING_END:
2388            expression_sql = f"{from_sql}{where_sql}{returning}"
2389        else:
2390            expression_sql = f"{returning}{from_sql}{where_sql}"
2391        options = self.expressions(expression, key="options")
2392        options = f" OPTION({options})" if options else ""
2393        sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2394        return self.prepend_ctes(expression, sql)
2395
2396    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2397        values_as_table = values_as_table and self.VALUES_AS_TABLE
2398
2399        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2400        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2401            args = self.expressions(expression)
2402            alias = self.sql(expression, "alias")
2403            values = f"VALUES{self.seg('')}{args}"
2404            values = (
2405                f"({values})"
2406                if self.WRAP_DERIVED_VALUES
2407                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2408                else values
2409            )
2410            values = self.query_modifiers(expression, values)
2411            return f"{values} AS {alias}" if alias else values
2412
2413        # Converts `VALUES...` expression into a series of select unions.
2414        alias_node = expression.args.get("alias")
2415        column_names = alias_node and alias_node.columns
2416
2417        selects: t.List[exp.Query] = []
2418
2419        for i, tup in enumerate(expression.expressions):
2420            row = tup.expressions
2421
2422            if i == 0 and column_names:
2423                row = [
2424                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2425                ]
2426
2427            selects.append(exp.Select(expressions=row))
2428
2429        if self.pretty:
2430            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2431            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2432            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2433            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2434            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2435
2436        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2437        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2438        return f"({unions}){alias}"
2439
2440    def var_sql(self, expression: exp.Var) -> str:
2441        return self.sql(expression, "this")
2442
2443    @unsupported_args("expressions")
2444    def into_sql(self, expression: exp.Into) -> str:
2445        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2446        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2447        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2448
2449    def from_sql(self, expression: exp.From) -> str:
2450        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2451
2452    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2453        grouping_sets = self.expressions(expression, indent=False)
2454        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2455
2456    def rollup_sql(self, expression: exp.Rollup) -> str:
2457        expressions = self.expressions(expression, indent=False)
2458        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2459
2460    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2461        this = self.sql(expression, "this")
2462
2463        columns = self.expressions(expression, flat=True)
2464
2465        from_sql = self.sql(expression, "from_index")
2466        from_sql = f" FROM {from_sql}" if from_sql else ""
2467
2468        properties = expression.args.get("properties")
2469        properties_sql = (
2470            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2471        )
2472
2473        return f"{this}({columns}){from_sql}{properties_sql}"
2474
2475    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2476        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2477
2478    def cube_sql(self, expression: exp.Cube) -> str:
2479        expressions = self.expressions(expression, indent=False)
2480        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2481
2482    def group_sql(self, expression: exp.Group) -> str:
2483        group_by_all = expression.args.get("all")
2484        if group_by_all is True:
2485            modifier = " ALL"
2486        elif group_by_all is False:
2487            modifier = " DISTINCT"
2488        else:
2489            modifier = ""
2490
2491        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2492
2493        grouping_sets = self.expressions(expression, key="grouping_sets")
2494        cube = self.expressions(expression, key="cube")
2495        rollup = self.expressions(expression, key="rollup")
2496
2497        groupings = csv(
2498            self.seg(grouping_sets) if grouping_sets else "",
2499            self.seg(cube) if cube else "",
2500            self.seg(rollup) if rollup else "",
2501            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2502            sep=self.GROUPINGS_SEP,
2503        )
2504
2505        if (
2506            expression.expressions
2507            and groupings
2508            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2509        ):
2510            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2511
2512        return f"{group_by}{groupings}"
2513
2514    def having_sql(self, expression: exp.Having) -> str:
2515        this = self.indent(self.sql(expression, "this"))
2516        return f"{self.seg('HAVING')}{self.sep()}{this}"
2517
2518    def connect_sql(self, expression: exp.Connect) -> str:
2519        start = self.sql(expression, "start")
2520        start = self.seg(f"START WITH {start}") if start else ""
2521        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2522        connect = self.sql(expression, "connect")
2523        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2524        return start + connect
2525
2526    def prior_sql(self, expression: exp.Prior) -> str:
2527        return f"PRIOR {self.sql(expression, 'this')}"
2528
2529    def join_sql(self, expression: exp.Join) -> str:
2530        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2531            side = None
2532        else:
2533            side = expression.side
2534
2535        op_sql = " ".join(
2536            op
2537            for op in (
2538                expression.method,
2539                "GLOBAL" if expression.args.get("global_") else None,
2540                side,
2541                expression.kind,
2542                expression.hint if self.JOIN_HINTS else None,
2543                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2544            )
2545            if op
2546        )
2547        match_cond = self.sql(expression, "match_condition")
2548        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2549        on_sql = self.sql(expression, "on")
2550        using = expression.args.get("using")
2551
2552        if not on_sql and using:
2553            on_sql = csv(*(self.sql(column) for column in using))
2554
2555        this = expression.this
2556        this_sql = self.sql(this)
2557
2558        exprs = self.expressions(expression)
2559        if exprs:
2560            this_sql = f"{this_sql},{self.seg(exprs)}"
2561
2562        if on_sql:
2563            on_sql = self.indent(on_sql, skip_first=True)
2564            space = self.seg(" " * self.pad) if self.pretty else " "
2565            if using:
2566                on_sql = f"{space}USING ({on_sql})"
2567            else:
2568                on_sql = f"{space}ON {on_sql}"
2569        elif not op_sql:
2570            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2571                return f" {this_sql}"
2572
2573            return f", {this_sql}"
2574
2575        if op_sql != "STRAIGHT_JOIN":
2576            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2577
2578        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2579        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2580
2581    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2582        args = self.expressions(expression, flat=True)
2583        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2584        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2585
2586    def lateral_op(self, expression: exp.Lateral) -> str:
2587        cross_apply = expression.args.get("cross_apply")
2588
2589        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2590        if cross_apply is True:
2591            op = "INNER JOIN "
2592        elif cross_apply is False:
2593            op = "LEFT JOIN "
2594        else:
2595            op = ""
2596
2597        return f"{op}LATERAL"
2598
2599    def lateral_sql(self, expression: exp.Lateral) -> str:
2600        this = self.sql(expression, "this")
2601
2602        if expression.args.get("view"):
2603            alias = expression.args["alias"]
2604            columns = self.expressions(alias, key="columns", flat=True)
2605            table = f" {alias.name}" if alias.name else ""
2606            columns = f" AS {columns}" if columns else ""
2607            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2608            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2609
2610        alias = self.sql(expression, "alias")
2611        alias = f" AS {alias}" if alias else ""
2612
2613        ordinality = expression.args.get("ordinality") or ""
2614        if ordinality:
2615            ordinality = f" WITH ORDINALITY{alias}"
2616            alias = ""
2617
2618        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2619
2620    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2621        this = self.sql(expression, "this")
2622
2623        args = [
2624            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2625            for e in (expression.args.get(k) for k in ("offset", "expression"))
2626            if e
2627        ]
2628
2629        args_sql = ", ".join(self.sql(e) for e in args)
2630        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2631        expressions = self.expressions(expression, flat=True)
2632        limit_options = self.sql(expression, "limit_options")
2633        expressions = f" BY {expressions}" if expressions else ""
2634
2635        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2636
2637    def offset_sql(self, expression: exp.Offset) -> str:
2638        this = self.sql(expression, "this")
2639        value = expression.expression
2640        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2641        expressions = self.expressions(expression, flat=True)
2642        expressions = f" BY {expressions}" if expressions else ""
2643        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2644
2645    def setitem_sql(self, expression: exp.SetItem) -> str:
2646        kind = self.sql(expression, "kind")
2647        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2648            kind = ""
2649        else:
2650            kind = f"{kind} " if kind else ""
2651        this = self.sql(expression, "this")
2652        expressions = self.expressions(expression)
2653        collate = self.sql(expression, "collate")
2654        collate = f" COLLATE {collate}" if collate else ""
2655        global_ = "GLOBAL " if expression.args.get("global_") else ""
2656        return f"{global_}{kind}{this}{expressions}{collate}"
2657
2658    def set_sql(self, expression: exp.Set) -> str:
2659        expressions = f" {self.expressions(expression, flat=True)}"
2660        tag = " TAG" if expression.args.get("tag") else ""
2661        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2662
2663    def queryband_sql(self, expression: exp.QueryBand) -> str:
2664        this = self.sql(expression, "this")
2665        update = " UPDATE" if expression.args.get("update") else ""
2666        scope = self.sql(expression, "scope")
2667        scope = f" FOR {scope}" if scope else ""
2668
2669        return f"QUERY_BAND = {this}{update}{scope}"
2670
2671    def pragma_sql(self, expression: exp.Pragma) -> str:
2672        return f"PRAGMA {self.sql(expression, 'this')}"
2673
2674    def lock_sql(self, expression: exp.Lock) -> str:
2675        if not self.LOCKING_READS_SUPPORTED:
2676            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2677            return ""
2678
2679        update = expression.args["update"]
2680        key = expression.args.get("key")
2681        if update:
2682            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2683        else:
2684            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2685        expressions = self.expressions(expression, flat=True)
2686        expressions = f" OF {expressions}" if expressions else ""
2687        wait = expression.args.get("wait")
2688
2689        if wait is not None:
2690            if isinstance(wait, exp.Literal):
2691                wait = f" WAIT {self.sql(wait)}"
2692            else:
2693                wait = " NOWAIT" if wait else " SKIP LOCKED"
2694
2695        return f"{lock_type}{expressions}{wait or ''}"
2696
2697    def literal_sql(self, expression: exp.Literal) -> str:
2698        text = expression.this or ""
2699        if expression.is_string:
2700            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2701        return text
2702
2703    def escape_str(
2704        self,
2705        text: str,
2706        escape_backslash: bool = True,
2707        delimiter: t.Optional[str] = None,
2708        escaped_delimiter: t.Optional[str] = None,
2709        is_byte_string: bool = False,
2710    ) -> str:
2711        if is_byte_string:
2712            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
2713        else:
2714            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
2715
2716        if supports_escape_sequences:
2717            text = "".join(
2718                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
2719                for ch in text
2720            )
2721
2722        delimiter = delimiter or self.dialect.QUOTE_END
2723        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2724
2725        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2726
2727    def loaddata_sql(self, expression: exp.LoadData) -> str:
2728        local = " LOCAL" if expression.args.get("local") else ""
2729        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2730        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2731        this = f" INTO TABLE {self.sql(expression, 'this')}"
2732        partition = self.sql(expression, "partition")
2733        partition = f" {partition}" if partition else ""
2734        input_format = self.sql(expression, "input_format")
2735        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2736        serde = self.sql(expression, "serde")
2737        serde = f" SERDE {serde}" if serde else ""
2738        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2739
2740    def null_sql(self, *_) -> str:
2741        return "NULL"
2742
2743    def boolean_sql(self, expression: exp.Boolean) -> str:
2744        return "TRUE" if expression.this else "FALSE"
2745
2746    def booland_sql(self, expression: exp.Booland) -> str:
2747        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
2748
2749    def boolor_sql(self, expression: exp.Boolor) -> str:
2750        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
2751
2752    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2753        this = self.sql(expression, "this")
2754        this = f"{this} " if this else this
2755        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2756        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
2757
2758    def withfill_sql(self, expression: exp.WithFill) -> str:
2759        from_sql = self.sql(expression, "from_")
2760        from_sql = f" FROM {from_sql}" if from_sql else ""
2761        to_sql = self.sql(expression, "to")
2762        to_sql = f" TO {to_sql}" if to_sql else ""
2763        step_sql = self.sql(expression, "step")
2764        step_sql = f" STEP {step_sql}" if step_sql else ""
2765        interpolated_values = [
2766            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2767            if isinstance(e, exp.Alias)
2768            else self.sql(e, "this")
2769            for e in expression.args.get("interpolate") or []
2770        ]
2771        interpolate = (
2772            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2773        )
2774        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2775
2776    def cluster_sql(self, expression: exp.Cluster) -> str:
2777        return self.op_expressions("CLUSTER BY", expression)
2778
2779    def distribute_sql(self, expression: exp.Distribute) -> str:
2780        return self.op_expressions("DISTRIBUTE BY", expression)
2781
2782    def sort_sql(self, expression: exp.Sort) -> str:
2783        return self.op_expressions("SORT BY", expression)
2784
2785    def ordered_sql(self, expression: exp.Ordered) -> str:
2786        desc = expression.args.get("desc")
2787        asc = not desc
2788
2789        nulls_first = expression.args.get("nulls_first")
2790        nulls_last = not nulls_first
2791        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2792        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2793        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2794
2795        this = self.sql(expression, "this")
2796
2797        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2798        nulls_sort_change = ""
2799        if nulls_first and (
2800            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2801        ):
2802            nulls_sort_change = " NULLS FIRST"
2803        elif (
2804            nulls_last
2805            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2806            and not nulls_are_last
2807        ):
2808            nulls_sort_change = " NULLS LAST"
2809
2810        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2811        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2812            window = expression.find_ancestor(exp.Window, exp.Select)
2813            if isinstance(window, exp.Window) and window.args.get("spec"):
2814                self.unsupported(
2815                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2816                )
2817                nulls_sort_change = ""
2818            elif self.NULL_ORDERING_SUPPORTED is False and (
2819                (asc and nulls_sort_change == " NULLS LAST")
2820                or (desc and nulls_sort_change == " NULLS FIRST")
2821            ):
2822                # BigQuery does not allow these ordering/nulls combinations when used under
2823                # an aggregation func or under a window containing one
2824                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2825
2826                if isinstance(ancestor, exp.Window):
2827                    ancestor = ancestor.this
2828                if isinstance(ancestor, exp.AggFunc):
2829                    self.unsupported(
2830                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2831                    )
2832                    nulls_sort_change = ""
2833            elif self.NULL_ORDERING_SUPPORTED is None:
2834                if expression.this.is_int:
2835                    self.unsupported(
2836                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2837                    )
2838                elif not isinstance(expression.this, exp.Rand):
2839                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2840                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2841                nulls_sort_change = ""
2842
2843        with_fill = self.sql(expression, "with_fill")
2844        with_fill = f" {with_fill}" if with_fill else ""
2845
2846        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2847
2848    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2849        window_frame = self.sql(expression, "window_frame")
2850        window_frame = f"{window_frame} " if window_frame else ""
2851
2852        this = self.sql(expression, "this")
2853
2854        return f"{window_frame}{this}"
2855
2856    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2857        partition = self.partition_by_sql(expression)
2858        order = self.sql(expression, "order")
2859        measures = self.expressions(expression, key="measures")
2860        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2861        rows = self.sql(expression, "rows")
2862        rows = self.seg(rows) if rows else ""
2863        after = self.sql(expression, "after")
2864        after = self.seg(after) if after else ""
2865        pattern = self.sql(expression, "pattern")
2866        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2867        definition_sqls = [
2868            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2869            for definition in expression.args.get("define", [])
2870        ]
2871        definitions = self.expressions(sqls=definition_sqls)
2872        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2873        body = "".join(
2874            (
2875                partition,
2876                order,
2877                measures,
2878                rows,
2879                after,
2880                pattern,
2881                define,
2882            )
2883        )
2884        alias = self.sql(expression, "alias")
2885        alias = f" {alias}" if alias else ""
2886        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2887
2888    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2889        limit = expression.args.get("limit")
2890
2891        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2892            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2893        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2894            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2895
2896        return csv(
2897            *sqls,
2898            *[self.sql(join) for join in expression.args.get("joins") or []],
2899            self.sql(expression, "match"),
2900            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2901            self.sql(expression, "prewhere"),
2902            self.sql(expression, "where"),
2903            self.sql(expression, "connect"),
2904            self.sql(expression, "group"),
2905            self.sql(expression, "having"),
2906            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2907            self.sql(expression, "order"),
2908            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2909            *self.after_limit_modifiers(expression),
2910            self.options_modifier(expression),
2911            self.for_modifiers(expression),
2912            sep="",
2913        )
2914
2915    def options_modifier(self, expression: exp.Expression) -> str:
2916        options = self.expressions(expression, key="options")
2917        return f" {options}" if options else ""
2918
2919    def for_modifiers(self, expression: exp.Expression) -> str:
2920        for_modifiers = self.expressions(expression, key="for_")
2921        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
2922
2923    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2924        self.unsupported("Unsupported query option.")
2925        return ""
2926
2927    def offset_limit_modifiers(
2928        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2929    ) -> t.List[str]:
2930        return [
2931            self.sql(expression, "offset") if fetch else self.sql(limit),
2932            self.sql(limit) if fetch else self.sql(expression, "offset"),
2933        ]
2934
2935    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2936        locks = self.expressions(expression, key="locks", sep=" ")
2937        locks = f" {locks}" if locks else ""
2938        return [locks, self.sql(expression, "sample")]
2939
2940    def select_sql(self, expression: exp.Select) -> str:
2941        into = expression.args.get("into")
2942        if not self.SUPPORTS_SELECT_INTO and into:
2943            into.pop()
2944
2945        hint = self.sql(expression, "hint")
2946        distinct = self.sql(expression, "distinct")
2947        distinct = f" {distinct}" if distinct else ""
2948        kind = self.sql(expression, "kind")
2949
2950        limit = expression.args.get("limit")
2951        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2952            top = self.limit_sql(limit, top=True)
2953            limit.pop()
2954        else:
2955            top = ""
2956
2957        expressions = self.expressions(expression)
2958
2959        if kind:
2960            if kind in self.SELECT_KINDS:
2961                kind = f" AS {kind}"
2962            else:
2963                if kind == "STRUCT":
2964                    expressions = self.expressions(
2965                        sqls=[
2966                            self.sql(
2967                                exp.Struct(
2968                                    expressions=[
2969                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2970                                        if isinstance(e, exp.Alias)
2971                                        else e
2972                                        for e in expression.expressions
2973                                    ]
2974                                )
2975                            )
2976                        ]
2977                    )
2978                kind = ""
2979
2980        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2981        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2982
2983        exclude = expression.args.get("exclude")
2984
2985        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
2986            exclude_sql = self.expressions(sqls=exclude, flat=True)
2987            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
2988
2989        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2990        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2991        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2992        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2993        sql = self.query_modifiers(
2994            expression,
2995            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2996            self.sql(expression, "into", comment=False),
2997            self.sql(expression, "from_", comment=False),
2998        )
2999
3000        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3001        if expression.args.get("with_"):
3002            sql = self.maybe_comment(sql, expression)
3003            expression.pop_comments()
3004
3005        sql = self.prepend_ctes(expression, sql)
3006
3007        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3008            expression.set("exclude", None)
3009            subquery = expression.subquery(copy=False)
3010            star = exp.Star(except_=exclude)
3011            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3012
3013        if not self.SUPPORTS_SELECT_INTO and into:
3014            if into.args.get("temporary"):
3015                table_kind = " TEMPORARY"
3016            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3017                table_kind = " UNLOGGED"
3018            else:
3019                table_kind = ""
3020            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3021
3022        return sql
3023
3024    def schema_sql(self, expression: exp.Schema) -> str:
3025        this = self.sql(expression, "this")
3026        sql = self.schema_columns_sql(expression)
3027        return f"{this} {sql}" if this and sql else this or sql
3028
3029    def schema_columns_sql(self, expression: exp.Schema) -> str:
3030        if expression.expressions:
3031            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3032        return ""
3033
3034    def star_sql(self, expression: exp.Star) -> str:
3035        except_ = self.expressions(expression, key="except_", flat=True)
3036        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3037        replace = self.expressions(expression, key="replace", flat=True)
3038        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3039        rename = self.expressions(expression, key="rename", flat=True)
3040        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3041        return f"*{except_}{replace}{rename}"
3042
3043    def parameter_sql(self, expression: exp.Parameter) -> str:
3044        this = self.sql(expression, "this")
3045        return f"{self.PARAMETER_TOKEN}{this}"
3046
3047    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3048        this = self.sql(expression, "this")
3049        kind = expression.text("kind")
3050        if kind:
3051            kind = f"{kind}."
3052        return f"@@{kind}{this}"
3053
3054    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3055        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3056
3057    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3058        alias = self.sql(expression, "alias")
3059        alias = f"{sep}{alias}" if alias else ""
3060        sample = self.sql(expression, "sample")
3061        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3062            alias = f"{sample}{alias}"
3063
3064            # Set to None so it's not generated again by self.query_modifiers()
3065            expression.set("sample", None)
3066
3067        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3068        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3069        return self.prepend_ctes(expression, sql)
3070
3071    def qualify_sql(self, expression: exp.Qualify) -> str:
3072        this = self.indent(self.sql(expression, "this"))
3073        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3074
3075    def unnest_sql(self, expression: exp.Unnest) -> str:
3076        args = self.expressions(expression, flat=True)
3077
3078        alias = expression.args.get("alias")
3079        offset = expression.args.get("offset")
3080
3081        if self.UNNEST_WITH_ORDINALITY:
3082            if alias and isinstance(offset, exp.Expression):
3083                alias.append("columns", offset)
3084
3085        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3086            columns = alias.columns
3087            alias = self.sql(columns[0]) if columns else ""
3088        else:
3089            alias = self.sql(alias)
3090
3091        alias = f" AS {alias}" if alias else alias
3092        if self.UNNEST_WITH_ORDINALITY:
3093            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3094        else:
3095            if isinstance(offset, exp.Expression):
3096                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3097            elif offset:
3098                suffix = f"{alias} WITH OFFSET"
3099            else:
3100                suffix = alias
3101
3102        return f"UNNEST({args}){suffix}"
3103
3104    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3105        return ""
3106
3107    def where_sql(self, expression: exp.Where) -> str:
3108        this = self.indent(self.sql(expression, "this"))
3109        return f"{self.seg('WHERE')}{self.sep()}{this}"
3110
3111    def window_sql(self, expression: exp.Window) -> str:
3112        this = self.sql(expression, "this")
3113        partition = self.partition_by_sql(expression)
3114        order = expression.args.get("order")
3115        order = self.order_sql(order, flat=True) if order else ""
3116        spec = self.sql(expression, "spec")
3117        alias = self.sql(expression, "alias")
3118        over = self.sql(expression, "over") or "OVER"
3119
3120        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3121
3122        first = expression.args.get("first")
3123        if first is None:
3124            first = ""
3125        else:
3126            first = "FIRST" if first else "LAST"
3127
3128        if not partition and not order and not spec and alias:
3129            return f"{this} {alias}"
3130
3131        args = self.format_args(
3132            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3133        )
3134        return f"{this} ({args})"
3135
3136    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3137        partition = self.expressions(expression, key="partition_by", flat=True)
3138        return f"PARTITION BY {partition}" if partition else ""
3139
3140    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3141        kind = self.sql(expression, "kind")
3142        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3143        end = (
3144            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3145            or "CURRENT ROW"
3146        )
3147
3148        window_spec = f"{kind} BETWEEN {start} AND {end}"
3149
3150        exclude = self.sql(expression, "exclude")
3151        if exclude:
3152            if self.SUPPORTS_WINDOW_EXCLUDE:
3153                window_spec += f" EXCLUDE {exclude}"
3154            else:
3155                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3156
3157        return window_spec
3158
3159    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3160        this = self.sql(expression, "this")
3161        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3162        return f"{this} WITHIN GROUP ({expression_sql})"
3163
3164    def between_sql(self, expression: exp.Between) -> str:
3165        this = self.sql(expression, "this")
3166        low = self.sql(expression, "low")
3167        high = self.sql(expression, "high")
3168        symmetric = expression.args.get("symmetric")
3169
3170        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3171            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3172
3173        flag = (
3174            " SYMMETRIC"
3175            if symmetric
3176            else " ASYMMETRIC"
3177            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3178            else ""  # silently drop ASYMMETRIC – semantics identical
3179        )
3180        return f"{this} BETWEEN{flag} {low} AND {high}"
3181
3182    def bracket_offset_expressions(
3183        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
3184    ) -> t.List[exp.Expression]:
3185        return apply_index_offset(
3186            expression.this,
3187            expression.expressions,
3188            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3189            dialect=self.dialect,
3190        )
3191
3192    def bracket_sql(self, expression: exp.Bracket) -> str:
3193        expressions = self.bracket_offset_expressions(expression)
3194        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3195        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3196
3197    def all_sql(self, expression: exp.All) -> str:
3198        this = self.sql(expression, "this")
3199        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3200            this = self.wrap(this)
3201        return f"ALL {this}"
3202
3203    def any_sql(self, expression: exp.Any) -> str:
3204        this = self.sql(expression, "this")
3205        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3206            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3207                this = self.wrap(this)
3208            return f"ANY{this}"
3209        return f"ANY {this}"
3210
3211    def exists_sql(self, expression: exp.Exists) -> str:
3212        return f"EXISTS{self.wrap(expression)}"
3213
3214    def case_sql(self, expression: exp.Case) -> str:
3215        this = self.sql(expression, "this")
3216        statements = [f"CASE {this}" if this else "CASE"]
3217
3218        for e in expression.args["ifs"]:
3219            statements.append(f"WHEN {self.sql(e, 'this')}")
3220            statements.append(f"THEN {self.sql(e, 'true')}")
3221
3222        default = self.sql(expression, "default")
3223
3224        if default:
3225            statements.append(f"ELSE {default}")
3226
3227        statements.append("END")
3228
3229        if self.pretty and self.too_wide(statements):
3230            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3231
3232        return " ".join(statements)
3233
3234    def constraint_sql(self, expression: exp.Constraint) -> str:
3235        this = self.sql(expression, "this")
3236        expressions = self.expressions(expression, flat=True)
3237        return f"CONSTRAINT {this} {expressions}"
3238
3239    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3240        order = expression.args.get("order")
3241        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3242        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3243
3244    def extract_sql(self, expression: exp.Extract) -> str:
3245        from sqlglot.dialects.dialect import map_date_part
3246
3247        this = (
3248            map_date_part(expression.this, self.dialect)
3249            if self.NORMALIZE_EXTRACT_DATE_PARTS
3250            else expression.this
3251        )
3252        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3253        expression_sql = self.sql(expression, "expression")
3254
3255        return f"EXTRACT({this_sql} FROM {expression_sql})"
3256
3257    def trim_sql(self, expression: exp.Trim) -> str:
3258        trim_type = self.sql(expression, "position")
3259
3260        if trim_type == "LEADING":
3261            func_name = "LTRIM"
3262        elif trim_type == "TRAILING":
3263            func_name = "RTRIM"
3264        else:
3265            func_name = "TRIM"
3266
3267        return self.func(func_name, expression.this, expression.expression)
3268
3269    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
3270        args = expression.expressions
3271        if isinstance(expression, exp.ConcatWs):
3272            args = args[1:]  # Skip the delimiter
3273
3274        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3275            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
3276
3277        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
3278
3279            def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression:
3280                if not e.type:
3281                    from sqlglot.optimizer.annotate_types import annotate_types
3282
3283                    e = annotate_types(e, dialect=self.dialect)
3284
3285                if e.is_string or e.is_type(exp.DataType.Type.ARRAY):
3286                    return e
3287
3288                return exp.func("coalesce", e, exp.Literal.string(""))
3289
3290            args = [_wrap_with_coalesce(e) for e in args]
3291
3292        return args
3293
3294    def concat_sql(self, expression: exp.Concat) -> str:
3295        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3296            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3297            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3298            # instead of coalescing them to empty string.
3299            from sqlglot.dialects.dialect import concat_to_dpipe_sql
3300
3301            return concat_to_dpipe_sql(self, expression)
3302
3303        expressions = self.convert_concat_args(expression)
3304
3305        # Some dialects don't allow a single-argument CONCAT call
3306        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3307            return self.sql(expressions[0])
3308
3309        return self.func("CONCAT", *expressions)
3310
3311    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3312        return self.func(
3313            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3314        )
3315
3316    def check_sql(self, expression: exp.Check) -> str:
3317        this = self.sql(expression, key="this")
3318        return f"CHECK ({this})"
3319
3320    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3321        expressions = self.expressions(expression, flat=True)
3322        expressions = f" ({expressions})" if expressions else ""
3323        reference = self.sql(expression, "reference")
3324        reference = f" {reference}" if reference else ""
3325        delete = self.sql(expression, "delete")
3326        delete = f" ON DELETE {delete}" if delete else ""
3327        update = self.sql(expression, "update")
3328        update = f" ON UPDATE {update}" if update else ""
3329        options = self.expressions(expression, key="options", flat=True, sep=" ")
3330        options = f" {options}" if options else ""
3331        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3332
3333    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3334        this = self.sql(expression, "this")
3335        this = f" {this}" if this else ""
3336        expressions = self.expressions(expression, flat=True)
3337        include = self.sql(expression, "include")
3338        options = self.expressions(expression, key="options", flat=True, sep=" ")
3339        options = f" {options}" if options else ""
3340        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3341
3342    def if_sql(self, expression: exp.If) -> str:
3343        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3344
3345    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3346        if self.MATCH_AGAINST_TABLE_PREFIX:
3347            expressions = []
3348            for expr in expression.expressions:
3349                if isinstance(expr, exp.Table):
3350                    expressions.append(f"TABLE {self.sql(expr)}")
3351                else:
3352                    expressions.append(expr)
3353        else:
3354            expressions = expression.expressions
3355
3356        modifier = expression.args.get("modifier")
3357        modifier = f" {modifier}" if modifier else ""
3358        return (
3359            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3360        )
3361
3362    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3363        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3364
3365    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3366        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3367
3368        if expression.args.get("escape"):
3369            path = self.escape_str(path)
3370
3371        if self.QUOTE_JSON_PATH:
3372            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3373
3374        return path
3375
3376    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3377        if isinstance(expression, exp.JSONPathPart):
3378            transform = self.TRANSFORMS.get(expression.__class__)
3379            if not callable(transform):
3380                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3381                return ""
3382
3383            return transform(self, expression)
3384
3385        if isinstance(expression, int):
3386            return str(expression)
3387
3388        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3389            escaped = expression.replace("'", "\\'")
3390            escaped = f"\\'{expression}\\'"
3391        else:
3392            escaped = expression.replace('"', '\\"')
3393            escaped = f'"{escaped}"'
3394
3395        return escaped
3396
3397    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3398        return f"{self.sql(expression, 'this')} FORMAT JSON"
3399
3400    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3401        # Output the Teradata column FORMAT override.
3402        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3403        this = self.sql(expression, "this")
3404        fmt = self.sql(expression, "format")
3405        return f"{this} (FORMAT {fmt})"
3406
3407    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3408        null_handling = expression.args.get("null_handling")
3409        null_handling = f" {null_handling}" if null_handling else ""
3410
3411        unique_keys = expression.args.get("unique_keys")
3412        if unique_keys is not None:
3413            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3414        else:
3415            unique_keys = ""
3416
3417        return_type = self.sql(expression, "return_type")
3418        return_type = f" RETURNING {return_type}" if return_type else ""
3419        encoding = self.sql(expression, "encoding")
3420        encoding = f" ENCODING {encoding}" if encoding else ""
3421
3422        return self.func(
3423            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3424            *expression.expressions,
3425            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3426        )
3427
3428    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3429        return self.jsonobject_sql(expression)
3430
3431    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3432        null_handling = expression.args.get("null_handling")
3433        null_handling = f" {null_handling}" if null_handling else ""
3434        return_type = self.sql(expression, "return_type")
3435        return_type = f" RETURNING {return_type}" if return_type else ""
3436        strict = " STRICT" if expression.args.get("strict") else ""
3437        return self.func(
3438            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3439        )
3440
3441    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3442        this = self.sql(expression, "this")
3443        order = self.sql(expression, "order")
3444        null_handling = expression.args.get("null_handling")
3445        null_handling = f" {null_handling}" if null_handling else ""
3446        return_type = self.sql(expression, "return_type")
3447        return_type = f" RETURNING {return_type}" if return_type else ""
3448        strict = " STRICT" if expression.args.get("strict") else ""
3449        return self.func(
3450            "JSON_ARRAYAGG",
3451            this,
3452            suffix=f"{order}{null_handling}{return_type}{strict})",
3453        )
3454
3455    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3456        path = self.sql(expression, "path")
3457        path = f" PATH {path}" if path else ""
3458        nested_schema = self.sql(expression, "nested_schema")
3459
3460        if nested_schema:
3461            return f"NESTED{path} {nested_schema}"
3462
3463        this = self.sql(expression, "this")
3464        kind = self.sql(expression, "kind")
3465        kind = f" {kind}" if kind else ""
3466
3467        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3468        return f"{this}{kind}{path}{ordinality}"
3469
3470    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3471        return self.func("COLUMNS", *expression.expressions)
3472
3473    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3474        this = self.sql(expression, "this")
3475        path = self.sql(expression, "path")
3476        path = f", {path}" if path else ""
3477        error_handling = expression.args.get("error_handling")
3478        error_handling = f" {error_handling}" if error_handling else ""
3479        empty_handling = expression.args.get("empty_handling")
3480        empty_handling = f" {empty_handling}" if empty_handling else ""
3481        schema = self.sql(expression, "schema")
3482        return self.func(
3483            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3484        )
3485
3486    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3487        this = self.sql(expression, "this")
3488        kind = self.sql(expression, "kind")
3489        path = self.sql(expression, "path")
3490        path = f" {path}" if path else ""
3491        as_json = " AS JSON" if expression.args.get("as_json") else ""
3492        return f"{this} {kind}{path}{as_json}"
3493
3494    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3495        this = self.sql(expression, "this")
3496        path = self.sql(expression, "path")
3497        path = f", {path}" if path else ""
3498        expressions = self.expressions(expression)
3499        with_ = (
3500            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3501            if expressions
3502            else ""
3503        )
3504        return f"OPENJSON({this}{path}){with_}"
3505
3506    def in_sql(self, expression: exp.In) -> str:
3507        query = expression.args.get("query")
3508        unnest = expression.args.get("unnest")
3509        field = expression.args.get("field")
3510        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3511
3512        if query:
3513            in_sql = self.sql(query)
3514        elif unnest:
3515            in_sql = self.in_unnest_op(unnest)
3516        elif field:
3517            in_sql = self.sql(field)
3518        else:
3519            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3520
3521        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3522
3523    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3524        return f"(SELECT {self.sql(unnest)})"
3525
3526    def interval_sql(self, expression: exp.Interval) -> str:
3527        unit_expression = expression.args.get("unit")
3528        unit = self.sql(unit_expression) if unit_expression else ""
3529        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3530            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3531        unit = f" {unit}" if unit else ""
3532
3533        if self.SINGLE_STRING_INTERVAL:
3534            this = expression.this.name if expression.this else ""
3535            if this:
3536                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3537                    return f"INTERVAL '{this}'{unit}"
3538                return f"INTERVAL '{this}{unit}'"
3539            return f"INTERVAL{unit}"
3540
3541        this = self.sql(expression, "this")
3542        if this:
3543            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3544            this = f" {this}" if unwrapped else f" ({this})"
3545
3546        return f"INTERVAL{this}{unit}"
3547
3548    def return_sql(self, expression: exp.Return) -> str:
3549        return f"RETURN {self.sql(expression, 'this')}"
3550
3551    def reference_sql(self, expression: exp.Reference) -> str:
3552        this = self.sql(expression, "this")
3553        expressions = self.expressions(expression, flat=True)
3554        expressions = f"({expressions})" if expressions else ""
3555        options = self.expressions(expression, key="options", flat=True, sep=" ")
3556        options = f" {options}" if options else ""
3557        return f"REFERENCES {this}{expressions}{options}"
3558
3559    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3560        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3561        parent = expression.parent
3562        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3563
3564        return self.func(
3565            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3566        )
3567
3568    def paren_sql(self, expression: exp.Paren) -> str:
3569        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3570        return f"({sql}{self.seg(')', sep='')}"
3571
3572    def neg_sql(self, expression: exp.Neg) -> str:
3573        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3574        this_sql = self.sql(expression, "this")
3575        sep = " " if this_sql[0] == "-" else ""
3576        return f"-{sep}{this_sql}"
3577
3578    def not_sql(self, expression: exp.Not) -> str:
3579        return f"NOT {self.sql(expression, 'this')}"
3580
3581    def alias_sql(self, expression: exp.Alias) -> str:
3582        alias = self.sql(expression, "alias")
3583        alias = f" AS {alias}" if alias else ""
3584        return f"{self.sql(expression, 'this')}{alias}"
3585
3586    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3587        alias = expression.args["alias"]
3588
3589        parent = expression.parent
3590        pivot = parent and parent.parent
3591
3592        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3593            identifier_alias = isinstance(alias, exp.Identifier)
3594            literal_alias = isinstance(alias, exp.Literal)
3595
3596            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3597                alias.replace(exp.Literal.string(alias.output_name))
3598            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3599                alias.replace(exp.to_identifier(alias.output_name))
3600
3601        return self.alias_sql(expression)
3602
3603    def aliases_sql(self, expression: exp.Aliases) -> str:
3604        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3605
3606    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3607        this = self.sql(expression, "this")
3608        index = self.sql(expression, "expression")
3609        return f"{this} AT {index}"
3610
3611    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3612        this = self.sql(expression, "this")
3613        zone = self.sql(expression, "zone")
3614        return f"{this} AT TIME ZONE {zone}"
3615
3616    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3617        this = self.sql(expression, "this")
3618        zone = self.sql(expression, "zone")
3619        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3620
3621    def add_sql(self, expression: exp.Add) -> str:
3622        return self.binary(expression, "+")
3623
3624    def and_sql(
3625        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3626    ) -> str:
3627        return self.connector_sql(expression, "AND", stack)
3628
3629    def or_sql(
3630        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3631    ) -> str:
3632        return self.connector_sql(expression, "OR", stack)
3633
3634    def xor_sql(
3635        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3636    ) -> str:
3637        return self.connector_sql(expression, "XOR", stack)
3638
3639    def connector_sql(
3640        self,
3641        expression: exp.Connector,
3642        op: str,
3643        stack: t.Optional[t.List[str | exp.Expression]] = None,
3644    ) -> str:
3645        if stack is not None:
3646            if expression.expressions:
3647                stack.append(self.expressions(expression, sep=f" {op} "))
3648            else:
3649                stack.append(expression.right)
3650                if expression.comments and self.comments:
3651                    for comment in expression.comments:
3652                        if comment:
3653                            op += f" /*{self.sanitize_comment(comment)}*/"
3654                stack.extend((op, expression.left))
3655            return op
3656
3657        stack = [expression]
3658        sqls: t.List[str] = []
3659        ops = set()
3660
3661        while stack:
3662            node = stack.pop()
3663            if isinstance(node, exp.Connector):
3664                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3665            else:
3666                sql = self.sql(node)
3667                if sqls and sqls[-1] in ops:
3668                    sqls[-1] += f" {sql}"
3669                else:
3670                    sqls.append(sql)
3671
3672        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3673        return sep.join(sqls)
3674
3675    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3676        return self.binary(expression, "&")
3677
3678    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3679        return self.binary(expression, "<<")
3680
3681    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3682        return f"~{self.sql(expression, 'this')}"
3683
3684    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3685        return self.binary(expression, "|")
3686
3687    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3688        return self.binary(expression, ">>")
3689
3690    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3691        return self.binary(expression, "^")
3692
3693    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3694        format_sql = self.sql(expression, "format")
3695        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3696        to_sql = self.sql(expression, "to")
3697        to_sql = f" {to_sql}" if to_sql else ""
3698        action = self.sql(expression, "action")
3699        action = f" {action}" if action else ""
3700        default = self.sql(expression, "default")
3701        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3702        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3703
3704    # Base implementation that excludes safe, zone, and target_type metadata args
3705    def strtotime_sql(self, expression: exp.StrToTime) -> str:
3706        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
3707
3708    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3709        zone = self.sql(expression, "this")
3710        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3711
3712    def collate_sql(self, expression: exp.Collate) -> str:
3713        if self.COLLATE_IS_FUNC:
3714            return self.function_fallback_sql(expression)
3715        return self.binary(expression, "COLLATE")
3716
3717    def command_sql(self, expression: exp.Command) -> str:
3718        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3719
3720    def comment_sql(self, expression: exp.Comment) -> str:
3721        this = self.sql(expression, "this")
3722        kind = expression.args["kind"]
3723        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3724        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3725        expression_sql = self.sql(expression, "expression")
3726        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3727
3728    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3729        this = self.sql(expression, "this")
3730        delete = " DELETE" if expression.args.get("delete") else ""
3731        recompress = self.sql(expression, "recompress")
3732        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3733        to_disk = self.sql(expression, "to_disk")
3734        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3735        to_volume = self.sql(expression, "to_volume")
3736        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3737        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3738
3739    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3740        where = self.sql(expression, "where")
3741        group = self.sql(expression, "group")
3742        aggregates = self.expressions(expression, key="aggregates")
3743        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3744
3745        if not (where or group or aggregates) and len(expression.expressions) == 1:
3746            return f"TTL {self.expressions(expression, flat=True)}"
3747
3748        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3749
3750    def transaction_sql(self, expression: exp.Transaction) -> str:
3751        modes = self.expressions(expression, key="modes")
3752        modes = f" {modes}" if modes else ""
3753        return f"BEGIN{modes}"
3754
3755    def commit_sql(self, expression: exp.Commit) -> str:
3756        chain = expression.args.get("chain")
3757        if chain is not None:
3758            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3759
3760        return f"COMMIT{chain or ''}"
3761
3762    def rollback_sql(self, expression: exp.Rollback) -> str:
3763        savepoint = expression.args.get("savepoint")
3764        savepoint = f" TO {savepoint}" if savepoint else ""
3765        return f"ROLLBACK{savepoint}"
3766
3767    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3768        this = self.sql(expression, "this")
3769
3770        dtype = self.sql(expression, "dtype")
3771        if dtype:
3772            collate = self.sql(expression, "collate")
3773            collate = f" COLLATE {collate}" if collate else ""
3774            using = self.sql(expression, "using")
3775            using = f" USING {using}" if using else ""
3776            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3777            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3778
3779        default = self.sql(expression, "default")
3780        if default:
3781            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3782
3783        comment = self.sql(expression, "comment")
3784        if comment:
3785            return f"ALTER COLUMN {this} COMMENT {comment}"
3786
3787        visible = expression.args.get("visible")
3788        if visible:
3789            return f"ALTER COLUMN {this} SET {visible}"
3790
3791        allow_null = expression.args.get("allow_null")
3792        drop = expression.args.get("drop")
3793
3794        if not drop and not allow_null:
3795            self.unsupported("Unsupported ALTER COLUMN syntax")
3796
3797        if allow_null is not None:
3798            keyword = "DROP" if drop else "SET"
3799            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3800
3801        return f"ALTER COLUMN {this} DROP DEFAULT"
3802
3803    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3804        this = self.sql(expression, "this")
3805
3806        visible = expression.args.get("visible")
3807        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3808
3809        return f"ALTER INDEX {this} {visible_sql}"
3810
3811    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3812        this = self.sql(expression, "this")
3813        if not isinstance(expression.this, exp.Var):
3814            this = f"KEY DISTKEY {this}"
3815        return f"ALTER DISTSTYLE {this}"
3816
3817    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3818        compound = " COMPOUND" if expression.args.get("compound") else ""
3819        this = self.sql(expression, "this")
3820        expressions = self.expressions(expression, flat=True)
3821        expressions = f"({expressions})" if expressions else ""
3822        return f"ALTER{compound} SORTKEY {this or expressions}"
3823
3824    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
3825        if not self.RENAME_TABLE_WITH_DB:
3826            # Remove db from tables
3827            expression = expression.transform(
3828                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3829            ).assert_is(exp.AlterRename)
3830        this = self.sql(expression, "this")
3831        to_kw = " TO" if include_to else ""
3832        return f"RENAME{to_kw} {this}"
3833
3834    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3835        exists = " IF EXISTS" if expression.args.get("exists") else ""
3836        old_column = self.sql(expression, "this")
3837        new_column = self.sql(expression, "to")
3838        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3839
3840    def alterset_sql(self, expression: exp.AlterSet) -> str:
3841        exprs = self.expressions(expression, flat=True)
3842        if self.ALTER_SET_WRAPPED:
3843            exprs = f"({exprs})"
3844
3845        return f"SET {exprs}"
3846
3847    def alter_sql(self, expression: exp.Alter) -> str:
3848        actions = expression.args["actions"]
3849
3850        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3851            actions[0], exp.ColumnDef
3852        ):
3853            actions_sql = self.expressions(expression, key="actions", flat=True)
3854            actions_sql = f"ADD {actions_sql}"
3855        else:
3856            actions_list = []
3857            for action in actions:
3858                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3859                    action_sql = self.add_column_sql(action)
3860                else:
3861                    action_sql = self.sql(action)
3862                    if isinstance(action, exp.Query):
3863                        action_sql = f"AS {action_sql}"
3864
3865                actions_list.append(action_sql)
3866
3867            actions_sql = self.format_args(*actions_list).lstrip("\n")
3868
3869        exists = " IF EXISTS" if expression.args.get("exists") else ""
3870        on_cluster = self.sql(expression, "cluster")
3871        on_cluster = f" {on_cluster}" if on_cluster else ""
3872        only = " ONLY" if expression.args.get("only") else ""
3873        options = self.expressions(expression, key="options")
3874        options = f", {options}" if options else ""
3875        kind = self.sql(expression, "kind")
3876        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3877        check = " WITH CHECK" if expression.args.get("check") else ""
3878        cascade = (
3879            " CASCADE"
3880            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
3881            else ""
3882        )
3883        this = self.sql(expression, "this")
3884        this = f" {this}" if this else ""
3885
3886        return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
3887
3888    def altersession_sql(self, expression: exp.AlterSession) -> str:
3889        items_sql = self.expressions(expression, flat=True)
3890        keyword = "UNSET" if expression.args.get("unset") else "SET"
3891        return f"{keyword} {items_sql}"
3892
3893    def add_column_sql(self, expression: exp.Expression) -> str:
3894        sql = self.sql(expression)
3895        if isinstance(expression, exp.Schema):
3896            column_text = " COLUMNS"
3897        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3898            column_text = " COLUMN"
3899        else:
3900            column_text = ""
3901
3902        return f"ADD{column_text} {sql}"
3903
3904    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3905        expressions = self.expressions(expression)
3906        exists = " IF EXISTS " if expression.args.get("exists") else " "
3907        return f"DROP{exists}{expressions}"
3908
3909    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3910        return f"ADD {self.expressions(expression, indent=False)}"
3911
3912    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3913        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3914        location = self.sql(expression, "location")
3915        location = f" {location}" if location else ""
3916        return f"ADD {exists}{self.sql(expression.this)}{location}"
3917
3918    def distinct_sql(self, expression: exp.Distinct) -> str:
3919        this = self.expressions(expression, flat=True)
3920
3921        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3922            case = exp.case()
3923            for arg in expression.expressions:
3924                case = case.when(arg.is_(exp.null()), exp.null())
3925            this = self.sql(case.else_(f"({this})"))
3926
3927        this = f" {this}" if this else ""
3928
3929        on = self.sql(expression, "on")
3930        on = f" ON {on}" if on else ""
3931        return f"DISTINCT{this}{on}"
3932
3933    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3934        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
3935
3936    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3937        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
3938
3939    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3940        this_sql = self.sql(expression, "this")
3941        expression_sql = self.sql(expression, "expression")
3942        kind = "MAX" if expression.args.get("max") else "MIN"
3943        return f"{this_sql} HAVING {kind} {expression_sql}"
3944
3945    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3946        return self.sql(
3947            exp.Cast(
3948                this=exp.Div(this=expression.this, expression=expression.expression),
3949                to=exp.DataType(this=exp.DataType.Type.INT),
3950            )
3951        )
3952
3953    def dpipe_sql(self, expression: exp.DPipe) -> str:
3954        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3955            return self.func(
3956                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3957            )
3958        return self.binary(expression, "||")
3959
3960    def div_sql(self, expression: exp.Div) -> str:
3961        l, r = expression.left, expression.right
3962
3963        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3964            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3965
3966        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3967            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3968                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3969
3970        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3971            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3972                return self.sql(
3973                    exp.cast(
3974                        l / r,
3975                        to=exp.DataType.Type.BIGINT,
3976                    )
3977                )
3978
3979        return self.binary(expression, "/")
3980
3981    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3982        n = exp._wrap(expression.this, exp.Binary)
3983        d = exp._wrap(expression.expression, exp.Binary)
3984        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
3985
3986    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3987        return self.binary(expression, "OVERLAPS")
3988
3989    def distance_sql(self, expression: exp.Distance) -> str:
3990        return self.binary(expression, "<->")
3991
3992    def dot_sql(self, expression: exp.Dot) -> str:
3993        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
3994
3995    def eq_sql(self, expression: exp.EQ) -> str:
3996        return self.binary(expression, "=")
3997
3998    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
3999        return self.binary(expression, ":=")
4000
4001    def escape_sql(self, expression: exp.Escape) -> str:
4002        return self.binary(expression, "ESCAPE")
4003
4004    def glob_sql(self, expression: exp.Glob) -> str:
4005        return self.binary(expression, "GLOB")
4006
4007    def gt_sql(self, expression: exp.GT) -> str:
4008        return self.binary(expression, ">")
4009
4010    def gte_sql(self, expression: exp.GTE) -> str:
4011        return self.binary(expression, ">=")
4012
4013    def is_sql(self, expression: exp.Is) -> str:
4014        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4015            return self.sql(
4016                expression.this if expression.expression.this else exp.not_(expression.this)
4017            )
4018        return self.binary(expression, "IS")
4019
4020    def _like_sql(self, expression: exp.Like | exp.ILike) -> str:
4021        this = expression.this
4022        rhs = expression.expression
4023
4024        if isinstance(expression, exp.Like):
4025            exp_class: t.Type[exp.Like | exp.ILike] = exp.Like
4026            op = "LIKE"
4027        else:
4028            exp_class = exp.ILike
4029            op = "ILIKE"
4030
4031        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4032            exprs = rhs.this.unnest()
4033
4034            if isinstance(exprs, exp.Tuple):
4035                exprs = exprs.expressions
4036
4037            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4038
4039            like_expr: exp.Expression = exp_class(this=this, expression=exprs[0])
4040            for expr in exprs[1:]:
4041                like_expr = connective(like_expr, exp_class(this=this, expression=expr))
4042
4043            parent = expression.parent
4044            if not isinstance(parent, type(like_expr)) and isinstance(parent, exp.Condition):
4045                like_expr = exp.paren(like_expr, copy=False)
4046
4047            return self.sql(like_expr)
4048
4049        return self.binary(expression, op)
4050
4051    def like_sql(self, expression: exp.Like) -> str:
4052        return self._like_sql(expression)
4053
4054    def ilike_sql(self, expression: exp.ILike) -> str:
4055        return self._like_sql(expression)
4056
4057    def match_sql(self, expression: exp.Match) -> str:
4058        return self.binary(expression, "MATCH")
4059
4060    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4061        return self.binary(expression, "SIMILAR TO")
4062
4063    def lt_sql(self, expression: exp.LT) -> str:
4064        return self.binary(expression, "<")
4065
4066    def lte_sql(self, expression: exp.LTE) -> str:
4067        return self.binary(expression, "<=")
4068
4069    def mod_sql(self, expression: exp.Mod) -> str:
4070        return self.binary(expression, "%")
4071
4072    def mul_sql(self, expression: exp.Mul) -> str:
4073        return self.binary(expression, "*")
4074
4075    def neq_sql(self, expression: exp.NEQ) -> str:
4076        return self.binary(expression, "<>")
4077
4078    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4079        return self.binary(expression, "IS NOT DISTINCT FROM")
4080
4081    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4082        return self.binary(expression, "IS DISTINCT FROM")
4083
4084    def sub_sql(self, expression: exp.Sub) -> str:
4085        return self.binary(expression, "-")
4086
4087    def trycast_sql(self, expression: exp.TryCast) -> str:
4088        return self.cast_sql(expression, safe_prefix="TRY_")
4089
4090    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4091        return self.cast_sql(expression)
4092
4093    def try_sql(self, expression: exp.Try) -> str:
4094        if not self.TRY_SUPPORTED:
4095            self.unsupported("Unsupported TRY function")
4096            return self.sql(expression, "this")
4097
4098        return self.func("TRY", expression.this)
4099
4100    def log_sql(self, expression: exp.Log) -> str:
4101        this = expression.this
4102        expr = expression.expression
4103
4104        if self.dialect.LOG_BASE_FIRST is False:
4105            this, expr = expr, this
4106        elif self.dialect.LOG_BASE_FIRST is None and expr:
4107            if this.name in ("2", "10"):
4108                return self.func(f"LOG{this.name}", expr)
4109
4110            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4111
4112        return self.func("LOG", this, expr)
4113
4114    def use_sql(self, expression: exp.Use) -> str:
4115        kind = self.sql(expression, "kind")
4116        kind = f" {kind}" if kind else ""
4117        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4118        this = f" {this}" if this else ""
4119        return f"USE{kind}{this}"
4120
4121    def binary(self, expression: exp.Binary, op: str) -> str:
4122        sqls: t.List[str] = []
4123        stack: t.List[t.Union[str, exp.Expression]] = [expression]
4124        binary_type = type(expression)
4125
4126        while stack:
4127            node = stack.pop()
4128
4129            if type(node) is binary_type:
4130                op_func = node.args.get("operator")
4131                if op_func:
4132                    op = f"OPERATOR({self.sql(op_func)})"
4133
4134                stack.append(node.right)
4135                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4136                stack.append(node.left)
4137            else:
4138                sqls.append(self.sql(node))
4139
4140        return "".join(sqls)
4141
4142    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4143        to_clause = self.sql(expression, "to")
4144        if to_clause:
4145            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4146
4147        return self.function_fallback_sql(expression)
4148
4149    def function_fallback_sql(self, expression: exp.Func) -> str:
4150        args = []
4151
4152        for key in expression.arg_types:
4153            arg_value = expression.args.get(key)
4154
4155            if isinstance(arg_value, list):
4156                for value in arg_value:
4157                    args.append(value)
4158            elif arg_value is not None:
4159                args.append(arg_value)
4160
4161        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4162            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
4163        else:
4164            name = expression.sql_name()
4165
4166        return self.func(name, *args)
4167
4168    def func(
4169        self,
4170        name: str,
4171        *args: t.Optional[exp.Expression | str],
4172        prefix: str = "(",
4173        suffix: str = ")",
4174        normalize: bool = True,
4175    ) -> str:
4176        name = self.normalize_func(name) if normalize else name
4177        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4178
4179    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
4180        arg_sqls = tuple(
4181            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4182        )
4183        if self.pretty and self.too_wide(arg_sqls):
4184            return self.indent(
4185                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4186            )
4187        return sep.join(arg_sqls)
4188
4189    def too_wide(self, args: t.Iterable) -> bool:
4190        return sum(len(arg) for arg in args) > self.max_text_width
4191
4192    def format_time(
4193        self,
4194        expression: exp.Expression,
4195        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
4196        inverse_time_trie: t.Optional[t.Dict] = None,
4197    ) -> t.Optional[str]:
4198        return format_time(
4199            self.sql(expression, "format"),
4200            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4201            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4202        )
4203
4204    def expressions(
4205        self,
4206        expression: t.Optional[exp.Expression] = None,
4207        key: t.Optional[str] = None,
4208        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
4209        flat: bool = False,
4210        indent: bool = True,
4211        skip_first: bool = False,
4212        skip_last: bool = False,
4213        sep: str = ", ",
4214        prefix: str = "",
4215        dynamic: bool = False,
4216        new_line: bool = False,
4217    ) -> str:
4218        expressions = expression.args.get(key or "expressions") if expression else sqls
4219
4220        if not expressions:
4221            return ""
4222
4223        if flat:
4224            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4225
4226        num_sqls = len(expressions)
4227        result_sqls = []
4228
4229        for i, e in enumerate(expressions):
4230            sql = self.sql(e, comment=False)
4231            if not sql:
4232                continue
4233
4234            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
4235
4236            if self.pretty:
4237                if self.leading_comma:
4238                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4239                else:
4240                    result_sqls.append(
4241                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4242                    )
4243            else:
4244                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4245
4246        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4247            if new_line:
4248                result_sqls.insert(0, "")
4249                result_sqls.append("")
4250            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4251        else:
4252            result_sql = "".join(result_sqls)
4253
4254        return (
4255            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4256            if indent
4257            else result_sql
4258        )
4259
4260    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
4261        flat = flat or isinstance(expression.parent, exp.Properties)
4262        expressions_sql = self.expressions(expression, flat=flat)
4263        if flat:
4264            return f"{op} {expressions_sql}"
4265        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4266
4267    def naked_property(self, expression: exp.Property) -> str:
4268        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4269        if not property_name:
4270            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4271        return f"{property_name} {self.sql(expression, 'this')}"
4272
4273    def tag_sql(self, expression: exp.Tag) -> str:
4274        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4275
4276    def token_sql(self, token_type: TokenType) -> str:
4277        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4278
4279    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4280        this = self.sql(expression, "this")
4281        expressions = self.no_identify(self.expressions, expression)
4282        expressions = (
4283            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4284        )
4285        return f"{this}{expressions}" if expressions.strip() != "" else this
4286
4287    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4288        this = self.sql(expression, "this")
4289        expressions = self.expressions(expression, flat=True)
4290        return f"{this}({expressions})"
4291
4292    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4293        return self.binary(expression, "=>")
4294
4295    def when_sql(self, expression: exp.When) -> str:
4296        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4297        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4298        condition = self.sql(expression, "condition")
4299        condition = f" AND {condition}" if condition else ""
4300
4301        then_expression = expression.args.get("then")
4302        if isinstance(then_expression, exp.Insert):
4303            this = self.sql(then_expression, "this")
4304            this = f"INSERT {this}" if this else "INSERT"
4305            then = self.sql(then_expression, "expression")
4306            then = f"{this} VALUES {then}" if then else this
4307        elif isinstance(then_expression, exp.Update):
4308            if isinstance(then_expression.args.get("expressions"), exp.Star):
4309                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4310            else:
4311                expressions_sql = self.expressions(then_expression)
4312                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4313
4314        else:
4315            then = self.sql(then_expression)
4316        return f"WHEN {matched}{source}{condition} THEN {then}"
4317
4318    def whens_sql(self, expression: exp.Whens) -> str:
4319        return self.expressions(expression, sep=" ", indent=False)
4320
4321    def merge_sql(self, expression: exp.Merge) -> str:
4322        table = expression.this
4323        table_alias = ""
4324
4325        hints = table.args.get("hints")
4326        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4327            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4328            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4329
4330        this = self.sql(table)
4331        using = f"USING {self.sql(expression, 'using')}"
4332        whens = self.sql(expression, "whens")
4333
4334        on = self.sql(expression, "on")
4335        on = f"ON {on}" if on else ""
4336
4337        if not on:
4338            on = self.expressions(expression, key="using_cond")
4339            on = f"USING ({on})" if on else ""
4340
4341        returning = self.sql(expression, "returning")
4342        if returning:
4343            whens = f"{whens}{returning}"
4344
4345        sep = self.sep()
4346
4347        return self.prepend_ctes(
4348            expression,
4349            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4350        )
4351
4352    @unsupported_args("format")
4353    def tochar_sql(self, expression: exp.ToChar) -> str:
4354        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
4355
4356    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4357        if not self.SUPPORTS_TO_NUMBER:
4358            self.unsupported("Unsupported TO_NUMBER function")
4359            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4360
4361        fmt = expression.args.get("format")
4362        if not fmt:
4363            self.unsupported("Conversion format is required for TO_NUMBER")
4364            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4365
4366        return self.func("TO_NUMBER", expression.this, fmt)
4367
4368    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4369        this = self.sql(expression, "this")
4370        kind = self.sql(expression, "kind")
4371        settings_sql = self.expressions(expression, key="settings", sep=" ")
4372        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4373        return f"{this}({kind}{args})"
4374
4375    def dictrange_sql(self, expression: exp.DictRange) -> str:
4376        this = self.sql(expression, "this")
4377        max = self.sql(expression, "max")
4378        min = self.sql(expression, "min")
4379        return f"{this}(MIN {min} MAX {max})"
4380
4381    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4382        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4383
4384    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4385        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4386
4387    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4388    def uniquekeyproperty_sql(
4389        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4390    ) -> str:
4391        return f"{prefix} ({self.expressions(expression, flat=True)})"
4392
4393    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4394    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4395        expressions = self.expressions(expression, flat=True)
4396        expressions = f" {self.wrap(expressions)}" if expressions else ""
4397        buckets = self.sql(expression, "buckets")
4398        kind = self.sql(expression, "kind")
4399        buckets = f" BUCKETS {buckets}" if buckets else ""
4400        order = self.sql(expression, "order")
4401        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4402
4403    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4404        return ""
4405
4406    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4407        expressions = self.expressions(expression, key="expressions", flat=True)
4408        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4409        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4410        buckets = self.sql(expression, "buckets")
4411        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4412
4413    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4414        this = self.sql(expression, "this")
4415        having = self.sql(expression, "having")
4416
4417        if having:
4418            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4419
4420        return self.func("ANY_VALUE", this)
4421
4422    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4423        transform = self.func("TRANSFORM", *expression.expressions)
4424        row_format_before = self.sql(expression, "row_format_before")
4425        row_format_before = f" {row_format_before}" if row_format_before else ""
4426        record_writer = self.sql(expression, "record_writer")
4427        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4428        using = f" USING {self.sql(expression, 'command_script')}"
4429        schema = self.sql(expression, "schema")
4430        schema = f" AS {schema}" if schema else ""
4431        row_format_after = self.sql(expression, "row_format_after")
4432        row_format_after = f" {row_format_after}" if row_format_after else ""
4433        record_reader = self.sql(expression, "record_reader")
4434        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4435        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4436
4437    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4438        key_block_size = self.sql(expression, "key_block_size")
4439        if key_block_size:
4440            return f"KEY_BLOCK_SIZE = {key_block_size}"
4441
4442        using = self.sql(expression, "using")
4443        if using:
4444            return f"USING {using}"
4445
4446        parser = self.sql(expression, "parser")
4447        if parser:
4448            return f"WITH PARSER {parser}"
4449
4450        comment = self.sql(expression, "comment")
4451        if comment:
4452            return f"COMMENT {comment}"
4453
4454        visible = expression.args.get("visible")
4455        if visible is not None:
4456            return "VISIBLE" if visible else "INVISIBLE"
4457
4458        engine_attr = self.sql(expression, "engine_attr")
4459        if engine_attr:
4460            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4461
4462        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4463        if secondary_engine_attr:
4464            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4465
4466        self.unsupported("Unsupported index constraint option.")
4467        return ""
4468
4469    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4470        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4471        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4472
4473    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4474        kind = self.sql(expression, "kind")
4475        kind = f"{kind} INDEX" if kind else "INDEX"
4476        this = self.sql(expression, "this")
4477        this = f" {this}" if this else ""
4478        index_type = self.sql(expression, "index_type")
4479        index_type = f" USING {index_type}" if index_type else ""
4480        expressions = self.expressions(expression, flat=True)
4481        expressions = f" ({expressions})" if expressions else ""
4482        options = self.expressions(expression, key="options", sep=" ")
4483        options = f" {options}" if options else ""
4484        return f"{kind}{this}{index_type}{expressions}{options}"
4485
4486    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4487        if self.NVL2_SUPPORTED:
4488            return self.function_fallback_sql(expression)
4489
4490        case = exp.Case().when(
4491            expression.this.is_(exp.null()).not_(copy=False),
4492            expression.args["true"],
4493            copy=False,
4494        )
4495        else_cond = expression.args.get("false")
4496        if else_cond:
4497            case.else_(else_cond, copy=False)
4498
4499        return self.sql(case)
4500
4501    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4502        this = self.sql(expression, "this")
4503        expr = self.sql(expression, "expression")
4504        position = self.sql(expression, "position")
4505        position = f", {position}" if position else ""
4506        iterator = self.sql(expression, "iterator")
4507        condition = self.sql(expression, "condition")
4508        condition = f" IF {condition}" if condition else ""
4509        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
4510
4511    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4512        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4513
4514    def opclass_sql(self, expression: exp.Opclass) -> str:
4515        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4516
4517    def _ml_sql(self, expression: exp.Func, name: str) -> str:
4518        model = self.sql(expression, "this")
4519        model = f"MODEL {model}"
4520        expr = expression.expression
4521        if expr:
4522            expr_sql = self.sql(expression, "expression")
4523            expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql
4524        else:
4525            expr_sql = None
4526
4527        parameters = self.sql(expression, "params_struct") or None
4528
4529        return self.func(name, model, expr_sql, parameters)
4530
4531    def predict_sql(self, expression: exp.Predict) -> str:
4532        return self._ml_sql(expression, "PREDICT")
4533
4534    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4535        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4536        return self._ml_sql(expression, name)
4537
4538    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
4539        return self._ml_sql(expression, "TRANSLATE")
4540
4541    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
4542        return self._ml_sql(expression, "FORECAST")
4543
4544    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4545        this_sql = self.sql(expression, "this")
4546        if isinstance(expression.this, exp.Table):
4547            this_sql = f"TABLE {this_sql}"
4548
4549        return self.func(
4550            "FEATURES_AT_TIME",
4551            this_sql,
4552            expression.args.get("time"),
4553            expression.args.get("num_rows"),
4554            expression.args.get("ignore_feature_nulls"),
4555        )
4556
4557    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
4558        this_sql = self.sql(expression, "this")
4559        if isinstance(expression.this, exp.Table):
4560            this_sql = f"TABLE {this_sql}"
4561
4562        query_table = self.sql(expression, "query_table")
4563        if isinstance(expression.args["query_table"], exp.Table):
4564            query_table = f"TABLE {query_table}"
4565
4566        return self.func(
4567            "VECTOR_SEARCH",
4568            this_sql,
4569            expression.args.get("column_to_search"),
4570            query_table,
4571            expression.args.get("query_column_to_search"),
4572            expression.args.get("top_k"),
4573            expression.args.get("distance_type"),
4574            expression.args.get("options"),
4575        )
4576
4577    def forin_sql(self, expression: exp.ForIn) -> str:
4578        this = self.sql(expression, "this")
4579        expression_sql = self.sql(expression, "expression")
4580        return f"FOR {this} DO {expression_sql}"
4581
4582    def refresh_sql(self, expression: exp.Refresh) -> str:
4583        this = self.sql(expression, "this")
4584        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
4585        return f"REFRESH {kind}{this}"
4586
4587    def toarray_sql(self, expression: exp.ToArray) -> str:
4588        arg = expression.this
4589        if not arg.type:
4590            from sqlglot.optimizer.annotate_types import annotate_types
4591
4592            arg = annotate_types(arg, dialect=self.dialect)
4593
4594        if arg.is_type(exp.DataType.Type.ARRAY):
4595            return self.sql(arg)
4596
4597        cond_for_null = arg.is_(exp.null())
4598        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4599
4600    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4601        this = expression.this
4602        time_format = self.format_time(expression)
4603
4604        if time_format:
4605            return self.sql(
4606                exp.cast(
4607                    exp.StrToTime(this=this, format=expression.args["format"]),
4608                    exp.DataType.Type.TIME,
4609                )
4610            )
4611
4612        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4613            return self.sql(this)
4614
4615        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4616
4617    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4618        this = expression.this
4619        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4620            return self.sql(this)
4621
4622        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4623
4624    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4625        this = expression.this
4626        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4627            return self.sql(this)
4628
4629        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4630
4631    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4632        this = expression.this
4633        time_format = self.format_time(expression)
4634        safe = expression.args.get("safe")
4635        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4636            return self.sql(
4637                exp.cast(
4638                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
4639                    exp.DataType.Type.DATE,
4640                )
4641            )
4642
4643        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4644            return self.sql(this)
4645
4646        if safe:
4647            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DataType.Type.DATE)))
4648
4649        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4650
4651    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4652        return self.sql(
4653            exp.func(
4654                "DATEDIFF",
4655                expression.this,
4656                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4657                "day",
4658            )
4659        )
4660
4661    def lastday_sql(self, expression: exp.LastDay) -> str:
4662        if self.LAST_DAY_SUPPORTS_DATE_PART:
4663            return self.function_fallback_sql(expression)
4664
4665        unit = expression.text("unit")
4666        if unit and unit != "MONTH":
4667            self.unsupported("Date parts are not supported in LAST_DAY.")
4668
4669        return self.func("LAST_DAY", expression.this)
4670
4671    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4672        from sqlglot.dialects.dialect import unit_to_str
4673
4674        return self.func(
4675            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4676        )
4677
4678    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4679        if self.CAN_IMPLEMENT_ARRAY_ANY:
4680            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4681            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4682            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4683            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4684
4685        from sqlglot.dialects import Dialect
4686
4687        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4688        if self.dialect.__class__ != Dialect:
4689            self.unsupported("ARRAY_ANY is unsupported")
4690
4691        return self.function_fallback_sql(expression)
4692
4693    def struct_sql(self, expression: exp.Struct) -> str:
4694        expression.set(
4695            "expressions",
4696            [
4697                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4698                if isinstance(e, exp.PropertyEQ)
4699                else e
4700                for e in expression.expressions
4701            ],
4702        )
4703
4704        return self.function_fallback_sql(expression)
4705
4706    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4707        low = self.sql(expression, "this")
4708        high = self.sql(expression, "expression")
4709
4710        return f"{low} TO {high}"
4711
4712    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4713        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4714        tables = f" {self.expressions(expression)}"
4715
4716        exists = " IF EXISTS" if expression.args.get("exists") else ""
4717
4718        on_cluster = self.sql(expression, "cluster")
4719        on_cluster = f" {on_cluster}" if on_cluster else ""
4720
4721        identity = self.sql(expression, "identity")
4722        identity = f" {identity} IDENTITY" if identity else ""
4723
4724        option = self.sql(expression, "option")
4725        option = f" {option}" if option else ""
4726
4727        partition = self.sql(expression, "partition")
4728        partition = f" {partition}" if partition else ""
4729
4730        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4731
4732    # This transpiles T-SQL's CONVERT function
4733    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4734    def convert_sql(self, expression: exp.Convert) -> str:
4735        to = expression.this
4736        value = expression.expression
4737        style = expression.args.get("style")
4738        safe = expression.args.get("safe")
4739        strict = expression.args.get("strict")
4740
4741        if not to or not value:
4742            return ""
4743
4744        # Retrieve length of datatype and override to default if not specified
4745        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4746            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4747
4748        transformed: t.Optional[exp.Expression] = None
4749        cast = exp.Cast if strict else exp.TryCast
4750
4751        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4752        if isinstance(style, exp.Literal) and style.is_int:
4753            from sqlglot.dialects.tsql import TSQL
4754
4755            style_value = style.name
4756            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4757            if not converted_style:
4758                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4759
4760            fmt = exp.Literal.string(converted_style)
4761
4762            if to.this == exp.DataType.Type.DATE:
4763                transformed = exp.StrToDate(this=value, format=fmt)
4764            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4765                transformed = exp.StrToTime(this=value, format=fmt)
4766            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4767                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4768            elif to.this == exp.DataType.Type.TEXT:
4769                transformed = exp.TimeToStr(this=value, format=fmt)
4770
4771        if not transformed:
4772            transformed = cast(this=value, to=to, safe=safe)
4773
4774        return self.sql(transformed)
4775
4776    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4777        this = expression.this
4778        if isinstance(this, exp.JSONPathWildcard):
4779            this = self.json_path_part(this)
4780            return f".{this}" if this else ""
4781
4782        if self.SAFE_JSON_PATH_KEY_RE.match(this):
4783            return f".{this}"
4784
4785        this = self.json_path_part(this)
4786        return (
4787            f"[{this}]"
4788            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
4789            else f".{this}"
4790        )
4791
4792    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
4793        this = self.json_path_part(expression.this)
4794        return f"[{this}]" if this else ""
4795
4796    def _simplify_unless_literal(self, expression: E) -> E:
4797        if not isinstance(expression, exp.Literal):
4798            from sqlglot.optimizer.simplify import simplify
4799
4800            expression = simplify(expression, dialect=self.dialect)
4801
4802        return expression
4803
4804    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
4805        this = expression.this
4806        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
4807            self.unsupported(
4808                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
4809            )
4810            return self.sql(this)
4811
4812        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
4813            # The first modifier here will be the one closest to the AggFunc's arg
4814            mods = sorted(
4815                expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
4816                key=lambda x: 0
4817                if isinstance(x, exp.HavingMax)
4818                else (1 if isinstance(x, exp.Order) else 2),
4819            )
4820
4821            if mods:
4822                mod = mods[0]
4823                this = expression.__class__(this=mod.this.copy())
4824                this.meta["inline"] = True
4825                mod.this.replace(this)
4826                return self.sql(expression.this)
4827
4828            agg_func = expression.find(exp.AggFunc)
4829
4830            if agg_func:
4831                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
4832                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
4833
4834        return f"{self.sql(expression, 'this')} {text}"
4835
4836    def _replace_line_breaks(self, string: str) -> str:
4837        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
4838        if self.pretty:
4839            return string.replace("\n", self.SENTINEL_LINE_BREAK)
4840        return string
4841
4842    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4843        option = self.sql(expression, "this")
4844
4845        if expression.expressions:
4846            upper = option.upper()
4847
4848            # Snowflake FILE_FORMAT options are separated by whitespace
4849            sep = " " if upper == "FILE_FORMAT" else ", "
4850
4851            # Databricks copy/format options do not set their list of values with EQ
4852            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4853            values = self.expressions(expression, flat=True, sep=sep)
4854            return f"{option}{op}({values})"
4855
4856        value = self.sql(expression, "expression")
4857
4858        if not value:
4859            return option
4860
4861        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4862
4863        return f"{option}{op}{value}"
4864
4865    def credentials_sql(self, expression: exp.Credentials) -> str:
4866        cred_expr = expression.args.get("credentials")
4867        if isinstance(cred_expr, exp.Literal):
4868            # Redshift case: CREDENTIALS <string>
4869            credentials = self.sql(expression, "credentials")
4870            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4871        else:
4872            # Snowflake case: CREDENTIALS = (...)
4873            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4874            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4875
4876        storage = self.sql(expression, "storage")
4877        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4878
4879        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4880        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4881
4882        iam_role = self.sql(expression, "iam_role")
4883        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4884
4885        region = self.sql(expression, "region")
4886        region = f" REGION {region}" if region else ""
4887
4888        return f"{credentials}{storage}{encryption}{iam_role}{region}"
4889
4890    def copy_sql(self, expression: exp.Copy) -> str:
4891        this = self.sql(expression, "this")
4892        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4893
4894        credentials = self.sql(expression, "credentials")
4895        credentials = self.seg(credentials) if credentials else ""
4896        files = self.expressions(expression, key="files", flat=True)
4897        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
4898
4899        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4900        params = self.expressions(
4901            expression,
4902            key="params",
4903            sep=sep,
4904            new_line=True,
4905            skip_last=True,
4906            skip_first=True,
4907            indent=self.COPY_PARAMS_ARE_WRAPPED,
4908        )
4909
4910        if params:
4911            if self.COPY_PARAMS_ARE_WRAPPED:
4912                params = f" WITH ({params})"
4913            elif not self.pretty and (files or credentials):
4914                params = f" {params}"
4915
4916        return f"COPY{this}{kind} {files}{credentials}{params}"
4917
4918    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4919        return ""
4920
4921    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4922        on_sql = "ON" if expression.args.get("on") else "OFF"
4923        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4924        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4925        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4926        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4927
4928        if filter_col or retention_period:
4929            on_sql = self.func("ON", filter_col, retention_period)
4930
4931        return f"DATA_DELETION={on_sql}"
4932
4933    def maskingpolicycolumnconstraint_sql(
4934        self, expression: exp.MaskingPolicyColumnConstraint
4935    ) -> str:
4936        this = self.sql(expression, "this")
4937        expressions = self.expressions(expression, flat=True)
4938        expressions = f" USING ({expressions})" if expressions else ""
4939        return f"MASKING POLICY {this}{expressions}"
4940
4941    def gapfill_sql(self, expression: exp.GapFill) -> str:
4942        this = self.sql(expression, "this")
4943        this = f"TABLE {this}"
4944        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
4945
4946    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4947        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
4948
4949    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4950        this = self.sql(expression, "this")
4951        expr = expression.expression
4952
4953        if isinstance(expr, exp.Func):
4954            # T-SQL's CLR functions are case sensitive
4955            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4956        else:
4957            expr = self.sql(expression, "expression")
4958
4959        return self.scope_resolution(expr, this)
4960
4961    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4962        if self.PARSE_JSON_NAME is None:
4963            return self.sql(expression.this)
4964
4965        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
4966
4967    def rand_sql(self, expression: exp.Rand) -> str:
4968        lower = self.sql(expression, "lower")
4969        upper = self.sql(expression, "upper")
4970
4971        if lower and upper:
4972            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4973        return self.func("RAND", expression.this)
4974
4975    def changes_sql(self, expression: exp.Changes) -> str:
4976        information = self.sql(expression, "information")
4977        information = f"INFORMATION => {information}"
4978        at_before = self.sql(expression, "at_before")
4979        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4980        end = self.sql(expression, "end")
4981        end = f"{self.seg('')}{end}" if end else ""
4982
4983        return f"CHANGES ({information}){at_before}{end}"
4984
4985    def pad_sql(self, expression: exp.Pad) -> str:
4986        prefix = "L" if expression.args.get("is_left") else "R"
4987
4988        fill_pattern = self.sql(expression, "fill_pattern") or None
4989        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4990            fill_pattern = "' '"
4991
4992        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
4993
4994    def summarize_sql(self, expression: exp.Summarize) -> str:
4995        table = " TABLE" if expression.args.get("table") else ""
4996        return f"SUMMARIZE{table} {self.sql(expression.this)}"
4997
4998    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
4999        generate_series = exp.GenerateSeries(**expression.args)
5000
5001        parent = expression.parent
5002        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5003            parent = parent.parent
5004
5005        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5006            return self.sql(exp.Unnest(expressions=[generate_series]))
5007
5008        if isinstance(parent, exp.Select):
5009            self.unsupported("GenerateSeries projection unnesting is not supported.")
5010
5011        return self.sql(generate_series)
5012
5013    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5014        if self.SUPPORTS_CONVERT_TIMEZONE:
5015            return self.function_fallback_sql(expression)
5016
5017        source_tz = expression.args.get("source_tz")
5018        target_tz = expression.args.get("target_tz")
5019        timestamp = expression.args.get("timestamp")
5020
5021        if source_tz and timestamp:
5022            timestamp = exp.AtTimeZone(
5023                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
5024            )
5025
5026        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5027
5028        return self.sql(expr)
5029
5030    def json_sql(self, expression: exp.JSON) -> str:
5031        this = self.sql(expression, "this")
5032        this = f" {this}" if this else ""
5033
5034        _with = expression.args.get("with_")
5035
5036        if _with is None:
5037            with_sql = ""
5038        elif not _with:
5039            with_sql = " WITHOUT"
5040        else:
5041            with_sql = " WITH"
5042
5043        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5044
5045        return f"JSON{this}{with_sql}{unique_sql}"
5046
5047    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5048        path = self.sql(expression, "path")
5049        returning = self.sql(expression, "returning")
5050        returning = f" RETURNING {returning}" if returning else ""
5051
5052        on_condition = self.sql(expression, "on_condition")
5053        on_condition = f" {on_condition}" if on_condition else ""
5054
5055        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5056
5057    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5058        else_ = "ELSE " if expression.args.get("else_") else ""
5059        condition = self.sql(expression, "expression")
5060        condition = f"WHEN {condition} THEN " if condition else else_
5061        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5062        return f"{condition}{insert}"
5063
5064    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5065        kind = self.sql(expression, "kind")
5066        expressions = self.seg(self.expressions(expression, sep=" "))
5067        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
5068        return res
5069
5070    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5071        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5072        empty = expression.args.get("empty")
5073        empty = (
5074            f"DEFAULT {empty} ON EMPTY"
5075            if isinstance(empty, exp.Expression)
5076            else self.sql(expression, "empty")
5077        )
5078
5079        error = expression.args.get("error")
5080        error = (
5081            f"DEFAULT {error} ON ERROR"
5082            if isinstance(error, exp.Expression)
5083            else self.sql(expression, "error")
5084        )
5085
5086        if error and empty:
5087            error = (
5088                f"{empty} {error}"
5089                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5090                else f"{error} {empty}"
5091            )
5092            empty = ""
5093
5094        null = self.sql(expression, "null")
5095
5096        return f"{empty}{error}{null}"
5097
5098    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5099        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5100        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5101
5102    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5103        this = self.sql(expression, "this")
5104        path = self.sql(expression, "path")
5105
5106        passing = self.expressions(expression, "passing")
5107        passing = f" PASSING {passing}" if passing else ""
5108
5109        on_condition = self.sql(expression, "on_condition")
5110        on_condition = f" {on_condition}" if on_condition else ""
5111
5112        path = f"{path}{passing}{on_condition}"
5113
5114        return self.func("JSON_EXISTS", this, path)
5115
5116    def _add_arrayagg_null_filter(
5117        self,
5118        array_agg_sql: str,
5119        array_agg_expr: exp.ArrayAgg,
5120        column_expr: exp.Expression,
5121    ) -> str:
5122        """
5123        Add NULL filter to ARRAY_AGG if dialect requires it.
5124
5125        Args:
5126            array_agg_sql: The generated ARRAY_AGG SQL string
5127            array_agg_expr: The ArrayAgg expression node
5128            column_expr: The column/expression to filter (before ORDER BY wrapping)
5129
5130        Returns:
5131            SQL string with FILTER clause added if needed
5132        """
5133        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5134        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5135        if not (
5136            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5137        ):
5138            return array_agg_sql
5139
5140        parent = array_agg_expr.parent
5141        if isinstance(parent, exp.Filter):
5142            parent_cond = parent.expression.this
5143            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5144        elif column_expr.find(exp.Column):
5145            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5146            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5147            this_sql = (
5148                self.expressions(column_expr)
5149                if isinstance(column_expr, exp.Distinct)
5150                else self.sql(column_expr)
5151            )
5152            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5153
5154        return array_agg_sql
5155
5156    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5157        array_agg = self.function_fallback_sql(expression)
5158        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
5159
5160    def slice_sql(self, expression: exp.Slice) -> str:
5161        step = self.sql(expression, "step")
5162        end = self.sql(expression.expression)
5163        begin = self.sql(expression.this)
5164
5165        sql = f"{end}:{step}" if step else end
5166        return f"{begin}:{sql}" if sql else f"{begin}:"
5167
5168    def apply_sql(self, expression: exp.Apply) -> str:
5169        this = self.sql(expression, "this")
5170        expr = self.sql(expression, "expression")
5171
5172        return f"{this} APPLY({expr})"
5173
5174    def _grant_or_revoke_sql(
5175        self,
5176        expression: exp.Grant | exp.Revoke,
5177        keyword: str,
5178        preposition: str,
5179        grant_option_prefix: str = "",
5180        grant_option_suffix: str = "",
5181    ) -> str:
5182        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5183
5184        kind = self.sql(expression, "kind")
5185        kind = f" {kind}" if kind else ""
5186
5187        securable = self.sql(expression, "securable")
5188        securable = f" {securable}" if securable else ""
5189
5190        principals = self.expressions(expression, key="principals", flat=True)
5191
5192        if not expression.args.get("grant_option"):
5193            grant_option_prefix = grant_option_suffix = ""
5194
5195        # cascade for revoke only
5196        cascade = self.sql(expression, "cascade")
5197        cascade = f" {cascade}" if cascade else ""
5198
5199        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5200
5201    def grant_sql(self, expression: exp.Grant) -> str:
5202        return self._grant_or_revoke_sql(
5203            expression,
5204            keyword="GRANT",
5205            preposition="TO",
5206            grant_option_suffix=" WITH GRANT OPTION",
5207        )
5208
5209    def revoke_sql(self, expression: exp.Revoke) -> str:
5210        return self._grant_or_revoke_sql(
5211            expression,
5212            keyword="REVOKE",
5213            preposition="FROM",
5214            grant_option_prefix="GRANT OPTION FOR ",
5215        )
5216
5217    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5218        this = self.sql(expression, "this")
5219        columns = self.expressions(expression, flat=True)
5220        columns = f"({columns})" if columns else ""
5221
5222        return f"{this}{columns}"
5223
5224    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5225        this = self.sql(expression, "this")
5226
5227        kind = self.sql(expression, "kind")
5228        kind = f"{kind} " if kind else ""
5229
5230        return f"{kind}{this}"
5231
5232    def columns_sql(self, expression: exp.Columns) -> str:
5233        func = self.function_fallback_sql(expression)
5234        if expression.args.get("unpack"):
5235            func = f"*{func}"
5236
5237        return func
5238
5239    def overlay_sql(self, expression: exp.Overlay) -> str:
5240        this = self.sql(expression, "this")
5241        expr = self.sql(expression, "expression")
5242        from_sql = self.sql(expression, "from_")
5243        for_sql = self.sql(expression, "for_")
5244        for_sql = f" FOR {for_sql}" if for_sql else ""
5245
5246        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
5247
5248    @unsupported_args("format")
5249    def todouble_sql(self, expression: exp.ToDouble) -> str:
5250        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5251        return self.sql(cast(this=expression.this, to=exp.DataType.build(exp.DataType.Type.DOUBLE)))
5252
5253    def string_sql(self, expression: exp.String) -> str:
5254        this = expression.this
5255        zone = expression.args.get("zone")
5256
5257        if zone:
5258            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5259            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5260            # set for source_tz to transpile the time conversion before the STRING cast
5261            this = exp.ConvertTimezone(
5262                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5263            )
5264
5265        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
5266
5267    def median_sql(self, expression: exp.Median) -> str:
5268        if not self.SUPPORTS_MEDIAN:
5269            return self.sql(
5270                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5271            )
5272
5273        return self.function_fallback_sql(expression)
5274
5275    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5276        filler = self.sql(expression, "this")
5277        filler = f" {filler}" if filler else ""
5278        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5279        return f"TRUNCATE{filler} {with_count}"
5280
5281    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5282        if self.SUPPORTS_UNIX_SECONDS:
5283            return self.function_fallback_sql(expression)
5284
5285        start_ts = exp.cast(
5286            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
5287        )
5288
5289        return self.sql(
5290            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5291        )
5292
5293    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5294        dim = expression.expression
5295
5296        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5297        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5298            if not (dim.is_int and dim.name == "1"):
5299                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5300            dim = None
5301
5302        # If dimension is required but not specified, default initialize it
5303        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5304            dim = exp.Literal.number(1)
5305
5306        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5307
5308    def attach_sql(self, expression: exp.Attach) -> str:
5309        this = self.sql(expression, "this")
5310        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5311        expressions = self.expressions(expression)
5312        expressions = f" ({expressions})" if expressions else ""
5313
5314        return f"ATTACH{exists_sql} {this}{expressions}"
5315
5316    def detach_sql(self, expression: exp.Detach) -> str:
5317        this = self.sql(expression, "this")
5318        # the DATABASE keyword is required if IF EXISTS is set
5319        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
5320        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5321        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
5322
5323        return f"DETACH{exists_sql} {this}"
5324
5325    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5326        this = self.sql(expression, "this")
5327        value = self.sql(expression, "expression")
5328        value = f" {value}" if value else ""
5329        return f"{this}{value}"
5330
5331    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5332        return (
5333            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5334        )
5335
5336    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5337        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5338        encode = f"{encode} {self.sql(expression, 'this')}"
5339
5340        properties = expression.args.get("properties")
5341        if properties:
5342            encode = f"{encode} {self.properties(properties)}"
5343
5344        return encode
5345
5346    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5347        this = self.sql(expression, "this")
5348        include = f"INCLUDE {this}"
5349
5350        column_def = self.sql(expression, "column_def")
5351        if column_def:
5352            include = f"{include} {column_def}"
5353
5354        alias = self.sql(expression, "alias")
5355        if alias:
5356            include = f"{include} AS {alias}"
5357
5358        return include
5359
5360    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5361        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
5362        name = f"{prefix} {self.sql(expression, 'this')}"
5363        return self.func("XMLELEMENT", name, *expression.expressions)
5364
5365    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5366        this = self.sql(expression, "this")
5367        expr = self.sql(expression, "expression")
5368        expr = f"({expr})" if expr else ""
5369        return f"{this}{expr}"
5370
5371    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5372        partitions = self.expressions(expression, "partition_expressions")
5373        create = self.expressions(expression, "create_expressions")
5374        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
5375
5376    def partitionbyrangepropertydynamic_sql(
5377        self, expression: exp.PartitionByRangePropertyDynamic
5378    ) -> str:
5379        start = self.sql(expression, "start")
5380        end = self.sql(expression, "end")
5381
5382        every = expression.args["every"]
5383        if isinstance(every, exp.Interval) and every.this.is_string:
5384            every.this.replace(exp.Literal.number(every.name))
5385
5386        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5387
5388    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5389        name = self.sql(expression, "this")
5390        values = self.expressions(expression, flat=True)
5391
5392        return f"NAME {name} VALUE {values}"
5393
5394    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5395        kind = self.sql(expression, "kind")
5396        sample = self.sql(expression, "sample")
5397        return f"SAMPLE {sample} {kind}"
5398
5399    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5400        kind = self.sql(expression, "kind")
5401        option = self.sql(expression, "option")
5402        option = f" {option}" if option else ""
5403        this = self.sql(expression, "this")
5404        this = f" {this}" if this else ""
5405        columns = self.expressions(expression)
5406        columns = f" {columns}" if columns else ""
5407        return f"{kind}{option} STATISTICS{this}{columns}"
5408
5409    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5410        this = self.sql(expression, "this")
5411        columns = self.expressions(expression)
5412        inner_expression = self.sql(expression, "expression")
5413        inner_expression = f" {inner_expression}" if inner_expression else ""
5414        update_options = self.sql(expression, "update_options")
5415        update_options = f" {update_options} UPDATE" if update_options else ""
5416        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
5417
5418    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5419        kind = self.sql(expression, "kind")
5420        kind = f" {kind}" if kind else ""
5421        return f"DELETE{kind} STATISTICS"
5422
5423    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5424        inner_expression = self.sql(expression, "expression")
5425        return f"LIST CHAINED ROWS{inner_expression}"
5426
5427    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5428        kind = self.sql(expression, "kind")
5429        this = self.sql(expression, "this")
5430        this = f" {this}" if this else ""
5431        inner_expression = self.sql(expression, "expression")
5432        return f"VALIDATE {kind}{this}{inner_expression}"
5433
5434    def analyze_sql(self, expression: exp.Analyze) -> str:
5435        options = self.expressions(expression, key="options", sep=" ")
5436        options = f" {options}" if options else ""
5437        kind = self.sql(expression, "kind")
5438        kind = f" {kind}" if kind else ""
5439        this = self.sql(expression, "this")
5440        this = f" {this}" if this else ""
5441        mode = self.sql(expression, "mode")
5442        mode = f" {mode}" if mode else ""
5443        properties = self.sql(expression, "properties")
5444        properties = f" {properties}" if properties else ""
5445        partition = self.sql(expression, "partition")
5446        partition = f" {partition}" if partition else ""
5447        inner_expression = self.sql(expression, "expression")
5448        inner_expression = f" {inner_expression}" if inner_expression else ""
5449        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5450
5451    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5452        this = self.sql(expression, "this")
5453        namespaces = self.expressions(expression, key="namespaces")
5454        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5455        passing = self.expressions(expression, key="passing")
5456        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5457        columns = self.expressions(expression, key="columns")
5458        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5459        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5460        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5461
5462    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5463        this = self.sql(expression, "this")
5464        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
5465
5466    def export_sql(self, expression: exp.Export) -> str:
5467        this = self.sql(expression, "this")
5468        connection = self.sql(expression, "connection")
5469        connection = f"WITH CONNECTION {connection} " if connection else ""
5470        options = self.sql(expression, "options")
5471        return f"EXPORT DATA {connection}{options} AS {this}"
5472
5473    def declare_sql(self, expression: exp.Declare) -> str:
5474        return f"DECLARE {self.expressions(expression, flat=True)}"
5475
5476    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5477        variables = self.expressions(expression, "this")
5478        default = self.sql(expression, "default")
5479        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
5480
5481        kind = self.sql(expression, "kind")
5482        if isinstance(expression.args.get("kind"), exp.Schema):
5483            kind = f"TABLE {kind}"
5484
5485        kind = f" {kind}" if kind else ""
5486
5487        return f"{variables}{kind}{default}"
5488
5489    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5490        kind = self.sql(expression, "kind")
5491        this = self.sql(expression, "this")
5492        set = self.sql(expression, "expression")
5493        using = self.sql(expression, "using")
5494        using = f" USING {using}" if using else ""
5495
5496        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5497
5498        return f"{kind_sql} {this} SET {set}{using}"
5499
5500    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5501        params = self.expressions(expression, key="params", flat=True)
5502        return self.func(expression.name, *expression.expressions) + f"({params})"
5503
5504    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5505        return self.func(expression.name, *expression.expressions)
5506
5507    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5508        return self.anonymousaggfunc_sql(expression)
5509
5510    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5511        return self.parameterizedagg_sql(expression)
5512
5513    def show_sql(self, expression: exp.Show) -> str:
5514        self.unsupported("Unsupported SHOW statement")
5515        return ""
5516
5517    def install_sql(self, expression: exp.Install) -> str:
5518        self.unsupported("Unsupported INSTALL statement")
5519        return ""
5520
5521    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5522        # Snowflake GET/PUT statements:
5523        #   PUT <file> <internalStage> <properties>
5524        #   GET <internalStage> <file> <properties>
5525        props = expression.args.get("properties")
5526        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5527        this = self.sql(expression, "this")
5528        target = self.sql(expression, "target")
5529
5530        if isinstance(expression, exp.Put):
5531            return f"PUT {this} {target}{props_sql}"
5532        else:
5533            return f"GET {target} {this}{props_sql}"
5534
5535    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
5536        this = self.sql(expression, "this")
5537        expr = self.sql(expression, "expression")
5538        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5539        return f"TRANSLATE({this} USING {expr}{with_error})"
5540
5541    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5542        if self.SUPPORTS_DECODE_CASE:
5543            return self.func("DECODE", *expression.expressions)
5544
5545        expression, *expressions = expression.expressions
5546
5547        ifs = []
5548        for search, result in zip(expressions[::2], expressions[1::2]):
5549            if isinstance(search, exp.Literal):
5550                ifs.append(exp.If(this=expression.eq(search), true=result))
5551            elif isinstance(search, exp.Null):
5552                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5553            else:
5554                if isinstance(search, exp.Binary):
5555                    search = exp.paren(search)
5556
5557                cond = exp.or_(
5558                    expression.eq(search),
5559                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5560                    copy=False,
5561                )
5562                ifs.append(exp.If(this=cond, true=result))
5563
5564        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5565        return self.sql(case)
5566
5567    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5568        this = self.sql(expression, "this")
5569        this = self.seg(this, sep="")
5570        dimensions = self.expressions(
5571            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5572        )
5573        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5574        metrics = self.expressions(
5575            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5576        )
5577        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5578        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
5579        facts = self.seg(f"FACTS {facts}") if facts else ""
5580        where = self.sql(expression, "where")
5581        where = self.seg(f"WHERE {where}") if where else ""
5582        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
5583        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5584
5585    def getextract_sql(self, expression: exp.GetExtract) -> str:
5586        this = expression.this
5587        expr = expression.expression
5588
5589        if not this.type or not expression.type:
5590            from sqlglot.optimizer.annotate_types import annotate_types
5591
5592            this = annotate_types(this, dialect=self.dialect)
5593
5594        if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)):
5595            return self.sql(exp.Bracket(this=this, expressions=[expr]))
5596
5597        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
5598
5599    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
5600        return self.sql(
5601            exp.DateAdd(
5602                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
5603                expression=expression.this,
5604                unit=exp.var("DAY"),
5605            )
5606        )
5607
5608    def space_sql(self: Generator, expression: exp.Space) -> str:
5609        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
5610
5611    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
5612        return f"BUILD {self.sql(expression, 'this')}"
5613
5614    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
5615        method = self.sql(expression, "method")
5616        kind = expression.args.get("kind")
5617        if not kind:
5618            return f"REFRESH {method}"
5619
5620        every = self.sql(expression, "every")
5621        unit = self.sql(expression, "unit")
5622        every = f" EVERY {every} {unit}" if every else ""
5623        starts = self.sql(expression, "starts")
5624        starts = f" STARTS {starts}" if starts else ""
5625
5626        return f"REFRESH {method} ON {kind}{every}{starts}"
5627
5628    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
5629        self.unsupported("The model!attribute syntax is not supported")
5630        return ""
5631
5632    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
5633        return self.func("DIRECTORY", expression.this)
5634
5635    def uuid_sql(self, expression: exp.Uuid) -> str:
5636        is_string = expression.args.get("is_string", False)
5637        uuid_func_sql = self.func("UUID")
5638
5639        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
5640            return self.sql(
5641                exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect)
5642            )
5643
5644        return uuid_func_sql
5645
5646    def initcap_sql(self, expression: exp.Initcap) -> str:
5647        delimiters = expression.expression
5648
5649        if delimiters:
5650            # do not generate delimiters arg if we are round-tripping from default delimiters
5651            if (
5652                delimiters.is_string
5653                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
5654            ):
5655                delimiters = None
5656            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
5657                self.unsupported("INITCAP does not support custom delimiters")
5658                delimiters = None
5659
5660        return self.func("INITCAP", expression.this, delimiters)
5661
5662    def localtime_sql(self, expression: exp.Localtime) -> str:
5663        this = expression.this
5664        return self.func("LOCALTIME", this) if this else "LOCALTIME"
5665
5666    def localtimestamp_sql(self, expression: exp.Localtime) -> str:
5667        this = expression.this
5668        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
5669
5670    def weekstart_sql(self, expression: exp.WeekStart) -> str:
5671        this = expression.this.name.upper()
5672        if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
5673            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
5674            return "WEEK"
5675
5676        return self.func("WEEK", expression.this)
5677
5678    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
5679        this = self.expressions(expression)
5680        charset = self.sql(expression, "charset")
5681        using = f" USING {charset}" if charset else ""
5682        return self.func(name, this + using)
5683
5684    def block_sql(self, expression: exp.Block) -> str:
5685        expressions = self.expressions(expression, sep="; ", flat=True)
5686        return f"{expressions}" if expressions else ""
5687
5688    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
5689        self.unsupported("Unsupported Stored Procedure syntax")
5690        return ""
5691
5692    def ifblock_sql(self, expression: exp.IfBlock) -> str:
5693        self.unsupported("Unsupported If block syntax")
5694        return ""
5695
5696    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
5697        self.unsupported("Unsupported While block syntax")
5698        return ""
5699
5700    def execute_sql(self, expression: exp.Execute) -> str:
5701        self.unsupported("Unsupported Execute syntax")
5702        return ""
5703
5704    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
5705        self.unsupported("Unsupported Execute syntax")
5706        return ""
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]]:
31def unsupported_args(
32    *args: t.Union[str, t.Tuple[str, str]],
33) -> t.Callable[[GeneratorMethod], GeneratorMethod]:
34    """
35    Decorator that can be used to mark certain args of an `Expression` subclass as unsupported.
36    It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
37    """
38    diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {}
39    for arg in args:
40        if isinstance(arg, str):
41            diagnostic_by_arg[arg] = None
42        else:
43            diagnostic_by_arg[arg[0]] = arg[1]
44
45    def decorator(func: GeneratorMethod) -> GeneratorMethod:
46        @wraps(func)
47        def _func(generator: G, expression: E) -> str:
48            expression_name = expression.__class__.__name__
49            dialect_name = generator.dialect.__class__.__name__
50
51            for arg_name, diagnostic in diagnostic_by_arg.items():
52                if expression.args.get(arg_name):
53                    diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format(
54                        arg_name, expression_name, dialect_name
55                    )
56                    generator.unsupported(diagnostic)
57
58            return func(generator, expression)
59
60        return _func
61
62    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:
  76class Generator(metaclass=_Generator):
  77    """
  78    Generator converts a given syntax tree to the corresponding SQL string.
  79
  80    Args:
  81        pretty: Whether to format the produced SQL string.
  82            Default: False.
  83        identify: Determines when an identifier should be quoted. Possible values are:
  84            False (default): Never quote, except in cases where it's mandatory by the dialect.
  85            True: Always quote except for specials cases.
  86            'safe': Only quote identifiers that are case insensitive.
  87        normalize: Whether to normalize identifiers to lowercase.
  88            Default: False.
  89        pad: The pad size in a formatted string. For example, this affects the indentation of
  90            a projection in a query, relative to its nesting level.
  91            Default: 2.
  92        indent: The indentation size in a formatted string. For example, this affects the
  93            indentation of subqueries and filters under a `WHERE` clause.
  94            Default: 2.
  95        normalize_functions: How to normalize function names. Possible values are:
  96            "upper" or True (default): Convert names to uppercase.
  97            "lower": Convert names to lowercase.
  98            False: Disables function name normalization.
  99        unsupported_level: Determines the generator's behavior when it encounters unsupported expressions.
 100            Default ErrorLevel.WARN.
 101        max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.
 102            This is only relevant if unsupported_level is ErrorLevel.RAISE.
 103            Default: 3
 104        leading_comma: Whether the comma is leading or trailing in select expressions.
 105            This is only relevant when generating in pretty mode.
 106            Default: False
 107        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
 108            The default is on the smaller end because the length only represents a segment and not the true
 109            line length.
 110            Default: 80
 111        comments: Whether to preserve comments in the output SQL code.
 112            Default: True
 113    """
 114
 115    TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = {
 116        **JSON_PATH_PART_TRANSFORMS,
 117        exp.Adjacent: lambda self, e: self.binary(e, "-|-"),
 118        exp.AllowedValuesProperty: lambda self,
 119        e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}",
 120        exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"),
 121        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "),
 122        exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"),
 123        exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"),
 124        exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
 125        exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
 126        exp.CaseSpecificColumnConstraint: lambda _,
 127        e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC",
 128        exp.Ceil: lambda self, e: self.ceil_floor(e),
 129        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
 130        exp.CharacterSetProperty: lambda self,
 131        e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}",
 132        exp.ClusteredColumnConstraint: lambda self,
 133        e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})",
 134        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
 135        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
 136        exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}",
 137        exp.ConvertToCharset: lambda self, e: self.func(
 138            "CONVERT", e.this, e.args["dest"], e.args.get("source")
 139        ),
 140        exp.CopyGrantsProperty: lambda *_: "COPY GRANTS",
 141        exp.CredentialsProperty: lambda self,
 142        e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})",
 143        exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG",
 144        exp.SessionUser: lambda *_: "SESSION_USER",
 145        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
 146        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
 147        exp.DynamicProperty: lambda *_: "DYNAMIC",
 148        exp.EmptyProperty: lambda *_: "EMPTY",
 149        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
 150        exp.EndStatement: lambda *_: "END",
 151        exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})",
 152        exp.EphemeralColumnConstraint: lambda self,
 153        e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}",
 154        exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}",
 155        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
 156        exp.Except: lambda self, e: self.set_operations(e),
 157        exp.ExternalProperty: lambda *_: "EXTERNAL",
 158        exp.Floor: lambda self, e: self.ceil_floor(e),
 159        exp.Get: lambda self, e: self.get_put_sql(e),
 160        exp.GlobalProperty: lambda *_: "GLOBAL",
 161        exp.HeapProperty: lambda *_: "HEAP",
 162        exp.IcebergProperty: lambda *_: "ICEBERG",
 163        exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})",
 164        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
 165        exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
 166        exp.Intersect: lambda self, e: self.set_operations(e),
 167        exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
 168        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)),
 169        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"),
 170        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"),
 171        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"),
 172        exp.LanguageProperty: lambda self, e: self.naked_property(e),
 173        exp.LocationProperty: lambda self, e: self.naked_property(e),
 174        exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG",
 175        exp.MaterializedProperty: lambda *_: "MATERIALIZED",
 176        exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}",
 177        exp.NonClusteredColumnConstraint: lambda self,
 178        e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})",
 179        exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX",
 180        exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION",
 181        exp.OnCommitProperty: lambda _,
 182        e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS",
 183        exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
 184        exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
 185        exp.Operator: lambda self, e: self.binary(e, ""),  # The operator is produced in `binary`
 186        exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
 187        exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"),
 188        exp.ExtendsRight: lambda self, e: self.binary(e, "&>"),
 189        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
 190        exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression),
 191        exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression),
 192        exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}",
 193        exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}",
 194        exp.ProjectionPolicyColumnConstraint: lambda self,
 195        e: f"PROJECTION POLICY {self.sql(e, 'this')}",
 196        exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL",
 197        exp.Put: lambda self, e: self.get_put_sql(e),
 198        exp.RemoteWithConnectionModelProperty: lambda self,
 199        e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
 200        exp.ReturnsProperty: lambda self, e: (
 201            "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
 202        ),
 203        exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}",
 204        exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
 205        exp.SecureProperty: lambda *_: "SECURE",
 206        exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}",
 207        exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
 208        exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
 209        exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
 210        exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}",
 211        exp.SqlReadWriteProperty: lambda _, e: e.name,
 212        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}",
 213        exp.StabilityProperty: lambda _, e: e.name,
 214        exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}",
 215        exp.StreamingTableProperty: lambda *_: "STREAMING",
 216        exp.StrictProperty: lambda *_: "STRICT",
 217        exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}",
 218        exp.TableColumn: lambda self, e: self.sql(e.this),
 219        exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})",
 220        exp.TemporaryProperty: lambda *_: "TEMPORARY",
 221        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
 222        exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}",
 223        exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
 224        exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
 225        exp.TransientProperty: lambda *_: "TRANSIENT",
 226        exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}",
 227        exp.Union: lambda self, e: self.set_operations(e),
 228        exp.UnloggedProperty: lambda *_: "UNLOGGED",
 229        exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}",
 230        exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}",
 231        exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE",
 232        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))),
 233        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))),
 234        exp.UtcTimestamp: lambda self, e: self.sql(
 235            exp.CurrentTimestamp(this=exp.Literal.string("UTC"))
 236        ),
 237        exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}",
 238        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
 239        exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}",
 240        exp.VolatileProperty: lambda *_: "VOLATILE",
 241        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
 242        exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}",
 243        exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}",
 244        exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}",
 245        exp.ForceProperty: lambda *_: "FORCE",
 246    }
 247
 248    # Whether null ordering is supported in order by
 249    # True: Full Support, None: No support, False: No support for certain cases
 250    # such as window specifications, aggregate functions etc
 251    NULL_ORDERING_SUPPORTED: t.Optional[bool] = True
 252
 253    # Whether ignore nulls is inside the agg or outside.
 254    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER
 255    IGNORE_NULLS_IN_FUNC = False
 256
 257    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 258    LOCKING_READS_SUPPORTED = False
 259
 260    # Whether the EXCEPT and INTERSECT operations can return duplicates
 261    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True
 262
 263    # Wrap derived values in parens, usually standard but spark doesn't support it
 264    WRAP_DERIVED_VALUES = True
 265
 266    # Whether create function uses an AS before the RETURN
 267    CREATE_FUNCTION_RETURN_AS = True
 268
 269    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
 270    MATCHED_BY_SOURCE = True
 271
 272    # Whether the INTERVAL expression works only with values like '1 day'
 273    SINGLE_STRING_INTERVAL = False
 274
 275    # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs
 276    INTERVAL_ALLOWS_PLURAL_FORM = True
 277
 278    # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
 279    LIMIT_FETCH = "ALL"
 280
 281    # Whether limit and fetch allows expresions or just limits
 282    LIMIT_ONLY_LITERALS = False
 283
 284    # Whether a table is allowed to be renamed with a db
 285    RENAME_TABLE_WITH_DB = True
 286
 287    # The separator for grouping sets and rollups
 288    GROUPINGS_SEP = ","
 289
 290    # The string used for creating an index on a table
 291    INDEX_ON = "ON"
 292
 293    # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT")
 294    INOUT_SEPARATOR = " "
 295
 296    # Whether join hints should be generated
 297    JOIN_HINTS = True
 298
 299    # Whether directed joins are supported
 300    DIRECTED_JOINS = False
 301
 302    # Whether table hints should be generated
 303    TABLE_HINTS = True
 304
 305    # Whether query hints should be generated
 306    QUERY_HINTS = True
 307
 308    # What kind of separator to use for query hints
 309    QUERY_HINT_SEP = ", "
 310
 311    # Whether comparing against booleans (e.g. x IS TRUE) is supported
 312    IS_BOOL_ALLOWED = True
 313
 314    # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
 315    DUPLICATE_KEY_UPDATE_WITH_SET = True
 316
 317    # Whether to generate the limit as TOP <value> instead of LIMIT <value>
 318    LIMIT_IS_TOP = False
 319
 320    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
 321    RETURNING_END = True
 322
 323    # Whether to generate an unquoted value for EXTRACT's date part argument
 324    EXTRACT_ALLOWS_QUOTES = True
 325
 326    # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
 327    TZ_TO_WITH_TIME_ZONE = False
 328
 329    # Whether the NVL2 function is supported
 330    NVL2_SUPPORTED = True
 331
 332    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 333    SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE")
 334
 335    # Whether VALUES statements can be used as derived tables.
 336    # MySQL 5 and Redshift do not allow this, so when False, it will convert
 337    # SELECT * VALUES into SELECT UNION
 338    VALUES_AS_TABLE = True
 339
 340    # Whether the word COLUMN is included when adding a column with ALTER TABLE
 341    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True
 342
 343    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
 344    UNNEST_WITH_ORDINALITY = True
 345
 346    # Whether FILTER (WHERE cond) can be used for conditional aggregation
 347    AGGREGATE_FILTER_SUPPORTED = True
 348
 349    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
 350    SEMI_ANTI_JOIN_WITH_SIDE = True
 351
 352    # Whether to include the type of a computed column in the CREATE DDL
 353    COMPUTED_COLUMN_WITH_TYPE = True
 354
 355    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
 356    SUPPORTS_TABLE_COPY = True
 357
 358    # Whether parentheses are required around the table sample's expression
 359    TABLESAMPLE_REQUIRES_PARENS = True
 360
 361    # Whether a table sample clause's size needs to be followed by the ROWS keyword
 362    TABLESAMPLE_SIZE_IS_ROWS = True
 363
 364    # The keyword(s) to use when generating a sample clause
 365    TABLESAMPLE_KEYWORDS = "TABLESAMPLE"
 366
 367    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
 368    TABLESAMPLE_WITH_METHOD = True
 369
 370    # The keyword to use when specifying the seed of a sample clause
 371    TABLESAMPLE_SEED_KEYWORD = "SEED"
 372
 373    # Whether COLLATE is a function instead of a binary operator
 374    COLLATE_IS_FUNC = False
 375
 376    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)
 377    DATA_TYPE_SPECIFIERS_ALLOWED = False
 378
 379    # Whether conditions require booleans WHERE x = 0 vs WHERE x
 380    ENSURE_BOOLS = False
 381
 382    # Whether the "RECURSIVE" keyword is required when defining recursive CTEs
 383    CTE_RECURSIVE_KEYWORD_REQUIRED = True
 384
 385    # Whether CONCAT requires >1 arguments
 386    SUPPORTS_SINGLE_ARG_CONCAT = True
 387
 388    # Whether LAST_DAY function supports a date part argument
 389    LAST_DAY_SUPPORTS_DATE_PART = True
 390
 391    # Whether named columns are allowed in table aliases
 392    SUPPORTS_TABLE_ALIAS_COLUMNS = True
 393
 394    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)
 395    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True
 396
 397    # What delimiter to use for separating JSON key/value pairs
 398    JSON_KEY_VALUE_PAIR_SEP = ":"
 399
 400    # INSERT OVERWRITE TABLE x override
 401    INSERT_OVERWRITE = " OVERWRITE TABLE"
 402
 403    # Whether the SELECT .. INTO syntax is used instead of CTAS
 404    SUPPORTS_SELECT_INTO = False
 405
 406    # Whether UNLOGGED tables can be created
 407    SUPPORTS_UNLOGGED_TABLES = False
 408
 409    # Whether the CREATE TABLE LIKE statement is supported
 410    SUPPORTS_CREATE_TABLE_LIKE = True
 411
 412    # Whether the LikeProperty needs to be specified inside of the schema clause
 413    LIKE_PROPERTY_INSIDE_SCHEMA = False
 414
 415    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be
 416    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args
 417    MULTI_ARG_DISTINCT = True
 418
 419    # Whether the JSON extraction operators expect a value of type JSON
 420    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False
 421
 422    # Whether bracketed keys like ["foo"] are supported in JSON paths
 423    JSON_PATH_BRACKETED_KEY_SUPPORTED = True
 424
 425    # Whether to escape keys using single quotes in JSON paths
 426    JSON_PATH_SINGLE_QUOTE_ESCAPE = False
 427
 428    # The JSONPathPart expressions supported by this dialect
 429    SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy()
 430
 431    # Whether any(f(x) for x in array) can be implemented by this dialect
 432    CAN_IMPLEMENT_ARRAY_ANY = False
 433
 434    # Whether the function TO_NUMBER is supported
 435    SUPPORTS_TO_NUMBER = True
 436
 437    # Whether EXCLUDE in window specification is supported
 438    SUPPORTS_WINDOW_EXCLUDE = False
 439
 440    # Whether or not set op modifiers apply to the outer set op or select.
 441    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1
 442    # True means limit 1 happens after the set op, False means it it happens on y.
 443    SET_OP_MODIFIERS = True
 444
 445    # Whether parameters from COPY statement are wrapped in parentheses
 446    COPY_PARAMS_ARE_WRAPPED = True
 447
 448    # Whether values of params are set with "=" token or empty space
 449    COPY_PARAMS_EQ_REQUIRED = False
 450
 451    # Whether COPY statement has INTO keyword
 452    COPY_HAS_INTO_KEYWORD = True
 453
 454    # Whether the conditional TRY(expression) function is supported
 455    TRY_SUPPORTED = True
 456
 457    # Whether the UESCAPE syntax in unicode strings is supported
 458    SUPPORTS_UESCAPE = True
 459
 460    # Function used to replace escaped unicode codes in unicode strings
 461    UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None
 462
 463    # The keyword to use when generating a star projection with excluded columns
 464    STAR_EXCEPT = "EXCEPT"
 465
 466    # The HEX function name
 467    HEX_FUNC = "HEX"
 468
 469    # The keywords to use when prefixing & separating WITH based properties
 470    WITH_PROPERTIES_PREFIX = "WITH"
 471
 472    # Whether to quote the generated expression of exp.JsonPath
 473    QUOTE_JSON_PATH = True
 474
 475    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)
 476    PAD_FILL_PATTERN_IS_REQUIRED = False
 477
 478    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.
 479    SUPPORTS_EXPLODING_PROJECTIONS = True
 480
 481    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version
 482    ARRAY_CONCAT_IS_VAR_LEN = True
 483
 484    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone
 485    SUPPORTS_CONVERT_TIMEZONE = False
 486
 487    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)
 488    SUPPORTS_MEDIAN = True
 489
 490    # Whether UNIX_SECONDS(timestamp) is supported
 491    SUPPORTS_UNIX_SECONDS = False
 492
 493    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)
 494    ALTER_SET_WRAPPED = False
 495
 496    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation
 497    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.
 498    # TODO: The normalization should be done by default once we've tested it across all dialects.
 499    NORMALIZE_EXTRACT_DATE_PARTS = False
 500
 501    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated
 502    PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON"
 503
 504    # The function name of the exp.ArraySize expression
 505    ARRAY_SIZE_NAME: str = "ARRAY_LENGTH"
 506
 507    # The syntax to use when altering the type of a column
 508    ALTER_SET_TYPE = "SET DATA TYPE"
 509
 510    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)
 511    # None -> Doesn't support it at all
 512    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without
 513    # True (Postgres) -> Explicitly requires it
 514    ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None
 515
 516    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated
 517    SUPPORTS_DECODE_CASE = True
 518
 519    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression
 520    SUPPORTS_BETWEEN_FLAGS = False
 521
 522    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
 523    SUPPORTS_LIKE_QUANTIFIERS = True
 524
 525    # Prefix which is appended to exp.Table expressions in MATCH AGAINST
 526    MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None
 527
 528    # Whether to include the VARIABLE keyword for SET assignments
 529    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False
 530
 531    # The keyword to use for default value assignment in DECLARE statements
 532    DECLARE_DEFAULT_ASSIGNMENT = "="
 533
 534    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:
 535    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2
 536    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b
 537    UPDATE_STATEMENT_SUPPORTS_FROM = True
 538
 539    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.
 540    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True
 541
 542    TYPE_MAPPING = {
 543        exp.DataType.Type.DATETIME2: "TIMESTAMP",
 544        exp.DataType.Type.NCHAR: "CHAR",
 545        exp.DataType.Type.NVARCHAR: "VARCHAR",
 546        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 547        exp.DataType.Type.LONGTEXT: "TEXT",
 548        exp.DataType.Type.TINYTEXT: "TEXT",
 549        exp.DataType.Type.BLOB: "VARBINARY",
 550        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 551        exp.DataType.Type.LONGBLOB: "BLOB",
 552        exp.DataType.Type.TINYBLOB: "BLOB",
 553        exp.DataType.Type.INET: "INET",
 554        exp.DataType.Type.ROWVERSION: "VARBINARY",
 555        exp.DataType.Type.SMALLDATETIME: "TIMESTAMP",
 556    }
 557
 558    UNSUPPORTED_TYPES: set[exp.DataType.Type] = set()
 559
 560    TIME_PART_SINGULARS = {
 561        "MICROSECONDS": "MICROSECOND",
 562        "SECONDS": "SECOND",
 563        "MINUTES": "MINUTE",
 564        "HOURS": "HOUR",
 565        "DAYS": "DAY",
 566        "WEEKS": "WEEK",
 567        "MONTHS": "MONTH",
 568        "QUARTERS": "QUARTER",
 569        "YEARS": "YEAR",
 570    }
 571
 572    AFTER_HAVING_MODIFIER_TRANSFORMS = {
 573        "cluster": lambda self, e: self.sql(e, "cluster"),
 574        "distribute": lambda self, e: self.sql(e, "distribute"),
 575        "sort": lambda self, e: self.sql(e, "sort"),
 576        "windows": lambda self, e: (
 577            self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True)
 578            if e.args.get("windows")
 579            else ""
 580        ),
 581        "qualify": lambda self, e: self.sql(e, "qualify"),
 582    }
 583
 584    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 585
 586    STRUCT_DELIMITER = ("<", ">")
 587
 588    PARAMETER_TOKEN = "@"
 589    NAMED_PLACEHOLDER_TOKEN = ":"
 590
 591    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set()
 592
 593    PROPERTIES_LOCATION = {
 594        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
 595        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 596        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 597        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
 598        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,
 599        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 600        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 601        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 602        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 603        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
 604        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 605        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
 606        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,
 607        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,
 608        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 609        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
 610        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 611        exp.DictRange: exp.Properties.Location.POST_SCHEMA,
 612        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
 613        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,
 614        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 615        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 616        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,
 617        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,
 618        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 619        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,
 620        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 621        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 622        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 623        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 624        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 625        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,
 626        exp.HeapProperty: exp.Properties.Location.POST_WITH,
 627        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,
 628        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,
 629        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,
 630        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
 631        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 632        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 633        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 634        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 635        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 636        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,
 637        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 638        exp.LogProperty: exp.Properties.Location.POST_NAME,
 639        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 640        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 641        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 642        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
 643        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 644        exp.Order: exp.Properties.Location.POST_SCHEMA,
 645        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
 646        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 647        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
 648        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
 649        exp.Property: exp.Properties.Location.POST_WITH,
 650        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,
 651        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
 652        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 653        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,
 654        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
 655        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 656        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 657        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
 658        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 659        exp.SecureProperty: exp.Properties.Location.POST_CREATE,
 660        exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA,
 661        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 662        exp.Set: exp.Properties.Location.POST_SCHEMA,
 663        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
 664        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 665        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,
 666        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,
 667        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,
 668        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,
 669        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 670        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
 671        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 672        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
 673        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,
 674        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,
 675        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
 676        exp.Tags: exp.Properties.Location.POST_WITH,
 677        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 678        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
 679        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 680        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
 681        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
 682        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
 683        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
 684        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
 685        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
 686        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 687        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 688        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,
 689        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,
 690        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
 691        exp.ForceProperty: exp.Properties.Location.POST_CREATE,
 692    }
 693
 694    # Keywords that can't be used as unquoted identifier names
 695    RESERVED_KEYWORDS: t.Set[str] = set()
 696
 697    # Expressions whose comments are separated from them for better formatting
 698    WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 699        exp.Command,
 700        exp.Create,
 701        exp.Describe,
 702        exp.Delete,
 703        exp.Drop,
 704        exp.From,
 705        exp.Insert,
 706        exp.Join,
 707        exp.MultitableInserts,
 708        exp.Order,
 709        exp.Group,
 710        exp.Having,
 711        exp.Select,
 712        exp.SetOperation,
 713        exp.Update,
 714        exp.Where,
 715        exp.With,
 716    )
 717
 718    # Expressions that should not have their comments generated in maybe_comment
 719    EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
 720        exp.Binary,
 721        exp.SetOperation,
 722    )
 723
 724    # Expressions that can remain unwrapped when appearing in the context of an INTERVAL
 725    UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = (
 726        exp.Column,
 727        exp.Literal,
 728        exp.Neg,
 729        exp.Paren,
 730    )
 731
 732    PARAMETERIZABLE_TEXT_TYPES = {
 733        exp.DataType.Type.NVARCHAR,
 734        exp.DataType.Type.VARCHAR,
 735        exp.DataType.Type.CHAR,
 736        exp.DataType.Type.NCHAR,
 737    }
 738
 739    # Expressions that need to have all CTEs under them bubbled up to them
 740    EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set()
 741
 742    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = ()
 743
 744    SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE
 745
 746    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 747
 748    __slots__ = (
 749        "pretty",
 750        "identify",
 751        "normalize",
 752        "pad",
 753        "_indent",
 754        "normalize_functions",
 755        "unsupported_level",
 756        "max_unsupported",
 757        "leading_comma",
 758        "max_text_width",
 759        "comments",
 760        "dialect",
 761        "unsupported_messages",
 762        "_escaped_quote_end",
 763        "_escaped_byte_quote_end",
 764        "_escaped_identifier_end",
 765        "_next_name",
 766        "_identifier_start",
 767        "_identifier_end",
 768        "_quote_json_path_key_using_brackets",
 769    )
 770
 771    def __init__(
 772        self,
 773        pretty: t.Optional[bool] = None,
 774        identify: str | bool = False,
 775        normalize: bool = False,
 776        pad: int = 2,
 777        indent: int = 2,
 778        normalize_functions: t.Optional[str | bool] = None,
 779        unsupported_level: ErrorLevel = ErrorLevel.WARN,
 780        max_unsupported: int = 3,
 781        leading_comma: bool = False,
 782        max_text_width: int = 80,
 783        comments: bool = True,
 784        dialect: DialectType = None,
 785    ):
 786        import sqlglot
 787        from sqlglot.dialects import Dialect
 788
 789        self.pretty = pretty if pretty is not None else sqlglot.pretty
 790        self.identify = identify
 791        self.normalize = normalize
 792        self.pad = pad
 793        self._indent = indent
 794        self.unsupported_level = unsupported_level
 795        self.max_unsupported = max_unsupported
 796        self.leading_comma = leading_comma
 797        self.max_text_width = max_text_width
 798        self.comments = comments
 799        self.dialect = Dialect.get_or_raise(dialect)
 800
 801        # This is both a Dialect property and a Generator argument, so we prioritize the latter
 802        self.normalize_functions = (
 803            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
 804        )
 805
 806        self.unsupported_messages: t.List[str] = []
 807        self._escaped_quote_end: str = (
 808            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
 809        )
 810        self._escaped_byte_quote_end: str = (
 811            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
 812            if self.dialect.BYTE_END
 813            else ""
 814        )
 815        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
 816
 817        self._next_name = name_sequence("_t")
 818
 819        self._identifier_start = self.dialect.IDENTIFIER_START
 820        self._identifier_end = self.dialect.IDENTIFIER_END
 821
 822        self._quote_json_path_key_using_brackets = True
 823
 824    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
 825        """
 826        Generates the SQL string corresponding to the given syntax tree.
 827
 828        Args:
 829            expression: The syntax tree.
 830            copy: Whether to copy the expression. The generator performs mutations so
 831                it is safer to copy.
 832
 833        Returns:
 834            The SQL string corresponding to `expression`.
 835        """
 836        if copy:
 837            expression = expression.copy()
 838
 839        expression = self.preprocess(expression)
 840
 841        self.unsupported_messages = []
 842        sql = self.sql(expression).strip()
 843
 844        if self.pretty:
 845            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 846
 847        if self.unsupported_level == ErrorLevel.IGNORE:
 848            return sql
 849
 850        if self.unsupported_level == ErrorLevel.WARN:
 851            for msg in self.unsupported_messages:
 852                logger.warning(msg)
 853        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 854            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 855
 856        return sql
 857
 858    def preprocess(self, expression: exp.Expression) -> exp.Expression:
 859        """Apply generic preprocessing transformations to a given expression."""
 860        expression = self._move_ctes_to_top_level(expression)
 861
 862        if self.ENSURE_BOOLS:
 863            from sqlglot.transforms import ensure_bools
 864
 865            expression = ensure_bools(expression)
 866
 867        return expression
 868
 869    def _move_ctes_to_top_level(self, expression: E) -> E:
 870        if (
 871            not expression.parent
 872            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
 873            and any(node.parent is not expression for node in expression.find_all(exp.With))
 874        ):
 875            from sqlglot.transforms import move_ctes_to_top_level
 876
 877            expression = move_ctes_to_top_level(expression)
 878        return expression
 879
 880    def unsupported(self, message: str) -> None:
 881        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 882            raise UnsupportedError(message)
 883        self.unsupported_messages.append(message)
 884
 885    def sep(self, sep: str = " ") -> str:
 886        return f"{sep.strip()}\n" if self.pretty else sep
 887
 888    def seg(self, sql: str, sep: str = " ") -> str:
 889        return f"{self.sep(sep)}{sql}"
 890
 891    def sanitize_comment(self, comment: str) -> str:
 892        comment = " " + comment if comment[0].strip() else comment
 893        comment = comment + " " if comment[-1].strip() else comment
 894
 895        if not self.dialect.tokenizer_class.NESTED_COMMENTS:
 896            # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */
 897            comment = comment.replace("*/", "* /")
 898
 899        return comment
 900
 901    def maybe_comment(
 902        self,
 903        sql: str,
 904        expression: t.Optional[exp.Expression] = None,
 905        comments: t.Optional[t.List[str]] = None,
 906        separated: bool = False,
 907    ) -> str:
 908        comments = (
 909            ((expression and expression.comments) if comments is None else comments)  # type: ignore
 910            if self.comments
 911            else None
 912        )
 913
 914        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
 915            return sql
 916
 917        comments_sql = " ".join(
 918            f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment
 919        )
 920
 921        if not comments_sql:
 922            return sql
 923
 924        comments_sql = self._replace_line_breaks(comments_sql)
 925
 926        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 927            return (
 928                f"{self.sep()}{comments_sql}{sql}"
 929                if not sql or sql[0].isspace()
 930                else f"{comments_sql}{self.sep()}{sql}"
 931            )
 932
 933        return f"{sql} {comments_sql}"
 934
 935    def wrap(self, expression: exp.Expression | str) -> str:
 936        this_sql = (
 937            self.sql(expression)
 938            if isinstance(expression, exp.UNWRAPPED_QUERIES)
 939            else self.sql(expression, "this")
 940        )
 941        if not this_sql:
 942            return "()"
 943
 944        this_sql = self.indent(this_sql, level=1, pad=0)
 945        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 946
 947    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 948        original = self.identify
 949        self.identify = False
 950        result = func(*args, **kwargs)
 951        self.identify = original
 952        return result
 953
 954    def normalize_func(self, name: str) -> str:
 955        if self.normalize_functions == "upper" or self.normalize_functions is True:
 956            return name.upper()
 957        if self.normalize_functions == "lower":
 958            return name.lower()
 959        return name
 960
 961    def indent(
 962        self,
 963        sql: str,
 964        level: int = 0,
 965        pad: t.Optional[int] = None,
 966        skip_first: bool = False,
 967        skip_last: bool = False,
 968    ) -> str:
 969        if not self.pretty or not sql:
 970            return sql
 971
 972        pad = self.pad if pad is None else pad
 973        lines = sql.split("\n")
 974
 975        return "\n".join(
 976            (
 977                line
 978                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 979                else f"{' ' * (level * self._indent + pad)}{line}"
 980            )
 981            for i, line in enumerate(lines)
 982        )
 983
 984    def sql(
 985        self,
 986        expression: t.Optional[str | exp.Expression],
 987        key: t.Optional[str] = None,
 988        comment: bool = True,
 989    ) -> str:
 990        if not expression:
 991            return ""
 992
 993        if isinstance(expression, str):
 994            return expression
 995
 996        if key:
 997            value = expression.args.get(key)
 998            if value:
 999                return self.sql(value)
1000            return ""
1001
1002        transform = self.TRANSFORMS.get(expression.__class__)
1003
1004        if transform:
1005            sql = transform(self, expression)
1006        else:
1007            exp_handler_name = expression.key + "_sql"
1008
1009            if handler := getattr(self, exp_handler_name, None):
1010                sql = handler(expression)
1011            elif isinstance(expression, exp.Func):
1012                sql = self.function_fallback_sql(expression)
1013            elif isinstance(expression, exp.Property):
1014                sql = self.property_sql(expression)
1015            else:
1016                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
1017
1018        return self.maybe_comment(sql, expression) if self.comments and comment else sql
1019
1020    def uncache_sql(self, expression: exp.Uncache) -> str:
1021        table = self.sql(expression, "this")
1022        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
1023        return f"UNCACHE TABLE{exists_sql} {table}"
1024
1025    def cache_sql(self, expression: exp.Cache) -> str:
1026        lazy = " LAZY" if expression.args.get("lazy") else ""
1027        table = self.sql(expression, "this")
1028        options = expression.args.get("options")
1029        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
1030        sql = self.sql(expression, "expression")
1031        sql = f" AS{self.sep()}{sql}" if sql else ""
1032        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
1033        return self.prepend_ctes(expression, sql)
1034
1035    def characterset_sql(self, expression: exp.CharacterSet) -> str:
1036        if isinstance(expression.parent, exp.Cast):
1037            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
1038        default = "DEFAULT " if expression.args.get("default") else ""
1039        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1040
1041    def column_parts(self, expression: exp.Column) -> str:
1042        return ".".join(
1043            self.sql(part)
1044            for part in (
1045                expression.args.get("catalog"),
1046                expression.args.get("db"),
1047                expression.args.get("table"),
1048                expression.args.get("this"),
1049            )
1050            if part
1051        )
1052
1053    def column_sql(self, expression: exp.Column) -> str:
1054        join_mark = " (+)" if expression.args.get("join_mark") else ""
1055
1056        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
1057            join_mark = ""
1058            self.unsupported("Outer join syntax using the (+) operator is not supported.")
1059
1060        return f"{self.column_parts(expression)}{join_mark}"
1061
1062    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:
1063        return self.column_sql(expression)
1064
1065    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
1066        this = self.sql(expression, "this")
1067        this = f" {this}" if this else ""
1068        position = self.sql(expression, "position")
1069        return f"{position}{this}"
1070
1071    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
1072        column = self.sql(expression, "this")
1073        kind = self.sql(expression, "kind")
1074        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
1075        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
1076        kind = f"{sep}{kind}" if kind else ""
1077        constraints = f" {constraints}" if constraints else ""
1078        position = self.sql(expression, "position")
1079        position = f" {position}" if position else ""
1080
1081        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
1082            kind = ""
1083
1084        return f"{exists}{column}{kind}{constraints}{position}"
1085
1086    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
1087        this = self.sql(expression, "this")
1088        kind_sql = self.sql(expression, "kind").strip()
1089        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
1090
1091    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
1092        this = self.sql(expression, "this")
1093        if expression.args.get("not_null"):
1094            persisted = " PERSISTED NOT NULL"
1095        elif expression.args.get("persisted"):
1096            persisted = " PERSISTED"
1097        else:
1098            persisted = ""
1099
1100        return f"AS {this}{persisted}"
1101
1102    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:
1103        return self.token_sql(TokenType.AUTO_INCREMENT)
1104
1105    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
1106        if isinstance(expression.this, list):
1107            this = self.wrap(self.expressions(expression, key="this", flat=True))
1108        else:
1109            this = self.sql(expression, "this")
1110
1111        return f"COMPRESS {this}"
1112
1113    def generatedasidentitycolumnconstraint_sql(
1114        self, expression: exp.GeneratedAsIdentityColumnConstraint
1115    ) -> str:
1116        this = ""
1117        if expression.this is not None:
1118            on_null = " ON NULL" if expression.args.get("on_null") else ""
1119            this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
1120
1121        start = expression.args.get("start")
1122        start = f"START WITH {start}" if start else ""
1123        increment = expression.args.get("increment")
1124        increment = f" INCREMENT BY {increment}" if increment else ""
1125        minvalue = expression.args.get("minvalue")
1126        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1127        maxvalue = expression.args.get("maxvalue")
1128        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1129        cycle = expression.args.get("cycle")
1130        cycle_sql = ""
1131
1132        if cycle is not None:
1133            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
1134            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
1135
1136        sequence_opts = ""
1137        if start or increment or cycle_sql:
1138            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
1139            sequence_opts = f" ({sequence_opts.strip()})"
1140
1141        expr = self.sql(expression, "expression")
1142        expr = f"({expr})" if expr else "IDENTITY"
1143
1144        return f"GENERATED{this} AS {expr}{sequence_opts}"
1145
1146    def generatedasrowcolumnconstraint_sql(
1147        self, expression: exp.GeneratedAsRowColumnConstraint
1148    ) -> str:
1149        start = "START" if expression.args.get("start") else "END"
1150        hidden = " HIDDEN" if expression.args.get("hidden") else ""
1151        return f"GENERATED ALWAYS AS ROW {start}{hidden}"
1152
1153    def periodforsystemtimeconstraint_sql(
1154        self, expression: exp.PeriodForSystemTimeConstraint
1155    ) -> str:
1156        return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
1157
1158    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
1159        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
1160
1161    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
1162        desc = expression.args.get("desc")
1163        if desc is not None:
1164            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
1165        options = self.expressions(expression, key="options", flat=True, sep=" ")
1166        options = f" {options}" if options else ""
1167        return f"PRIMARY KEY{options}"
1168
1169    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
1170        this = self.sql(expression, "this")
1171        this = f" {this}" if this else ""
1172        index_type = expression.args.get("index_type")
1173        index_type = f" USING {index_type}" if index_type else ""
1174        on_conflict = self.sql(expression, "on_conflict")
1175        on_conflict = f" {on_conflict}" if on_conflict else ""
1176        nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else ""
1177        options = self.expressions(expression, key="options", flat=True, sep=" ")
1178        options = f" {options}" if options else ""
1179        return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1180
1181    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:
1182        input_ = expression.args.get("input_")
1183        output = expression.args.get("output")
1184        variadic = expression.args.get("variadic")
1185
1186        # VARIADIC is mutually exclusive with IN/OUT/INOUT
1187        if variadic:
1188            return "VARIADIC"
1189
1190        if input_ and output:
1191            return f"IN{self.INOUT_SEPARATOR}OUT"
1192        if input_:
1193            return "IN"
1194        if output:
1195            return "OUT"
1196
1197        return ""
1198
1199    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
1200        return self.sql(expression, "this")
1201
1202    def create_sql(self, expression: exp.Create) -> str:
1203        kind = self.sql(expression, "kind")
1204        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1205
1206        properties = expression.args.get("properties")
1207
1208        if (
1209            kind == "TRIGGER"
1210            and properties
1211            and properties.expressions
1212            and isinstance(properties.expressions[0], exp.TriggerProperties)
1213            and properties.expressions[0].args.get("constraint")
1214        ):
1215            kind = f"CONSTRAINT {kind}"
1216
1217        properties_locs = self.locate_properties(properties) if properties else defaultdict()
1218
1219        this = self.createable_sql(expression, properties_locs)
1220
1221        properties_sql = ""
1222        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
1223            exp.Properties.Location.POST_WITH
1224        ):
1225            props_ast = exp.Properties(
1226                expressions=[
1227                    *properties_locs[exp.Properties.Location.POST_SCHEMA],
1228                    *properties_locs[exp.Properties.Location.POST_WITH],
1229                ]
1230            )
1231            props_ast.parent = expression
1232            properties_sql = self.sql(props_ast)
1233
1234            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
1235                properties_sql = self.sep() + properties_sql
1236            elif not self.pretty:
1237                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
1238                properties_sql = f" {properties_sql}"
1239
1240        begin = " BEGIN" if expression.args.get("begin") else ""
1241
1242        expression_sql = self.sql(expression, "expression")
1243        if expression_sql:
1244            expression_sql = f"{begin}{self.sep()}{expression_sql}"
1245
1246            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
1247                postalias_props_sql = ""
1248                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
1249                    postalias_props_sql = self.properties(
1250                        exp.Properties(
1251                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
1252                        ),
1253                        wrapped=False,
1254                    )
1255                postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else ""
1256                expression_sql = f" AS{postalias_props_sql}{expression_sql}"
1257
1258        postindex_props_sql = ""
1259        if properties_locs.get(exp.Properties.Location.POST_INDEX):
1260            postindex_props_sql = self.properties(
1261                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
1262                wrapped=False,
1263                prefix=" ",
1264            )
1265
1266        indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
1267        indexes = f" {indexes}" if indexes else ""
1268        index_sql = indexes + postindex_props_sql
1269
1270        replace = " OR REPLACE" if expression.args.get("replace") else ""
1271        refresh = " OR REFRESH" if expression.args.get("refresh") else ""
1272        unique = " UNIQUE" if expression.args.get("unique") else ""
1273
1274        clustered = expression.args.get("clustered")
1275        if clustered is None:
1276            clustered_sql = ""
1277        elif clustered:
1278            clustered_sql = " CLUSTERED COLUMNSTORE"
1279        else:
1280            clustered_sql = " NONCLUSTERED COLUMNSTORE"
1281
1282        postcreate_props_sql = ""
1283        if properties_locs.get(exp.Properties.Location.POST_CREATE):
1284            postcreate_props_sql = self.properties(
1285                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
1286                sep=" ",
1287                prefix=" ",
1288                wrapped=False,
1289            )
1290
1291        modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))
1292
1293        postexpression_props_sql = ""
1294        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
1295            postexpression_props_sql = self.properties(
1296                exp.Properties(
1297                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
1298                ),
1299                sep=" ",
1300                prefix=" ",
1301                wrapped=False,
1302            )
1303
1304        concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1305        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
1306        no_schema_binding = (
1307            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
1308        )
1309
1310        clone = self.sql(expression, "clone")
1311        clone = f" {clone}" if clone else ""
1312
1313        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:
1314            properties_expression = f"{expression_sql}{properties_sql}"
1315        else:
1316            properties_expression = f"{properties_sql}{expression_sql}"
1317
1318        expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
1319        return self.prepend_ctes(expression, expression_sql)
1320
1321    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:
1322        start = self.sql(expression, "start")
1323        start = f"START WITH {start}" if start else ""
1324        increment = self.sql(expression, "increment")
1325        increment = f" INCREMENT BY {increment}" if increment else ""
1326        minvalue = self.sql(expression, "minvalue")
1327        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
1328        maxvalue = self.sql(expression, "maxvalue")
1329        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
1330        owned = self.sql(expression, "owned")
1331        owned = f" OWNED BY {owned}" if owned else ""
1332
1333        cache = expression.args.get("cache")
1334        if cache is None:
1335            cache_str = ""
1336        elif cache is True:
1337            cache_str = " CACHE"
1338        else:
1339            cache_str = f" CACHE {cache}"
1340
1341        options = self.expressions(expression, key="options", flat=True, sep=" ")
1342        options = f" {options}" if options else ""
1343
1344        return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1345
1346    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:
1347        timing = expression.args.get("timing", "")
1348        events = " OR ".join(self.sql(event) for event in expression.args.get("events") or [])
1349        timing_events = f"{timing} {events}".strip() if timing or events else ""
1350
1351        parts = [timing_events, "ON", self.sql(expression, "table")]
1352
1353        if referenced_table := expression.args.get("referenced_table"):
1354            parts.extend(["FROM", self.sql(referenced_table)])
1355
1356        if deferrable := expression.args.get("deferrable"):
1357            parts.append(deferrable)
1358
1359        if initially := expression.args.get("initially"):
1360            parts.append(f"INITIALLY {initially}")
1361
1362        if referencing := expression.args.get("referencing"):
1363            parts.append(self.sql(referencing))
1364
1365        if for_each := expression.args.get("for_each"):
1366            parts.append(f"FOR EACH {for_each}")
1367
1368        if when := expression.args.get("when"):
1369            parts.append(f"WHEN ({self.sql(when)})")
1370
1371        parts.append(self.sql(expression, "execute"))
1372
1373        return self.sep().join(parts)
1374
1375    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:
1376        parts = []
1377
1378        if old_alias := expression.args.get("old"):
1379            parts.append(f"OLD TABLE AS {self.sql(old_alias)}")
1380
1381        if new_alias := expression.args.get("new"):
1382            parts.append(f"NEW TABLE AS {self.sql(new_alias)}")
1383
1384        return f"REFERENCING {' '.join(parts)}"
1385
1386    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:
1387        columns = expression.args.get("columns")
1388        if columns:
1389            return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}"
1390
1391        return self.sql(expression, "this")
1392
1393    def clone_sql(self, expression: exp.Clone) -> str:
1394        this = self.sql(expression, "this")
1395        shallow = "SHALLOW " if expression.args.get("shallow") else ""
1396        keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
1397        return f"{shallow}{keyword} {this}"
1398
1399    def describe_sql(self, expression: exp.Describe) -> str:
1400        style = expression.args.get("style")
1401        style = f" {style}" if style else ""
1402        partition = self.sql(expression, "partition")
1403        partition = f" {partition}" if partition else ""
1404        format = self.sql(expression, "format")
1405        format = f" {format}" if format else ""
1406        as_json = " AS JSON" if expression.args.get("as_json") else ""
1407
1408        return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1409
1410    def heredoc_sql(self, expression: exp.Heredoc) -> str:
1411        tag = self.sql(expression, "tag")
1412        return f"${tag}${self.sql(expression, 'this')}${tag}$"
1413
1414    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
1415        with_ = self.sql(expression, "with_")
1416        if with_:
1417            sql = f"{with_}{self.sep()}{sql}"
1418        return sql
1419
1420    def with_sql(self, expression: exp.With) -> str:
1421        sql = self.expressions(expression, flat=True)
1422        recursive = (
1423            "RECURSIVE "
1424            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
1425            else ""
1426        )
1427        search = self.sql(expression, "search")
1428        search = f" {search}" if search else ""
1429
1430        return f"WITH {recursive}{sql}{search}"
1431
1432    def cte_sql(self, expression: exp.CTE) -> str:
1433        alias = expression.args.get("alias")
1434        if alias:
1435            alias.add_comments(expression.pop_comments())
1436
1437        alias_sql = self.sql(expression, "alias")
1438
1439        materialized = expression.args.get("materialized")
1440        if materialized is False:
1441            materialized = "NOT MATERIALIZED "
1442        elif materialized:
1443            materialized = "MATERIALIZED "
1444
1445        key_expressions = self.expressions(expression, key="key_expressions", flat=True)
1446        key_expressions = f" USING KEY ({key_expressions})" if key_expressions else ""
1447
1448        return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1449
1450    def tablealias_sql(self, expression: exp.TableAlias) -> str:
1451        alias = self.sql(expression, "this")
1452        columns = self.expressions(expression, key="columns", flat=True)
1453        columns = f"({columns})" if columns else ""
1454
1455        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:
1456            columns = ""
1457            self.unsupported("Named columns are not supported in table alias.")
1458
1459        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
1460            alias = self._next_name()
1461
1462        return f"{alias}{columns}"
1463
1464    def bitstring_sql(self, expression: exp.BitString) -> str:
1465        this = self.sql(expression, "this")
1466        if self.dialect.BIT_START:
1467            return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}"
1468        return f"{int(this, 2)}"
1469
1470    def hexstring_sql(
1471        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None
1472    ) -> str:
1473        this = self.sql(expression, "this")
1474        is_integer_type = expression.args.get("is_integer")
1475
1476        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (
1477            not self.dialect.HEX_START and not binary_function_repr
1478        ):
1479            # Integer representation will be returned if:
1480            # - The read dialect treats the hex value as integer literal but not the write
1481            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)
1482            return f"{int(this, 16)}"
1483
1484        if not is_integer_type:
1485            # Read dialect treats the hex value as BINARY/BLOB
1486            if binary_function_repr:
1487                # The write dialect supports the transpilation to its equivalent BINARY/BLOB
1488                return self.func(binary_function_repr, exp.Literal.string(this))
1489            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:
1490                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER
1491                self.unsupported("Unsupported transpilation from BINARY/BLOB hex string")
1492
1493        return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1494
1495    def bytestring_sql(self, expression: exp.ByteString) -> str:
1496        this = self.sql(expression, "this")
1497        if self.dialect.BYTE_START:
1498            escaped_byte_string = self.escape_str(
1499                this,
1500                escape_backslash=False,
1501                delimiter=self.dialect.BYTE_END,
1502                escaped_delimiter=self._escaped_byte_quote_end,
1503                is_byte_string=True,
1504            )
1505            is_bytes = expression.args.get("is_bytes", False)
1506            delimited_byte_string = (
1507                f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}"
1508            )
1509            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1510                return self.sql(
1511                    exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect)
1512                )
1513            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:
1514                return self.sql(
1515                    exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect)
1516                )
1517
1518            return delimited_byte_string
1519        return this
1520
1521    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:
1522        this = self.sql(expression, "this")
1523        escape = expression.args.get("escape")
1524
1525        if self.dialect.UNICODE_START:
1526            escape_substitute = r"\\\1"
1527            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END
1528        else:
1529            escape_substitute = r"\\u\1"
1530            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END
1531
1532        if escape:
1533            escape_pattern = re.compile(rf"{escape.name}(\d+)")
1534            escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else ""
1535        else:
1536            escape_pattern = ESCAPED_UNICODE_RE
1537            escape_sql = ""
1538
1539        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):
1540            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)
1541
1542        return f"{left_quote}{this}{right_quote}{escape_sql}"
1543
1544    def rawstring_sql(self, expression: exp.RawString) -> str:
1545        string = expression.this
1546        if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES:
1547            string = string.replace("\\", "\\\\")
1548
1549        string = self.escape_str(string, escape_backslash=False)
1550        return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1551
1552    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
1553        this = self.sql(expression, "this")
1554        specifier = self.sql(expression, "expression")
1555        specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
1556        return f"{this}{specifier}"
1557
1558    def datatype_sql(self, expression: exp.DataType) -> str:
1559        nested = ""
1560        values = ""
1561
1562        expr_nested = expression.args.get("nested")
1563        interior = (
1564            self.expressions(
1565                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True
1566            )
1567            if expr_nested and self.pretty
1568            else self.expressions(expression, flat=True)
1569        )
1570
1571        type_value = expression.this
1572        if type_value in self.UNSUPPORTED_TYPES:
1573            self.unsupported(
1574                f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}"
1575            )
1576
1577        if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
1578            type_sql = self.sql(expression, "kind")
1579        else:
1580            type_sql = (
1581                self.TYPE_MAPPING.get(type_value, type_value.value)
1582                if isinstance(type_value, exp.DataType.Type)
1583                else type_value
1584            )
1585
1586        if interior:
1587            if expr_nested:
1588                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
1589                if expression.args.get("values") is not None:
1590                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
1591                    values = self.expressions(expression, key="values", flat=True)
1592                    values = f"{delimiters[0]}{values}{delimiters[1]}"
1593            elif type_value == exp.DataType.Type.INTERVAL:
1594                nested = f" {interior}"
1595            else:
1596                nested = f"({interior})"
1597
1598        type_sql = f"{type_sql}{nested}{values}"
1599        if self.TZ_TO_WITH_TIME_ZONE and type_value in (
1600            exp.DataType.Type.TIMETZ,
1601            exp.DataType.Type.TIMESTAMPTZ,
1602        ):
1603            type_sql = f"{type_sql} WITH TIME ZONE"
1604
1605        return type_sql
1606
1607    def directory_sql(self, expression: exp.Directory) -> str:
1608        local = "LOCAL " if expression.args.get("local") else ""
1609        row_format = self.sql(expression, "row_format")
1610        row_format = f" {row_format}" if row_format else ""
1611        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1612
1613    def delete_sql(self, expression: exp.Delete) -> str:
1614        this = self.sql(expression, "this")
1615        this = f" FROM {this}" if this else ""
1616        using = self.expressions(expression, key="using")
1617        using = f" USING {using}" if using else ""
1618        cluster = self.sql(expression, "cluster")
1619        cluster = f" {cluster}" if cluster else ""
1620        where = self.sql(expression, "where")
1621        returning = self.sql(expression, "returning")
1622        order = self.sql(expression, "order")
1623        limit = self.sql(expression, "limit")
1624        tables = self.expressions(expression, key="tables")
1625        tables = f" {tables}" if tables else ""
1626        if self.RETURNING_END:
1627            expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}"
1628        else:
1629            expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}"
1630        return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1631
1632    def drop_sql(self, expression: exp.Drop) -> str:
1633        this = self.sql(expression, "this")
1634        expressions = self.expressions(expression, flat=True)
1635        expressions = f" ({expressions})" if expressions else ""
1636        kind = expression.args["kind"]
1637        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind
1638        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1639        concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else ""
1640        on_cluster = self.sql(expression, "cluster")
1641        on_cluster = f" {on_cluster}" if on_cluster else ""
1642        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1643        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1644        cascade = " CASCADE" if expression.args.get("cascade") else ""
1645        constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1646        purge = " PURGE" if expression.args.get("purge") else ""
1647        return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1648
1649    def set_operation(self, expression: exp.SetOperation) -> str:
1650        op_type = type(expression)
1651        op_name = op_type.key.upper()
1652
1653        distinct = expression.args.get("distinct")
1654        if (
1655            distinct is False
1656            and op_type in (exp.Except, exp.Intersect)
1657            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE
1658        ):
1659            self.unsupported(f"{op_name} ALL is not supported")
1660
1661        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]
1662
1663        if distinct is None:
1664            distinct = default_distinct
1665            if distinct is None:
1666                self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified")
1667
1668        if distinct is default_distinct:
1669            distinct_or_all = ""
1670        else:
1671            distinct_or_all = " DISTINCT" if distinct else " ALL"
1672
1673        side_kind = " ".join(filter(None, [expression.side, expression.kind]))
1674        side_kind = f"{side_kind} " if side_kind else ""
1675
1676        by_name = " BY NAME" if expression.args.get("by_name") else ""
1677        on = self.expressions(expression, key="on", flat=True)
1678        on = f" ON ({on})" if on else ""
1679
1680        return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1681
1682    def set_operations(self, expression: exp.SetOperation) -> str:
1683        if not self.SET_OP_MODIFIERS:
1684            limit = expression.args.get("limit")
1685            order = expression.args.get("order")
1686
1687            if limit or order:
1688                select = self._move_ctes_to_top_level(
1689                    exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
1690                )
1691
1692                if limit:
1693                    select = select.limit(limit.pop(), copy=False)
1694                if order:
1695                    select = select.order_by(order.pop(), copy=False)
1696                return self.sql(select)
1697
1698        sqls: t.List[str] = []
1699        stack: t.List[t.Union[str, exp.Expression]] = [expression]
1700
1701        while stack:
1702            node = stack.pop()
1703
1704            if isinstance(node, exp.SetOperation):
1705                stack.append(node.expression)
1706                stack.append(
1707                    self.maybe_comment(
1708                        self.set_operation(node), comments=node.comments, separated=True
1709                    )
1710                )
1711                stack.append(node.this)
1712            else:
1713                sqls.append(self.sql(node))
1714
1715        this = self.sep().join(sqls)
1716        this = self.query_modifiers(expression, this)
1717        return self.prepend_ctes(expression, this)
1718
1719    def fetch_sql(self, expression: exp.Fetch) -> str:
1720        direction = expression.args.get("direction")
1721        direction = f" {direction}" if direction else ""
1722        count = self.sql(expression, "count")
1723        count = f" {count}" if count else ""
1724        limit_options = self.sql(expression, "limit_options")
1725        limit_options = f"{limit_options}" if limit_options else " ROWS ONLY"
1726        return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1727
1728    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:
1729        percent = " PERCENT" if expression.args.get("percent") else ""
1730        rows = " ROWS" if expression.args.get("rows") else ""
1731        with_ties = " WITH TIES" if expression.args.get("with_ties") else ""
1732        if not with_ties and rows:
1733            with_ties = " ONLY"
1734        return f"{percent}{rows}{with_ties}"
1735
1736    def filter_sql(self, expression: exp.Filter) -> str:
1737        if self.AGGREGATE_FILTER_SUPPORTED:
1738            this = self.sql(expression, "this")
1739            where = self.sql(expression, "expression").strip()
1740            return f"{this} FILTER({where})"
1741
1742        agg = expression.this
1743        agg_arg = agg.this
1744        cond = expression.expression.this
1745        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1746        return self.sql(agg)
1747
1748    def hint_sql(self, expression: exp.Hint) -> str:
1749        if not self.QUERY_HINTS:
1750            self.unsupported("Hints are not supported")
1751            return ""
1752
1753        return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1754
1755    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:
1756        using = self.sql(expression, "using")
1757        using = f" USING {using}" if using else ""
1758        columns = self.expressions(expression, key="columns", flat=True)
1759        columns = f"({columns})" if columns else ""
1760        partition_by = self.expressions(expression, key="partition_by", flat=True)
1761        partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1762        where = self.sql(expression, "where")
1763        include = self.expressions(expression, key="include", flat=True)
1764        if include:
1765            include = f" INCLUDE ({include})"
1766        with_storage = self.expressions(expression, key="with_storage", flat=True)
1767        with_storage = f" WITH ({with_storage})" if with_storage else ""
1768        tablespace = self.sql(expression, "tablespace")
1769        tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else ""
1770        on = self.sql(expression, "on")
1771        on = f" ON {on}" if on else ""
1772
1773        return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1774
1775    def index_sql(self, expression: exp.Index) -> str:
1776        unique = "UNIQUE " if expression.args.get("unique") else ""
1777        primary = "PRIMARY " if expression.args.get("primary") else ""
1778        amp = "AMP " if expression.args.get("amp") else ""
1779        name = self.sql(expression, "this")
1780        name = f"{name} " if name else ""
1781        table = self.sql(expression, "table")
1782        table = f"{self.INDEX_ON} {table}" if table else ""
1783
1784        index = "INDEX " if not table else ""
1785
1786        params = self.sql(expression, "params")
1787        return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1788
1789    def identifier_sql(self, expression: exp.Identifier) -> str:
1790        text = expression.name
1791        lower = text.lower()
1792        quoted = expression.quoted
1793        text = lower if self.normalize and not quoted else text
1794        text = text.replace(self._identifier_end, self._escaped_identifier_end)
1795        if (
1796            quoted
1797            or self.dialect.can_quote(expression, self.identify)
1798            or lower in self.RESERVED_KEYWORDS
1799            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1800        ):
1801            text = f"{self._identifier_start}{text}{self._identifier_end}"
1802        return text
1803
1804    def hex_sql(self, expression: exp.Hex) -> str:
1805        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1806        if self.dialect.HEX_LOWERCASE:
1807            text = self.func("LOWER", text)
1808
1809        return text
1810
1811    def lowerhex_sql(self, expression: exp.LowerHex) -> str:
1812        text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
1813        if not self.dialect.HEX_LOWERCASE:
1814            text = self.func("LOWER", text)
1815        return text
1816
1817    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1818        input_format = self.sql(expression, "input_format")
1819        input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1820        output_format = self.sql(expression, "output_format")
1821        output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1822        return self.sep().join((input_format, output_format))
1823
1824    def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1825        string = self.sql(exp.Literal.string(expression.name))
1826        return f"{prefix}{string}"
1827
1828    def partition_sql(self, expression: exp.Partition) -> str:
1829        partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION"
1830        return f"{partition_keyword}({self.expressions(expression, flat=True)})"
1831
1832    def properties_sql(self, expression: exp.Properties) -> str:
1833        root_properties = []
1834        with_properties = []
1835
1836        for p in expression.expressions:
1837            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1838            if p_loc == exp.Properties.Location.POST_WITH:
1839                with_properties.append(p)
1840            elif p_loc == exp.Properties.Location.POST_SCHEMA:
1841                root_properties.append(p)
1842
1843        root_props_ast = exp.Properties(expressions=root_properties)
1844        root_props_ast.parent = expression.parent
1845
1846        with_props_ast = exp.Properties(expressions=with_properties)
1847        with_props_ast.parent = expression.parent
1848
1849        root_props = self.root_properties(root_props_ast)
1850        with_props = self.with_properties(with_props_ast)
1851
1852        if root_props and with_props and not self.pretty:
1853            with_props = " " + with_props
1854
1855        return root_props + with_props
1856
1857    def root_properties(self, properties: exp.Properties) -> str:
1858        if properties.expressions:
1859            return self.expressions(properties, indent=False, sep=" ")
1860        return ""
1861
1862    def properties(
1863        self,
1864        properties: exp.Properties,
1865        prefix: str = "",
1866        sep: str = ", ",
1867        suffix: str = "",
1868        wrapped: bool = True,
1869    ) -> str:
1870        if properties.expressions:
1871            expressions = self.expressions(properties, sep=sep, indent=False)
1872            if expressions:
1873                expressions = self.wrap(expressions) if wrapped else expressions
1874                return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}"
1875        return ""
1876
1877    def with_properties(self, properties: exp.Properties) -> str:
1878        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
1879
1880    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1881        properties_locs = defaultdict(list)
1882        for p in properties.expressions:
1883            p_loc = self.PROPERTIES_LOCATION[p.__class__]
1884            if p_loc != exp.Properties.Location.UNSUPPORTED:
1885                properties_locs[p_loc].append(p)
1886            else:
1887                self.unsupported(f"Unsupported property {p.key}")
1888
1889        return properties_locs
1890
1891    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1892        if isinstance(expression.this, exp.Dot):
1893            return self.sql(expression, "this")
1894        return f"'{expression.name}'" if string_key else expression.name
1895
1896    def property_sql(self, expression: exp.Property) -> str:
1897        property_cls = expression.__class__
1898        if property_cls == exp.Property:
1899            return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1900
1901        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1902        if not property_name:
1903            self.unsupported(f"Unsupported property {expression.key}")
1904
1905        return f"{property_name}={self.sql(expression, 'this')}"
1906
1907    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1908        if self.SUPPORTS_CREATE_TABLE_LIKE:
1909            options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1910            options = f" {options}" if options else ""
1911
1912            like = f"LIKE {self.sql(expression, 'this')}{options}"
1913            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):
1914                like = f"({like})"
1915
1916            return like
1917
1918        if expression.expressions:
1919            self.unsupported("Transpilation of LIKE property options is unsupported")
1920
1921        select = exp.select("*").from_(expression.this).limit(0)
1922        return f"AS {self.sql(select)}"
1923
1924    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1925        no = "NO " if expression.args.get("no") else ""
1926        protection = " PROTECTION" if expression.args.get("protection") else ""
1927        return f"{no}FALLBACK{protection}"
1928
1929    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1930        no = "NO " if expression.args.get("no") else ""
1931        local = expression.args.get("local")
1932        local = f"{local} " if local else ""
1933        dual = "DUAL " if expression.args.get("dual") else ""
1934        before = "BEFORE " if expression.args.get("before") else ""
1935        after = "AFTER " if expression.args.get("after") else ""
1936        return f"{no}{local}{dual}{before}{after}JOURNAL"
1937
1938    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1939        freespace = self.sql(expression, "this")
1940        percent = " PERCENT" if expression.args.get("percent") else ""
1941        return f"FREESPACE={freespace}{percent}"
1942
1943    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1944        if expression.args.get("default"):
1945            property = "DEFAULT"
1946        elif expression.args.get("on"):
1947            property = "ON"
1948        else:
1949            property = "OFF"
1950        return f"CHECKSUM={property}"
1951
1952    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1953        if expression.args.get("no"):
1954            return "NO MERGEBLOCKRATIO"
1955        if expression.args.get("default"):
1956            return "DEFAULT MERGEBLOCKRATIO"
1957
1958        percent = " PERCENT" if expression.args.get("percent") else ""
1959        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1960
1961    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1962        default = expression.args.get("default")
1963        minimum = expression.args.get("minimum")
1964        maximum = expression.args.get("maximum")
1965        if default or minimum or maximum:
1966            if default:
1967                prop = "DEFAULT"
1968            elif minimum:
1969                prop = "MINIMUM"
1970            else:
1971                prop = "MAXIMUM"
1972            return f"{prop} DATABLOCKSIZE"
1973        units = expression.args.get("units")
1974        units = f" {units}" if units else ""
1975        return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
1976
1977    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1978        autotemp = expression.args.get("autotemp")
1979        always = expression.args.get("always")
1980        default = expression.args.get("default")
1981        manual = expression.args.get("manual")
1982        never = expression.args.get("never")
1983
1984        if autotemp is not None:
1985            prop = f"AUTOTEMP({self.expressions(autotemp)})"
1986        elif always:
1987            prop = "ALWAYS"
1988        elif default:
1989            prop = "DEFAULT"
1990        elif manual:
1991            prop = "MANUAL"
1992        elif never:
1993            prop = "NEVER"
1994        return f"BLOCKCOMPRESSION={prop}"
1995
1996    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1997        no = expression.args.get("no")
1998        no = " NO" if no else ""
1999        concurrent = expression.args.get("concurrent")
2000        concurrent = " CONCURRENT" if concurrent else ""
2001        target = self.sql(expression, "target")
2002        target = f" {target}" if target else ""
2003        return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2004
2005    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
2006        if isinstance(expression.this, list):
2007            return f"IN ({self.expressions(expression, key='this', flat=True)})"
2008        if expression.this:
2009            modulus = self.sql(expression, "this")
2010            remainder = self.sql(expression, "expression")
2011            return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
2012
2013        from_expressions = self.expressions(expression, key="from_expressions", flat=True)
2014        to_expressions = self.expressions(expression, key="to_expressions", flat=True)
2015        return f"FROM ({from_expressions}) TO ({to_expressions})"
2016
2017    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
2018        this = self.sql(expression, "this")
2019
2020        for_values_or_default = expression.expression
2021        if isinstance(for_values_or_default, exp.PartitionBoundSpec):
2022            for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
2023        else:
2024            for_values_or_default = " DEFAULT"
2025
2026        return f"PARTITION OF {this}{for_values_or_default}"
2027
2028    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
2029        kind = expression.args.get("kind")
2030        this = f" {self.sql(expression, 'this')}" if expression.this else ""
2031        for_or_in = expression.args.get("for_or_in")
2032        for_or_in = f" {for_or_in}" if for_or_in else ""
2033        lock_type = expression.args.get("lock_type")
2034        override = " OVERRIDE" if expression.args.get("override") else ""
2035        return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2036
2037    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
2038        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
2039        statistics = expression.args.get("statistics")
2040        statistics_sql = ""
2041        if statistics is not None:
2042            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
2043        return f"{data_sql}{statistics_sql}"
2044
2045    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
2046        this = self.sql(expression, "this")
2047        this = f"HISTORY_TABLE={this}" if this else ""
2048        data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
2049        data_consistency = (
2050            f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
2051        )
2052        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
2053        retention_period = (
2054            f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
2055        )
2056
2057        if this:
2058            on_sql = self.func("ON", this, data_consistency, retention_period)
2059        else:
2060            on_sql = "ON" if expression.args.get("on") else "OFF"
2061
2062        sql = f"SYSTEM_VERSIONING={on_sql}"
2063
2064        return f"WITH({sql})" if expression.args.get("with_") else sql
2065
2066    def insert_sql(self, expression: exp.Insert) -> str:
2067        hint = self.sql(expression, "hint")
2068        overwrite = expression.args.get("overwrite")
2069
2070        if isinstance(expression.this, exp.Directory):
2071            this = " OVERWRITE" if overwrite else " INTO"
2072        else:
2073            this = self.INSERT_OVERWRITE if overwrite else " INTO"
2074
2075        stored = self.sql(expression, "stored")
2076        stored = f" {stored}" if stored else ""
2077        alternative = expression.args.get("alternative")
2078        alternative = f" OR {alternative}" if alternative else ""
2079        ignore = " IGNORE" if expression.args.get("ignore") else ""
2080        is_function = expression.args.get("is_function")
2081        if is_function:
2082            this = f"{this} FUNCTION"
2083        this = f"{this} {self.sql(expression, 'this')}"
2084
2085        exists = " IF EXISTS" if expression.args.get("exists") else ""
2086        where = self.sql(expression, "where")
2087        where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
2088        expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
2089        on_conflict = self.sql(expression, "conflict")
2090        on_conflict = f" {on_conflict}" if on_conflict else ""
2091        by_name = " BY NAME" if expression.args.get("by_name") else ""
2092        default_values = "DEFAULT VALUES" if expression.args.get("default") else ""
2093        returning = self.sql(expression, "returning")
2094
2095        if self.RETURNING_END:
2096            expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}"
2097        else:
2098            expression_sql = f"{returning}{expression_sql}{on_conflict}"
2099
2100        partition_by = self.sql(expression, "partition")
2101        partition_by = f" {partition_by}" if partition_by else ""
2102        settings = self.sql(expression, "settings")
2103        settings = f" {settings}" if settings else ""
2104
2105        source = self.sql(expression, "source")
2106        source = f"TABLE {source}" if source else ""
2107
2108        sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}"
2109        return self.prepend_ctes(expression, sql)
2110
2111    def introducer_sql(self, expression: exp.Introducer) -> str:
2112        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
2113
2114    def kill_sql(self, expression: exp.Kill) -> str:
2115        kind = self.sql(expression, "kind")
2116        kind = f" {kind}" if kind else ""
2117        this = self.sql(expression, "this")
2118        this = f" {this}" if this else ""
2119        return f"KILL{kind}{this}"
2120
2121    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
2122        return expression.name
2123
2124    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
2125        return expression.name
2126
2127    def onconflict_sql(self, expression: exp.OnConflict) -> str:
2128        conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
2129
2130        constraint = self.sql(expression, "constraint")
2131        constraint = f" ON CONSTRAINT {constraint}" if constraint else ""
2132
2133        conflict_keys = self.expressions(expression, key="conflict_keys", flat=True)
2134        if conflict_keys:
2135            conflict_keys = f"({conflict_keys})"
2136
2137        index_predicate = self.sql(expression, "index_predicate")
2138        conflict_keys = f"{conflict_keys}{index_predicate} "
2139
2140        action = self.sql(expression, "action")
2141
2142        expressions = self.expressions(expression, flat=True)
2143        if expressions:
2144            set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
2145            expressions = f" {set_keyword}{expressions}"
2146
2147        where = self.sql(expression, "where")
2148        return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
2149
2150    def returning_sql(self, expression: exp.Returning) -> str:
2151        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
2152
2153    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
2154        fields = self.sql(expression, "fields")
2155        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
2156        escaped = self.sql(expression, "escaped")
2157        escaped = f" ESCAPED BY {escaped}" if escaped else ""
2158        items = self.sql(expression, "collection_items")
2159        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
2160        keys = self.sql(expression, "map_keys")
2161        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
2162        lines = self.sql(expression, "lines")
2163        lines = f" LINES TERMINATED BY {lines}" if lines else ""
2164        null = self.sql(expression, "null")
2165        null = f" NULL DEFINED AS {null}" if null else ""
2166        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2167
2168    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
2169        return f"WITH ({self.expressions(expression, flat=True)})"
2170
2171    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
2172        this = f"{self.sql(expression, 'this')} INDEX"
2173        target = self.sql(expression, "target")
2174        target = f" FOR {target}" if target else ""
2175        return f"{this}{target} ({self.expressions(expression, flat=True)})"
2176
2177    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:
2178        this = self.sql(expression, "this")
2179        kind = self.sql(expression, "kind")
2180        expr = self.sql(expression, "expression")
2181        return f"{this} ({kind} => {expr})"
2182
2183    def table_parts(self, expression: exp.Table) -> str:
2184        return ".".join(
2185            self.sql(part)
2186            for part in (
2187                expression.args.get("catalog"),
2188                expression.args.get("db"),
2189                expression.args.get("this"),
2190            )
2191            if part is not None
2192        )
2193
2194    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
2195        table = self.table_parts(expression)
2196        only = "ONLY " if expression.args.get("only") else ""
2197        partition = self.sql(expression, "partition")
2198        partition = f" {partition}" if partition else ""
2199        version = self.sql(expression, "version")
2200        version = f" {version}" if version else ""
2201        alias = self.sql(expression, "alias")
2202        alias = f"{sep}{alias}" if alias else ""
2203
2204        sample = self.sql(expression, "sample")
2205        post_alias = ""
2206        pre_alias = ""
2207
2208        if self.dialect.ALIAS_POST_TABLESAMPLE:
2209            pre_alias = sample
2210        else:
2211            post_alias = sample
2212
2213        if self.dialect.ALIAS_POST_VERSION:
2214            pre_alias = f"{pre_alias}{version}"
2215        else:
2216            post_alias = f"{post_alias}{version}"
2217
2218        hints = self.expressions(expression, key="hints", sep=" ")
2219        hints = f" {hints}" if hints and self.TABLE_HINTS else ""
2220        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2221        joins = self.indent(
2222            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2223        )
2224        laterals = self.expressions(expression, key="laterals", sep="")
2225
2226        file_format = self.sql(expression, "format")
2227        if file_format:
2228            pattern = self.sql(expression, "pattern")
2229            pattern = f", PATTERN => {pattern}" if pattern else ""
2230            file_format = f" (FILE_FORMAT => {file_format}{pattern})"
2231
2232        ordinality = expression.args.get("ordinality") or ""
2233        if ordinality:
2234            ordinality = f" WITH ORDINALITY{alias}"
2235            alias = ""
2236
2237        when = self.sql(expression, "when")
2238        if when:
2239            table = f"{table} {when}"
2240
2241        changes = self.sql(expression, "changes")
2242        changes = f" {changes}" if changes else ""
2243
2244        rows_from = self.expressions(expression, key="rows_from")
2245        if rows_from:
2246            table = f"ROWS FROM {self.wrap(rows_from)}"
2247
2248        indexed = expression.args.get("indexed")
2249        if indexed is not None:
2250            indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED"
2251        else:
2252            indexed = ""
2253
2254        return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2255
2256    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:
2257        table = self.func("TABLE", expression.this)
2258        alias = self.sql(expression, "alias")
2259        alias = f" AS {alias}" if alias else ""
2260        sample = self.sql(expression, "sample")
2261        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2262        joins = self.indent(
2263            self.expressions(expression, key="joins", sep="", flat=True), skip_first=True
2264        )
2265        return f"{table}{alias}{pivots}{sample}{joins}"
2266
2267    def tablesample_sql(
2268        self,
2269        expression: exp.TableSample,
2270        tablesample_keyword: t.Optional[str] = None,
2271    ) -> str:
2272        method = self.sql(expression, "method")
2273        method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else ""
2274        numerator = self.sql(expression, "bucket_numerator")
2275        denominator = self.sql(expression, "bucket_denominator")
2276        field = self.sql(expression, "bucket_field")
2277        field = f" ON {field}" if field else ""
2278        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
2279        seed = self.sql(expression, "seed")
2280        seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else ""
2281
2282        size = self.sql(expression, "size")
2283        if size and self.TABLESAMPLE_SIZE_IS_ROWS:
2284            size = f"{size} ROWS"
2285
2286        percent = self.sql(expression, "percent")
2287        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
2288            percent = f"{percent} PERCENT"
2289
2290        expr = f"{bucket}{percent}{size}"
2291        if self.TABLESAMPLE_REQUIRES_PARENS:
2292            expr = f"({expr})"
2293
2294        return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2295
2296    def pivot_sql(self, expression: exp.Pivot) -> str:
2297        expressions = self.expressions(expression, flat=True)
2298        direction = "UNPIVOT" if expression.unpivot else "PIVOT"
2299
2300        group = self.sql(expression, "group")
2301
2302        if expression.this:
2303            this = self.sql(expression, "this")
2304            if not expressions:
2305                sql = f"UNPIVOT {this}"
2306            else:
2307                on = f"{self.seg('ON')} {expressions}"
2308                into = self.sql(expression, "into")
2309                into = f"{self.seg('INTO')} {into}" if into else ""
2310                using = self.expressions(expression, key="using", flat=True)
2311                using = f"{self.seg('USING')} {using}" if using else ""
2312                sql = f"{direction} {this}{on}{into}{using}{group}"
2313            return self.prepend_ctes(expression, sql)
2314
2315        alias = self.sql(expression, "alias")
2316        alias = f" AS {alias}" if alias else ""
2317
2318        fields = self.expressions(
2319            expression,
2320            "fields",
2321            sep=" ",
2322            dynamic=True,
2323            new_line=True,
2324            skip_first=True,
2325            skip_last=True,
2326        )
2327
2328        include_nulls = expression.args.get("include_nulls")
2329        if include_nulls is not None:
2330            nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
2331        else:
2332            nulls = ""
2333
2334        default_on_null = self.sql(expression, "default_on_null")
2335        default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else ""
2336        sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2337        return self.prepend_ctes(expression, sql)
2338
2339    def version_sql(self, expression: exp.Version) -> str:
2340        this = f"FOR {expression.name}"
2341        kind = expression.text("kind")
2342        expr = self.sql(expression, "expression")
2343        return f"{this} {kind} {expr}"
2344
2345    def tuple_sql(self, expression: exp.Tuple) -> str:
2346        return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
2347
2348    def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]:
2349        """
2350        Returns (join_sql, from_sql) for UPDATE statements.
2351        - join_sql: placed after UPDATE table, before SET
2352        - from_sql: placed after SET clause (standard position)
2353        Dialects like MySQL need to convert FROM to JOIN syntax.
2354        """
2355        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")):
2356            return ("", self.sql(expression, "from_"))
2357
2358        # Qualify unqualified columns in SET clause with the target table
2359        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity
2360        target_table = expression.this
2361        if isinstance(target_table, exp.Table):
2362            target_name = exp.to_identifier(target_table.alias_or_name)
2363            for eq in expression.expressions:
2364                col = eq.this
2365                if isinstance(col, exp.Column) and not col.table:
2366                    col.set("table", target_name)
2367
2368        table = from_expr.this
2369        if nested_joins := table.args.get("joins", []):
2370            table.set("joins", None)
2371
2372        join_sql = self.sql(exp.Join(this=table, on=exp.true()))
2373        for nested in nested_joins:
2374            if not nested.args.get("on") and not nested.args.get("using"):
2375                nested.set("on", exp.true())
2376            join_sql += self.sql(nested)
2377
2378        return (join_sql, "")
2379
2380    def update_sql(self, expression: exp.Update) -> str:
2381        this = self.sql(expression, "this")
2382        join_sql, from_sql = self._update_from_joins_sql(expression)
2383        set_sql = self.expressions(expression, flat=True)
2384        where_sql = self.sql(expression, "where")
2385        returning = self.sql(expression, "returning")
2386        order = self.sql(expression, "order")
2387        limit = self.sql(expression, "limit")
2388        if self.RETURNING_END:
2389            expression_sql = f"{from_sql}{where_sql}{returning}"
2390        else:
2391            expression_sql = f"{returning}{from_sql}{where_sql}"
2392        options = self.expressions(expression, key="options")
2393        options = f" OPTION({options})" if options else ""
2394        sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}"
2395        return self.prepend_ctes(expression, sql)
2396
2397    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
2398        values_as_table = values_as_table and self.VALUES_AS_TABLE
2399
2400        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
2401        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):
2402            args = self.expressions(expression)
2403            alias = self.sql(expression, "alias")
2404            values = f"VALUES{self.seg('')}{args}"
2405            values = (
2406                f"({values})"
2407                if self.WRAP_DERIVED_VALUES
2408                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))
2409                else values
2410            )
2411            values = self.query_modifiers(expression, values)
2412            return f"{values} AS {alias}" if alias else values
2413
2414        # Converts `VALUES...` expression into a series of select unions.
2415        alias_node = expression.args.get("alias")
2416        column_names = alias_node and alias_node.columns
2417
2418        selects: t.List[exp.Query] = []
2419
2420        for i, tup in enumerate(expression.expressions):
2421            row = tup.expressions
2422
2423            if i == 0 and column_names:
2424                row = [
2425                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
2426                ]
2427
2428            selects.append(exp.Select(expressions=row))
2429
2430        if self.pretty:
2431            # This may result in poor performance for large-cardinality `VALUES` tables, due to
2432            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
2433            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
2434            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
2435            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))
2436
2437        alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
2438        unions = " UNION ALL ".join(self.sql(select) for select in selects)
2439        return f"({unions}){alias}"
2440
2441    def var_sql(self, expression: exp.Var) -> str:
2442        return self.sql(expression, "this")
2443
2444    @unsupported_args("expressions")
2445    def into_sql(self, expression: exp.Into) -> str:
2446        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
2447        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
2448        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2449
2450    def from_sql(self, expression: exp.From) -> str:
2451        return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
2452
2453    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:
2454        grouping_sets = self.expressions(expression, indent=False)
2455        return f"GROUPING SETS {self.wrap(grouping_sets)}"
2456
2457    def rollup_sql(self, expression: exp.Rollup) -> str:
2458        expressions = self.expressions(expression, indent=False)
2459        return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
2460
2461    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:
2462        this = self.sql(expression, "this")
2463
2464        columns = self.expressions(expression, flat=True)
2465
2466        from_sql = self.sql(expression, "from_index")
2467        from_sql = f" FROM {from_sql}" if from_sql else ""
2468
2469        properties = expression.args.get("properties")
2470        properties_sql = (
2471            f" {self.properties(properties, prefix='PROPERTIES')}" if properties else ""
2472        )
2473
2474        return f"{this}({columns}){from_sql}{properties_sql}"
2475
2476    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:
2477        return f"ROLLUP ({self.expressions(expression, flat=True)})"
2478
2479    def cube_sql(self, expression: exp.Cube) -> str:
2480        expressions = self.expressions(expression, indent=False)
2481        return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
2482
2483    def group_sql(self, expression: exp.Group) -> str:
2484        group_by_all = expression.args.get("all")
2485        if group_by_all is True:
2486            modifier = " ALL"
2487        elif group_by_all is False:
2488            modifier = " DISTINCT"
2489        else:
2490            modifier = ""
2491
2492        group_by = self.op_expressions(f"GROUP BY{modifier}", expression)
2493
2494        grouping_sets = self.expressions(expression, key="grouping_sets")
2495        cube = self.expressions(expression, key="cube")
2496        rollup = self.expressions(expression, key="rollup")
2497
2498        groupings = csv(
2499            self.seg(grouping_sets) if grouping_sets else "",
2500            self.seg(cube) if cube else "",
2501            self.seg(rollup) if rollup else "",
2502            self.seg("WITH TOTALS") if expression.args.get("totals") else "",
2503            sep=self.GROUPINGS_SEP,
2504        )
2505
2506        if (
2507            expression.expressions
2508            and groupings
2509            and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP")
2510        ):
2511            group_by = f"{group_by}{self.GROUPINGS_SEP}"
2512
2513        return f"{group_by}{groupings}"
2514
2515    def having_sql(self, expression: exp.Having) -> str:
2516        this = self.indent(self.sql(expression, "this"))
2517        return f"{self.seg('HAVING')}{self.sep()}{this}"
2518
2519    def connect_sql(self, expression: exp.Connect) -> str:
2520        start = self.sql(expression, "start")
2521        start = self.seg(f"START WITH {start}") if start else ""
2522        nocycle = " NOCYCLE" if expression.args.get("nocycle") else ""
2523        connect = self.sql(expression, "connect")
2524        connect = self.seg(f"CONNECT BY{nocycle} {connect}")
2525        return start + connect
2526
2527    def prior_sql(self, expression: exp.Prior) -> str:
2528        return f"PRIOR {self.sql(expression, 'this')}"
2529
2530    def join_sql(self, expression: exp.Join) -> str:
2531        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
2532            side = None
2533        else:
2534            side = expression.side
2535
2536        op_sql = " ".join(
2537            op
2538            for op in (
2539                expression.method,
2540                "GLOBAL" if expression.args.get("global_") else None,
2541                side,
2542                expression.kind,
2543                expression.hint if self.JOIN_HINTS else None,
2544                "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None,
2545            )
2546            if op
2547        )
2548        match_cond = self.sql(expression, "match_condition")
2549        match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else ""
2550        on_sql = self.sql(expression, "on")
2551        using = expression.args.get("using")
2552
2553        if not on_sql and using:
2554            on_sql = csv(*(self.sql(column) for column in using))
2555
2556        this = expression.this
2557        this_sql = self.sql(this)
2558
2559        exprs = self.expressions(expression)
2560        if exprs:
2561            this_sql = f"{this_sql},{self.seg(exprs)}"
2562
2563        if on_sql:
2564            on_sql = self.indent(on_sql, skip_first=True)
2565            space = self.seg(" " * self.pad) if self.pretty else " "
2566            if using:
2567                on_sql = f"{space}USING ({on_sql})"
2568            else:
2569                on_sql = f"{space}ON {on_sql}"
2570        elif not op_sql:
2571            if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None:
2572                return f" {this_sql}"
2573
2574            return f", {this_sql}"
2575
2576        if op_sql != "STRAIGHT_JOIN":
2577            op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
2578
2579        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
2580        return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2581
2582    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str:
2583        args = self.expressions(expression, flat=True)
2584        args = f"({args})" if wrap and len(args.split(",")) > 1 else args
2585        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
2586
2587    def lateral_op(self, expression: exp.Lateral) -> str:
2588        cross_apply = expression.args.get("cross_apply")
2589
2590        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/
2591        if cross_apply is True:
2592            op = "INNER JOIN "
2593        elif cross_apply is False:
2594            op = "LEFT JOIN "
2595        else:
2596            op = ""
2597
2598        return f"{op}LATERAL"
2599
2600    def lateral_sql(self, expression: exp.Lateral) -> str:
2601        this = self.sql(expression, "this")
2602
2603        if expression.args.get("view"):
2604            alias = expression.args["alias"]
2605            columns = self.expressions(alias, key="columns", flat=True)
2606            table = f" {alias.name}" if alias.name else ""
2607            columns = f" AS {columns}" if columns else ""
2608            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
2609            return f"{op_sql}{self.sep()}{this}{table}{columns}"
2610
2611        alias = self.sql(expression, "alias")
2612        alias = f" AS {alias}" if alias else ""
2613
2614        ordinality = expression.args.get("ordinality") or ""
2615        if ordinality:
2616            ordinality = f" WITH ORDINALITY{alias}"
2617            alias = ""
2618
2619        return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2620
2621    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
2622        this = self.sql(expression, "this")
2623
2624        args = [
2625            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
2626            for e in (expression.args.get(k) for k in ("offset", "expression"))
2627            if e
2628        ]
2629
2630        args_sql = ", ".join(self.sql(e) for e in args)
2631        args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql
2632        expressions = self.expressions(expression, flat=True)
2633        limit_options = self.sql(expression, "limit_options")
2634        expressions = f" BY {expressions}" if expressions else ""
2635
2636        return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2637
2638    def offset_sql(self, expression: exp.Offset) -> str:
2639        this = self.sql(expression, "this")
2640        value = expression.expression
2641        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value
2642        expressions = self.expressions(expression, flat=True)
2643        expressions = f" BY {expressions}" if expressions else ""
2644        return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2645
2646    def setitem_sql(self, expression: exp.SetItem) -> str:
2647        kind = self.sql(expression, "kind")
2648        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE":
2649            kind = ""
2650        else:
2651            kind = f"{kind} " if kind else ""
2652        this = self.sql(expression, "this")
2653        expressions = self.expressions(expression)
2654        collate = self.sql(expression, "collate")
2655        collate = f" COLLATE {collate}" if collate else ""
2656        global_ = "GLOBAL " if expression.args.get("global_") else ""
2657        return f"{global_}{kind}{this}{expressions}{collate}"
2658
2659    def set_sql(self, expression: exp.Set) -> str:
2660        expressions = f" {self.expressions(expression, flat=True)}"
2661        tag = " TAG" if expression.args.get("tag") else ""
2662        return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2663
2664    def queryband_sql(self, expression: exp.QueryBand) -> str:
2665        this = self.sql(expression, "this")
2666        update = " UPDATE" if expression.args.get("update") else ""
2667        scope = self.sql(expression, "scope")
2668        scope = f" FOR {scope}" if scope else ""
2669
2670        return f"QUERY_BAND = {this}{update}{scope}"
2671
2672    def pragma_sql(self, expression: exp.Pragma) -> str:
2673        return f"PRAGMA {self.sql(expression, 'this')}"
2674
2675    def lock_sql(self, expression: exp.Lock) -> str:
2676        if not self.LOCKING_READS_SUPPORTED:
2677            self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
2678            return ""
2679
2680        update = expression.args["update"]
2681        key = expression.args.get("key")
2682        if update:
2683            lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE"
2684        else:
2685            lock_type = "FOR KEY SHARE" if key else "FOR SHARE"
2686        expressions = self.expressions(expression, flat=True)
2687        expressions = f" OF {expressions}" if expressions else ""
2688        wait = expression.args.get("wait")
2689
2690        if wait is not None:
2691            if isinstance(wait, exp.Literal):
2692                wait = f" WAIT {self.sql(wait)}"
2693            else:
2694                wait = " NOWAIT" if wait else " SKIP LOCKED"
2695
2696        return f"{lock_type}{expressions}{wait or ''}"
2697
2698    def literal_sql(self, expression: exp.Literal) -> str:
2699        text = expression.this or ""
2700        if expression.is_string:
2701            text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}"
2702        return text
2703
2704    def escape_str(
2705        self,
2706        text: str,
2707        escape_backslash: bool = True,
2708        delimiter: t.Optional[str] = None,
2709        escaped_delimiter: t.Optional[str] = None,
2710        is_byte_string: bool = False,
2711    ) -> str:
2712        if is_byte_string:
2713            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES
2714        else:
2715            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES
2716
2717        if supports_escape_sequences:
2718            text = "".join(
2719                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch
2720                for ch in text
2721            )
2722
2723        delimiter = delimiter or self.dialect.QUOTE_END
2724        escaped_delimiter = escaped_delimiter or self._escaped_quote_end
2725
2726        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2727
2728    def loaddata_sql(self, expression: exp.LoadData) -> str:
2729        local = " LOCAL" if expression.args.get("local") else ""
2730        inpath = f" INPATH {self.sql(expression, 'inpath')}"
2731        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
2732        this = f" INTO TABLE {self.sql(expression, 'this')}"
2733        partition = self.sql(expression, "partition")
2734        partition = f" {partition}" if partition else ""
2735        input_format = self.sql(expression, "input_format")
2736        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
2737        serde = self.sql(expression, "serde")
2738        serde = f" SERDE {serde}" if serde else ""
2739        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2740
2741    def null_sql(self, *_) -> str:
2742        return "NULL"
2743
2744    def boolean_sql(self, expression: exp.Boolean) -> str:
2745        return "TRUE" if expression.this else "FALSE"
2746
2747    def booland_sql(self, expression: exp.Booland) -> str:
2748        return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
2749
2750    def boolor_sql(self, expression: exp.Boolor) -> str:
2751        return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
2752
2753    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
2754        this = self.sql(expression, "this")
2755        this = f"{this} " if this else this
2756        siblings = "SIBLINGS " if expression.args.get("siblings") else ""
2757        return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat)  # type: ignore
2758
2759    def withfill_sql(self, expression: exp.WithFill) -> str:
2760        from_sql = self.sql(expression, "from_")
2761        from_sql = f" FROM {from_sql}" if from_sql else ""
2762        to_sql = self.sql(expression, "to")
2763        to_sql = f" TO {to_sql}" if to_sql else ""
2764        step_sql = self.sql(expression, "step")
2765        step_sql = f" STEP {step_sql}" if step_sql else ""
2766        interpolated_values = [
2767            f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}"
2768            if isinstance(e, exp.Alias)
2769            else self.sql(e, "this")
2770            for e in expression.args.get("interpolate") or []
2771        ]
2772        interpolate = (
2773            f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else ""
2774        )
2775        return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2776
2777    def cluster_sql(self, expression: exp.Cluster) -> str:
2778        return self.op_expressions("CLUSTER BY", expression)
2779
2780    def distribute_sql(self, expression: exp.Distribute) -> str:
2781        return self.op_expressions("DISTRIBUTE BY", expression)
2782
2783    def sort_sql(self, expression: exp.Sort) -> str:
2784        return self.op_expressions("SORT BY", expression)
2785
2786    def ordered_sql(self, expression: exp.Ordered) -> str:
2787        desc = expression.args.get("desc")
2788        asc = not desc
2789
2790        nulls_first = expression.args.get("nulls_first")
2791        nulls_last = not nulls_first
2792        nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large"
2793        nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small"
2794        nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last"
2795
2796        this = self.sql(expression, "this")
2797
2798        sort_order = " DESC" if desc else (" ASC" if desc is False else "")
2799        nulls_sort_change = ""
2800        if nulls_first and (
2801            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
2802        ):
2803            nulls_sort_change = " NULLS FIRST"
2804        elif (
2805            nulls_last
2806            and ((asc and nulls_are_small) or (desc and nulls_are_large))
2807            and not nulls_are_last
2808        ):
2809            nulls_sort_change = " NULLS LAST"
2810
2811        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
2812        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
2813            window = expression.find_ancestor(exp.Window, exp.Select)
2814            if isinstance(window, exp.Window) and window.args.get("spec"):
2815                self.unsupported(
2816                    f"'{nulls_sort_change.strip()}' translation not supported in window functions"
2817                )
2818                nulls_sort_change = ""
2819            elif self.NULL_ORDERING_SUPPORTED is False and (
2820                (asc and nulls_sort_change == " NULLS LAST")
2821                or (desc and nulls_sort_change == " NULLS FIRST")
2822            ):
2823                # BigQuery does not allow these ordering/nulls combinations when used under
2824                # an aggregation func or under a window containing one
2825                ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)
2826
2827                if isinstance(ancestor, exp.Window):
2828                    ancestor = ancestor.this
2829                if isinstance(ancestor, exp.AggFunc):
2830                    self.unsupported(
2831                        f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order"
2832                    )
2833                    nulls_sort_change = ""
2834            elif self.NULL_ORDERING_SUPPORTED is None:
2835                if expression.this.is_int:
2836                    self.unsupported(
2837                        f"'{nulls_sort_change.strip()}' translation not supported with positional ordering"
2838                    )
2839                elif not isinstance(expression.this, exp.Rand):
2840                    null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
2841                    this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
2842                nulls_sort_change = ""
2843
2844        with_fill = self.sql(expression, "with_fill")
2845        with_fill = f" {with_fill}" if with_fill else ""
2846
2847        return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2848
2849    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:
2850        window_frame = self.sql(expression, "window_frame")
2851        window_frame = f"{window_frame} " if window_frame else ""
2852
2853        this = self.sql(expression, "this")
2854
2855        return f"{window_frame}{this}"
2856
2857    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
2858        partition = self.partition_by_sql(expression)
2859        order = self.sql(expression, "order")
2860        measures = self.expressions(expression, key="measures")
2861        measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
2862        rows = self.sql(expression, "rows")
2863        rows = self.seg(rows) if rows else ""
2864        after = self.sql(expression, "after")
2865        after = self.seg(after) if after else ""
2866        pattern = self.sql(expression, "pattern")
2867        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
2868        definition_sqls = [
2869            f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
2870            for definition in expression.args.get("define", [])
2871        ]
2872        definitions = self.expressions(sqls=definition_sqls)
2873        define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
2874        body = "".join(
2875            (
2876                partition,
2877                order,
2878                measures,
2879                rows,
2880                after,
2881                pattern,
2882                define,
2883            )
2884        )
2885        alias = self.sql(expression, "alias")
2886        alias = f" {alias}" if alias else ""
2887        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2888
2889    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
2890        limit = expression.args.get("limit")
2891
2892        if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
2893            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
2894        elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
2895            limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
2896
2897        return csv(
2898            *sqls,
2899            *[self.sql(join) for join in expression.args.get("joins") or []],
2900            self.sql(expression, "match"),
2901            *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
2902            self.sql(expression, "prewhere"),
2903            self.sql(expression, "where"),
2904            self.sql(expression, "connect"),
2905            self.sql(expression, "group"),
2906            self.sql(expression, "having"),
2907            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],
2908            self.sql(expression, "order"),
2909            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
2910            *self.after_limit_modifiers(expression),
2911            self.options_modifier(expression),
2912            self.for_modifiers(expression),
2913            sep="",
2914        )
2915
2916    def options_modifier(self, expression: exp.Expression) -> str:
2917        options = self.expressions(expression, key="options")
2918        return f" {options}" if options else ""
2919
2920    def for_modifiers(self, expression: exp.Expression) -> str:
2921        for_modifiers = self.expressions(expression, key="for_")
2922        return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else ""
2923
2924    def queryoption_sql(self, expression: exp.QueryOption) -> str:
2925        self.unsupported("Unsupported query option.")
2926        return ""
2927
2928    def offset_limit_modifiers(
2929        self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
2930    ) -> t.List[str]:
2931        return [
2932            self.sql(expression, "offset") if fetch else self.sql(limit),
2933            self.sql(limit) if fetch else self.sql(expression, "offset"),
2934        ]
2935
2936    def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
2937        locks = self.expressions(expression, key="locks", sep=" ")
2938        locks = f" {locks}" if locks else ""
2939        return [locks, self.sql(expression, "sample")]
2940
2941    def select_sql(self, expression: exp.Select) -> str:
2942        into = expression.args.get("into")
2943        if not self.SUPPORTS_SELECT_INTO and into:
2944            into.pop()
2945
2946        hint = self.sql(expression, "hint")
2947        distinct = self.sql(expression, "distinct")
2948        distinct = f" {distinct}" if distinct else ""
2949        kind = self.sql(expression, "kind")
2950
2951        limit = expression.args.get("limit")
2952        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:
2953            top = self.limit_sql(limit, top=True)
2954            limit.pop()
2955        else:
2956            top = ""
2957
2958        expressions = self.expressions(expression)
2959
2960        if kind:
2961            if kind in self.SELECT_KINDS:
2962                kind = f" AS {kind}"
2963            else:
2964                if kind == "STRUCT":
2965                    expressions = self.expressions(
2966                        sqls=[
2967                            self.sql(
2968                                exp.Struct(
2969                                    expressions=[
2970                                        exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)
2971                                        if isinstance(e, exp.Alias)
2972                                        else e
2973                                        for e in expression.expressions
2974                                    ]
2975                                )
2976                            )
2977                        ]
2978                    )
2979                kind = ""
2980
2981        operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ")
2982        operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else ""
2983
2984        exclude = expression.args.get("exclude")
2985
2986        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
2987            exclude_sql = self.expressions(sqls=exclude, flat=True)
2988            expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})"
2989
2990        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
2991        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
2992        top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
2993        expressions = f"{self.sep()}{expressions}" if expressions else expressions
2994        sql = self.query_modifiers(
2995            expression,
2996            f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}",
2997            self.sql(expression, "into", comment=False),
2998            self.sql(expression, "from_", comment=False),
2999        )
3000
3001        # If both the CTE and SELECT clauses have comments, generate the latter earlier
3002        if expression.args.get("with_"):
3003            sql = self.maybe_comment(sql, expression)
3004            expression.pop_comments()
3005
3006        sql = self.prepend_ctes(expression, sql)
3007
3008        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:
3009            expression.set("exclude", None)
3010            subquery = expression.subquery(copy=False)
3011            star = exp.Star(except_=exclude)
3012            sql = self.sql(exp.select(star).from_(subquery, copy=False))
3013
3014        if not self.SUPPORTS_SELECT_INTO and into:
3015            if into.args.get("temporary"):
3016                table_kind = " TEMPORARY"
3017            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"):
3018                table_kind = " UNLOGGED"
3019            else:
3020                table_kind = ""
3021            sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}"
3022
3023        return sql
3024
3025    def schema_sql(self, expression: exp.Schema) -> str:
3026        this = self.sql(expression, "this")
3027        sql = self.schema_columns_sql(expression)
3028        return f"{this} {sql}" if this and sql else this or sql
3029
3030    def schema_columns_sql(self, expression: exp.Schema) -> str:
3031        if expression.expressions:
3032            return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
3033        return ""
3034
3035    def star_sql(self, expression: exp.Star) -> str:
3036        except_ = self.expressions(expression, key="except_", flat=True)
3037        except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
3038        replace = self.expressions(expression, key="replace", flat=True)
3039        replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
3040        rename = self.expressions(expression, key="rename", flat=True)
3041        rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
3042        return f"*{except_}{replace}{rename}"
3043
3044    def parameter_sql(self, expression: exp.Parameter) -> str:
3045        this = self.sql(expression, "this")
3046        return f"{self.PARAMETER_TOKEN}{this}"
3047
3048    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
3049        this = self.sql(expression, "this")
3050        kind = expression.text("kind")
3051        if kind:
3052            kind = f"{kind}."
3053        return f"@@{kind}{this}"
3054
3055    def placeholder_sql(self, expression: exp.Placeholder) -> str:
3056        return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?"
3057
3058    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
3059        alias = self.sql(expression, "alias")
3060        alias = f"{sep}{alias}" if alias else ""
3061        sample = self.sql(expression, "sample")
3062        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:
3063            alias = f"{sample}{alias}"
3064
3065            # Set to None so it's not generated again by self.query_modifiers()
3066            expression.set("sample", None)
3067
3068        pivots = self.expressions(expression, key="pivots", sep="", flat=True)
3069        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
3070        return self.prepend_ctes(expression, sql)
3071
3072    def qualify_sql(self, expression: exp.Qualify) -> str:
3073        this = self.indent(self.sql(expression, "this"))
3074        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
3075
3076    def unnest_sql(self, expression: exp.Unnest) -> str:
3077        args = self.expressions(expression, flat=True)
3078
3079        alias = expression.args.get("alias")
3080        offset = expression.args.get("offset")
3081
3082        if self.UNNEST_WITH_ORDINALITY:
3083            if alias and isinstance(offset, exp.Expression):
3084                alias.append("columns", offset)
3085
3086        if alias and self.dialect.UNNEST_COLUMN_ONLY:
3087            columns = alias.columns
3088            alias = self.sql(columns[0]) if columns else ""
3089        else:
3090            alias = self.sql(alias)
3091
3092        alias = f" AS {alias}" if alias else alias
3093        if self.UNNEST_WITH_ORDINALITY:
3094            suffix = f" WITH ORDINALITY{alias}" if offset else alias
3095        else:
3096            if isinstance(offset, exp.Expression):
3097                suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
3098            elif offset:
3099                suffix = f"{alias} WITH OFFSET"
3100            else:
3101                suffix = alias
3102
3103        return f"UNNEST({args}){suffix}"
3104
3105    def prewhere_sql(self, expression: exp.PreWhere) -> str:
3106        return ""
3107
3108    def where_sql(self, expression: exp.Where) -> str:
3109        this = self.indent(self.sql(expression, "this"))
3110        return f"{self.seg('WHERE')}{self.sep()}{this}"
3111
3112    def window_sql(self, expression: exp.Window) -> str:
3113        this = self.sql(expression, "this")
3114        partition = self.partition_by_sql(expression)
3115        order = expression.args.get("order")
3116        order = self.order_sql(order, flat=True) if order else ""
3117        spec = self.sql(expression, "spec")
3118        alias = self.sql(expression, "alias")
3119        over = self.sql(expression, "over") or "OVER"
3120
3121        this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
3122
3123        first = expression.args.get("first")
3124        if first is None:
3125            first = ""
3126        else:
3127            first = "FIRST" if first else "LAST"
3128
3129        if not partition and not order and not spec and alias:
3130            return f"{this} {alias}"
3131
3132        args = self.format_args(
3133            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" "
3134        )
3135        return f"{this} ({args})"
3136
3137    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
3138        partition = self.expressions(expression, key="partition_by", flat=True)
3139        return f"PARTITION BY {partition}" if partition else ""
3140
3141    def windowspec_sql(self, expression: exp.WindowSpec) -> str:
3142        kind = self.sql(expression, "kind")
3143        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
3144        end = (
3145            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
3146            or "CURRENT ROW"
3147        )
3148
3149        window_spec = f"{kind} BETWEEN {start} AND {end}"
3150
3151        exclude = self.sql(expression, "exclude")
3152        if exclude:
3153            if self.SUPPORTS_WINDOW_EXCLUDE:
3154                window_spec += f" EXCLUDE {exclude}"
3155            else:
3156                self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
3157
3158        return window_spec
3159
3160    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
3161        this = self.sql(expression, "this")
3162        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
3163        return f"{this} WITHIN GROUP ({expression_sql})"
3164
3165    def between_sql(self, expression: exp.Between) -> str:
3166        this = self.sql(expression, "this")
3167        low = self.sql(expression, "low")
3168        high = self.sql(expression, "high")
3169        symmetric = expression.args.get("symmetric")
3170
3171        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:
3172            return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})"
3173
3174        flag = (
3175            " SYMMETRIC"
3176            if symmetric
3177            else " ASYMMETRIC"
3178            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS
3179            else ""  # silently drop ASYMMETRIC – semantics identical
3180        )
3181        return f"{this} BETWEEN{flag} {low} AND {high}"
3182
3183    def bracket_offset_expressions(
3184        self, expression: exp.Bracket, index_offset: t.Optional[int] = None
3185    ) -> t.List[exp.Expression]:
3186        return apply_index_offset(
3187            expression.this,
3188            expression.expressions,
3189            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0),
3190            dialect=self.dialect,
3191        )
3192
3193    def bracket_sql(self, expression: exp.Bracket) -> str:
3194        expressions = self.bracket_offset_expressions(expression)
3195        expressions_sql = ", ".join(self.sql(e) for e in expressions)
3196        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
3197
3198    def all_sql(self, expression: exp.All) -> str:
3199        this = self.sql(expression, "this")
3200        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):
3201            this = self.wrap(this)
3202        return f"ALL {this}"
3203
3204    def any_sql(self, expression: exp.Any) -> str:
3205        this = self.sql(expression, "this")
3206        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):
3207            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):
3208                this = self.wrap(this)
3209            return f"ANY{this}"
3210        return f"ANY {this}"
3211
3212    def exists_sql(self, expression: exp.Exists) -> str:
3213        return f"EXISTS{self.wrap(expression)}"
3214
3215    def case_sql(self, expression: exp.Case) -> str:
3216        this = self.sql(expression, "this")
3217        statements = [f"CASE {this}" if this else "CASE"]
3218
3219        for e in expression.args["ifs"]:
3220            statements.append(f"WHEN {self.sql(e, 'this')}")
3221            statements.append(f"THEN {self.sql(e, 'true')}")
3222
3223        default = self.sql(expression, "default")
3224
3225        if default:
3226            statements.append(f"ELSE {default}")
3227
3228        statements.append("END")
3229
3230        if self.pretty and self.too_wide(statements):
3231            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
3232
3233        return " ".join(statements)
3234
3235    def constraint_sql(self, expression: exp.Constraint) -> str:
3236        this = self.sql(expression, "this")
3237        expressions = self.expressions(expression, flat=True)
3238        return f"CONSTRAINT {this} {expressions}"
3239
3240    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
3241        order = expression.args.get("order")
3242        order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
3243        return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
3244
3245    def extract_sql(self, expression: exp.Extract) -> str:
3246        from sqlglot.dialects.dialect import map_date_part
3247
3248        this = (
3249            map_date_part(expression.this, self.dialect)
3250            if self.NORMALIZE_EXTRACT_DATE_PARTS
3251            else expression.this
3252        )
3253        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name
3254        expression_sql = self.sql(expression, "expression")
3255
3256        return f"EXTRACT({this_sql} FROM {expression_sql})"
3257
3258    def trim_sql(self, expression: exp.Trim) -> str:
3259        trim_type = self.sql(expression, "position")
3260
3261        if trim_type == "LEADING":
3262            func_name = "LTRIM"
3263        elif trim_type == "TRAILING":
3264            func_name = "RTRIM"
3265        else:
3266            func_name = "TRIM"
3267
3268        return self.func(func_name, expression.this, expression.expression)
3269
3270    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]:
3271        args = expression.expressions
3272        if isinstance(expression, exp.ConcatWs):
3273            args = args[1:]  # Skip the delimiter
3274
3275        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3276            args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args]
3277
3278        if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"):
3279
3280            def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression:
3281                if not e.type:
3282                    from sqlglot.optimizer.annotate_types import annotate_types
3283
3284                    e = annotate_types(e, dialect=self.dialect)
3285
3286                if e.is_string or e.is_type(exp.DataType.Type.ARRAY):
3287                    return e
3288
3289                return exp.func("coalesce", e, exp.Literal.string(""))
3290
3291            args = [_wrap_with_coalesce(e) for e in args]
3292
3293        return args
3294
3295    def concat_sql(self, expression: exp.Concat) -> str:
3296        if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"):
3297            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.
3298            # Transpile to double pipe operators, which typically returns NULL if any args are NULL
3299            # instead of coalescing them to empty string.
3300            from sqlglot.dialects.dialect import concat_to_dpipe_sql
3301
3302            return concat_to_dpipe_sql(self, expression)
3303
3304        expressions = self.convert_concat_args(expression)
3305
3306        # Some dialects don't allow a single-argument CONCAT call
3307        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:
3308            return self.sql(expressions[0])
3309
3310        return self.func("CONCAT", *expressions)
3311
3312    def concatws_sql(self, expression: exp.ConcatWs) -> str:
3313        return self.func(
3314            "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)
3315        )
3316
3317    def check_sql(self, expression: exp.Check) -> str:
3318        this = self.sql(expression, key="this")
3319        return f"CHECK ({this})"
3320
3321    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
3322        expressions = self.expressions(expression, flat=True)
3323        expressions = f" ({expressions})" if expressions else ""
3324        reference = self.sql(expression, "reference")
3325        reference = f" {reference}" if reference else ""
3326        delete = self.sql(expression, "delete")
3327        delete = f" ON DELETE {delete}" if delete else ""
3328        update = self.sql(expression, "update")
3329        update = f" ON UPDATE {update}" if update else ""
3330        options = self.expressions(expression, key="options", flat=True, sep=" ")
3331        options = f" {options}" if options else ""
3332        return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3333
3334    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:
3335        this = self.sql(expression, "this")
3336        this = f" {this}" if this else ""
3337        expressions = self.expressions(expression, flat=True)
3338        include = self.sql(expression, "include")
3339        options = self.expressions(expression, key="options", flat=True, sep=" ")
3340        options = f" {options}" if options else ""
3341        return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3342
3343    def if_sql(self, expression: exp.If) -> str:
3344        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
3345
3346    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
3347        if self.MATCH_AGAINST_TABLE_PREFIX:
3348            expressions = []
3349            for expr in expression.expressions:
3350                if isinstance(expr, exp.Table):
3351                    expressions.append(f"TABLE {self.sql(expr)}")
3352                else:
3353                    expressions.append(expr)
3354        else:
3355            expressions = expression.expressions
3356
3357        modifier = expression.args.get("modifier")
3358        modifier = f" {modifier}" if modifier else ""
3359        return (
3360            f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
3361        )
3362
3363    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
3364        return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}"
3365
3366    def jsonpath_sql(self, expression: exp.JSONPath) -> str:
3367        path = self.expressions(expression, sep="", flat=True).lstrip(".")
3368
3369        if expression.args.get("escape"):
3370            path = self.escape_str(path)
3371
3372        if self.QUOTE_JSON_PATH:
3373            path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}"
3374
3375        return path
3376
3377    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:
3378        if isinstance(expression, exp.JSONPathPart):
3379            transform = self.TRANSFORMS.get(expression.__class__)
3380            if not callable(transform):
3381                self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}")
3382                return ""
3383
3384            return transform(self, expression)
3385
3386        if isinstance(expression, int):
3387            return str(expression)
3388
3389        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:
3390            escaped = expression.replace("'", "\\'")
3391            escaped = f"\\'{expression}\\'"
3392        else:
3393            escaped = expression.replace('"', '\\"')
3394            escaped = f'"{escaped}"'
3395
3396        return escaped
3397
3398    def formatjson_sql(self, expression: exp.FormatJson) -> str:
3399        return f"{self.sql(expression, 'this')} FORMAT JSON"
3400
3401    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:
3402        # Output the Teradata column FORMAT override.
3403        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT
3404        this = self.sql(expression, "this")
3405        fmt = self.sql(expression, "format")
3406        return f"{this} (FORMAT {fmt})"
3407
3408    def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str:
3409        null_handling = expression.args.get("null_handling")
3410        null_handling = f" {null_handling}" if null_handling else ""
3411
3412        unique_keys = expression.args.get("unique_keys")
3413        if unique_keys is not None:
3414            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
3415        else:
3416            unique_keys = ""
3417
3418        return_type = self.sql(expression, "return_type")
3419        return_type = f" RETURNING {return_type}" if return_type else ""
3420        encoding = self.sql(expression, "encoding")
3421        encoding = f" ENCODING {encoding}" if encoding else ""
3422
3423        return self.func(
3424            "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG",
3425            *expression.expressions,
3426            suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
3427        )
3428
3429    def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str:
3430        return self.jsonobject_sql(expression)
3431
3432    def jsonarray_sql(self, expression: exp.JSONArray) -> str:
3433        null_handling = expression.args.get("null_handling")
3434        null_handling = f" {null_handling}" if null_handling else ""
3435        return_type = self.sql(expression, "return_type")
3436        return_type = f" RETURNING {return_type}" if return_type else ""
3437        strict = " STRICT" if expression.args.get("strict") else ""
3438        return self.func(
3439            "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
3440        )
3441
3442    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
3443        this = self.sql(expression, "this")
3444        order = self.sql(expression, "order")
3445        null_handling = expression.args.get("null_handling")
3446        null_handling = f" {null_handling}" if null_handling else ""
3447        return_type = self.sql(expression, "return_type")
3448        return_type = f" RETURNING {return_type}" if return_type else ""
3449        strict = " STRICT" if expression.args.get("strict") else ""
3450        return self.func(
3451            "JSON_ARRAYAGG",
3452            this,
3453            suffix=f"{order}{null_handling}{return_type}{strict})",
3454        )
3455
3456    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
3457        path = self.sql(expression, "path")
3458        path = f" PATH {path}" if path else ""
3459        nested_schema = self.sql(expression, "nested_schema")
3460
3461        if nested_schema:
3462            return f"NESTED{path} {nested_schema}"
3463
3464        this = self.sql(expression, "this")
3465        kind = self.sql(expression, "kind")
3466        kind = f" {kind}" if kind else ""
3467
3468        ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else ""
3469        return f"{this}{kind}{path}{ordinality}"
3470
3471    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
3472        return self.func("COLUMNS", *expression.expressions)
3473
3474    def jsontable_sql(self, expression: exp.JSONTable) -> str:
3475        this = self.sql(expression, "this")
3476        path = self.sql(expression, "path")
3477        path = f", {path}" if path else ""
3478        error_handling = expression.args.get("error_handling")
3479        error_handling = f" {error_handling}" if error_handling else ""
3480        empty_handling = expression.args.get("empty_handling")
3481        empty_handling = f" {empty_handling}" if empty_handling else ""
3482        schema = self.sql(expression, "schema")
3483        return self.func(
3484            "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
3485        )
3486
3487    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
3488        this = self.sql(expression, "this")
3489        kind = self.sql(expression, "kind")
3490        path = self.sql(expression, "path")
3491        path = f" {path}" if path else ""
3492        as_json = " AS JSON" if expression.args.get("as_json") else ""
3493        return f"{this} {kind}{path}{as_json}"
3494
3495    def openjson_sql(self, expression: exp.OpenJSON) -> str:
3496        this = self.sql(expression, "this")
3497        path = self.sql(expression, "path")
3498        path = f", {path}" if path else ""
3499        expressions = self.expressions(expression)
3500        with_ = (
3501            f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
3502            if expressions
3503            else ""
3504        )
3505        return f"OPENJSON({this}{path}){with_}"
3506
3507    def in_sql(self, expression: exp.In) -> str:
3508        query = expression.args.get("query")
3509        unnest = expression.args.get("unnest")
3510        field = expression.args.get("field")
3511        is_global = " GLOBAL" if expression.args.get("is_global") else ""
3512
3513        if query:
3514            in_sql = self.sql(query)
3515        elif unnest:
3516            in_sql = self.in_unnest_op(unnest)
3517        elif field:
3518            in_sql = self.sql(field)
3519        else:
3520            in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})"
3521
3522        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3523
3524    def in_unnest_op(self, unnest: exp.Unnest) -> str:
3525        return f"(SELECT {self.sql(unnest)})"
3526
3527    def interval_sql(self, expression: exp.Interval) -> str:
3528        unit_expression = expression.args.get("unit")
3529        unit = self.sql(unit_expression) if unit_expression else ""
3530        if not self.INTERVAL_ALLOWS_PLURAL_FORM:
3531            unit = self.TIME_PART_SINGULARS.get(unit, unit)
3532        unit = f" {unit}" if unit else ""
3533
3534        if self.SINGLE_STRING_INTERVAL:
3535            this = expression.this.name if expression.this else ""
3536            if this:
3537                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
3538                    return f"INTERVAL '{this}'{unit}"
3539                return f"INTERVAL '{this}{unit}'"
3540            return f"INTERVAL{unit}"
3541
3542        this = self.sql(expression, "this")
3543        if this:
3544            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
3545            this = f" {this}" if unwrapped else f" ({this})"
3546
3547        return f"INTERVAL{this}{unit}"
3548
3549    def return_sql(self, expression: exp.Return) -> str:
3550        return f"RETURN {self.sql(expression, 'this')}"
3551
3552    def reference_sql(self, expression: exp.Reference) -> str:
3553        this = self.sql(expression, "this")
3554        expressions = self.expressions(expression, flat=True)
3555        expressions = f"({expressions})" if expressions else ""
3556        options = self.expressions(expression, key="options", flat=True, sep=" ")
3557        options = f" {options}" if options else ""
3558        return f"REFERENCES {this}{expressions}{options}"
3559
3560    def anonymous_sql(self, expression: exp.Anonymous) -> str:
3561        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive
3562        parent = expression.parent
3563        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression
3564
3565        return self.func(
3566            self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified
3567        )
3568
3569    def paren_sql(self, expression: exp.Paren) -> str:
3570        sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
3571        return f"({sql}{self.seg(')', sep='')}"
3572
3573    def neg_sql(self, expression: exp.Neg) -> str:
3574        # This makes sure we don't convert "- - 5" to "--5", which is a comment
3575        this_sql = self.sql(expression, "this")
3576        sep = " " if this_sql[0] == "-" else ""
3577        return f"-{sep}{this_sql}"
3578
3579    def not_sql(self, expression: exp.Not) -> str:
3580        return f"NOT {self.sql(expression, 'this')}"
3581
3582    def alias_sql(self, expression: exp.Alias) -> str:
3583        alias = self.sql(expression, "alias")
3584        alias = f" AS {alias}" if alias else ""
3585        return f"{self.sql(expression, 'this')}{alias}"
3586
3587    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:
3588        alias = expression.args["alias"]
3589
3590        parent = expression.parent
3591        pivot = parent and parent.parent
3592
3593        if isinstance(pivot, exp.Pivot) and pivot.unpivot:
3594            identifier_alias = isinstance(alias, exp.Identifier)
3595            literal_alias = isinstance(alias, exp.Literal)
3596
3597            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3598                alias.replace(exp.Literal.string(alias.output_name))
3599            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:
3600                alias.replace(exp.to_identifier(alias.output_name))
3601
3602        return self.alias_sql(expression)
3603
3604    def aliases_sql(self, expression: exp.Aliases) -> str:
3605        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
3606
3607    def atindex_sql(self, expression: exp.AtTimeZone) -> str:
3608        this = self.sql(expression, "this")
3609        index = self.sql(expression, "expression")
3610        return f"{this} AT {index}"
3611
3612    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
3613        this = self.sql(expression, "this")
3614        zone = self.sql(expression, "zone")
3615        return f"{this} AT TIME ZONE {zone}"
3616
3617    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:
3618        this = self.sql(expression, "this")
3619        zone = self.sql(expression, "zone")
3620        return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'"
3621
3622    def add_sql(self, expression: exp.Add) -> str:
3623        return self.binary(expression, "+")
3624
3625    def and_sql(
3626        self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None
3627    ) -> str:
3628        return self.connector_sql(expression, "AND", stack)
3629
3630    def or_sql(
3631        self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None
3632    ) -> str:
3633        return self.connector_sql(expression, "OR", stack)
3634
3635    def xor_sql(
3636        self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None
3637    ) -> str:
3638        return self.connector_sql(expression, "XOR", stack)
3639
3640    def connector_sql(
3641        self,
3642        expression: exp.Connector,
3643        op: str,
3644        stack: t.Optional[t.List[str | exp.Expression]] = None,
3645    ) -> str:
3646        if stack is not None:
3647            if expression.expressions:
3648                stack.append(self.expressions(expression, sep=f" {op} "))
3649            else:
3650                stack.append(expression.right)
3651                if expression.comments and self.comments:
3652                    for comment in expression.comments:
3653                        if comment:
3654                            op += f" /*{self.sanitize_comment(comment)}*/"
3655                stack.extend((op, expression.left))
3656            return op
3657
3658        stack = [expression]
3659        sqls: t.List[str] = []
3660        ops = set()
3661
3662        while stack:
3663            node = stack.pop()
3664            if isinstance(node, exp.Connector):
3665                ops.add(getattr(self, f"{node.key}_sql")(node, stack))
3666            else:
3667                sql = self.sql(node)
3668                if sqls and sqls[-1] in ops:
3669                    sqls[-1] += f" {sql}"
3670                else:
3671                    sqls.append(sql)
3672
3673        sep = "\n" if self.pretty and self.too_wide(sqls) else " "
3674        return sep.join(sqls)
3675
3676    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
3677        return self.binary(expression, "&")
3678
3679    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
3680        return self.binary(expression, "<<")
3681
3682    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
3683        return f"~{self.sql(expression, 'this')}"
3684
3685    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
3686        return self.binary(expression, "|")
3687
3688    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
3689        return self.binary(expression, ">>")
3690
3691    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
3692        return self.binary(expression, "^")
3693
3694    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
3695        format_sql = self.sql(expression, "format")
3696        format_sql = f" FORMAT {format_sql}" if format_sql else ""
3697        to_sql = self.sql(expression, "to")
3698        to_sql = f" {to_sql}" if to_sql else ""
3699        action = self.sql(expression, "action")
3700        action = f" {action}" if action else ""
3701        default = self.sql(expression, "default")
3702        default = f" DEFAULT {default} ON CONVERSION ERROR" if default else ""
3703        return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3704
3705    # Base implementation that excludes safe, zone, and target_type metadata args
3706    def strtotime_sql(self, expression: exp.StrToTime) -> str:
3707        return self.func("STR_TO_TIME", expression.this, expression.args.get("format"))
3708
3709    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
3710        zone = self.sql(expression, "this")
3711        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
3712
3713    def collate_sql(self, expression: exp.Collate) -> str:
3714        if self.COLLATE_IS_FUNC:
3715            return self.function_fallback_sql(expression)
3716        return self.binary(expression, "COLLATE")
3717
3718    def command_sql(self, expression: exp.Command) -> str:
3719        return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}"
3720
3721    def comment_sql(self, expression: exp.Comment) -> str:
3722        this = self.sql(expression, "this")
3723        kind = expression.args["kind"]
3724        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
3725        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
3726        expression_sql = self.sql(expression, "expression")
3727        return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3728
3729    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
3730        this = self.sql(expression, "this")
3731        delete = " DELETE" if expression.args.get("delete") else ""
3732        recompress = self.sql(expression, "recompress")
3733        recompress = f" RECOMPRESS {recompress}" if recompress else ""
3734        to_disk = self.sql(expression, "to_disk")
3735        to_disk = f" TO DISK {to_disk}" if to_disk else ""
3736        to_volume = self.sql(expression, "to_volume")
3737        to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
3738        return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3739
3740    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
3741        where = self.sql(expression, "where")
3742        group = self.sql(expression, "group")
3743        aggregates = self.expressions(expression, key="aggregates")
3744        aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
3745
3746        if not (where or group or aggregates) and len(expression.expressions) == 1:
3747            return f"TTL {self.expressions(expression, flat=True)}"
3748
3749        return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3750
3751    def transaction_sql(self, expression: exp.Transaction) -> str:
3752        modes = self.expressions(expression, key="modes")
3753        modes = f" {modes}" if modes else ""
3754        return f"BEGIN{modes}"
3755
3756    def commit_sql(self, expression: exp.Commit) -> str:
3757        chain = expression.args.get("chain")
3758        if chain is not None:
3759            chain = " AND CHAIN" if chain else " AND NO CHAIN"
3760
3761        return f"COMMIT{chain or ''}"
3762
3763    def rollback_sql(self, expression: exp.Rollback) -> str:
3764        savepoint = expression.args.get("savepoint")
3765        savepoint = f" TO {savepoint}" if savepoint else ""
3766        return f"ROLLBACK{savepoint}"
3767
3768    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
3769        this = self.sql(expression, "this")
3770
3771        dtype = self.sql(expression, "dtype")
3772        if dtype:
3773            collate = self.sql(expression, "collate")
3774            collate = f" COLLATE {collate}" if collate else ""
3775            using = self.sql(expression, "using")
3776            using = f" USING {using}" if using else ""
3777            alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else ""
3778            return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}"
3779
3780        default = self.sql(expression, "default")
3781        if default:
3782            return f"ALTER COLUMN {this} SET DEFAULT {default}"
3783
3784        comment = self.sql(expression, "comment")
3785        if comment:
3786            return f"ALTER COLUMN {this} COMMENT {comment}"
3787
3788        visible = expression.args.get("visible")
3789        if visible:
3790            return f"ALTER COLUMN {this} SET {visible}"
3791
3792        allow_null = expression.args.get("allow_null")
3793        drop = expression.args.get("drop")
3794
3795        if not drop and not allow_null:
3796            self.unsupported("Unsupported ALTER COLUMN syntax")
3797
3798        if allow_null is not None:
3799            keyword = "DROP" if drop else "SET"
3800            return f"ALTER COLUMN {this} {keyword} NOT NULL"
3801
3802        return f"ALTER COLUMN {this} DROP DEFAULT"
3803
3804    def alterindex_sql(self, expression: exp.AlterIndex) -> str:
3805        this = self.sql(expression, "this")
3806
3807        visible = expression.args.get("visible")
3808        visible_sql = "VISIBLE" if visible else "INVISIBLE"
3809
3810        return f"ALTER INDEX {this} {visible_sql}"
3811
3812    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
3813        this = self.sql(expression, "this")
3814        if not isinstance(expression.this, exp.Var):
3815            this = f"KEY DISTKEY {this}"
3816        return f"ALTER DISTSTYLE {this}"
3817
3818    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
3819        compound = " COMPOUND" if expression.args.get("compound") else ""
3820        this = self.sql(expression, "this")
3821        expressions = self.expressions(expression, flat=True)
3822        expressions = f"({expressions})" if expressions else ""
3823        return f"ALTER{compound} SORTKEY {this or expressions}"
3824
3825    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:
3826        if not self.RENAME_TABLE_WITH_DB:
3827            # Remove db from tables
3828            expression = expression.transform(
3829                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
3830            ).assert_is(exp.AlterRename)
3831        this = self.sql(expression, "this")
3832        to_kw = " TO" if include_to else ""
3833        return f"RENAME{to_kw} {this}"
3834
3835    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
3836        exists = " IF EXISTS" if expression.args.get("exists") else ""
3837        old_column = self.sql(expression, "this")
3838        new_column = self.sql(expression, "to")
3839        return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
3840
3841    def alterset_sql(self, expression: exp.AlterSet) -> str:
3842        exprs = self.expressions(expression, flat=True)
3843        if self.ALTER_SET_WRAPPED:
3844            exprs = f"({exprs})"
3845
3846        return f"SET {exprs}"
3847
3848    def alter_sql(self, expression: exp.Alter) -> str:
3849        actions = expression.args["actions"]
3850
3851        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(
3852            actions[0], exp.ColumnDef
3853        ):
3854            actions_sql = self.expressions(expression, key="actions", flat=True)
3855            actions_sql = f"ADD {actions_sql}"
3856        else:
3857            actions_list = []
3858            for action in actions:
3859                if isinstance(action, (exp.ColumnDef, exp.Schema)):
3860                    action_sql = self.add_column_sql(action)
3861                else:
3862                    action_sql = self.sql(action)
3863                    if isinstance(action, exp.Query):
3864                        action_sql = f"AS {action_sql}"
3865
3866                actions_list.append(action_sql)
3867
3868            actions_sql = self.format_args(*actions_list).lstrip("\n")
3869
3870        exists = " IF EXISTS" if expression.args.get("exists") else ""
3871        on_cluster = self.sql(expression, "cluster")
3872        on_cluster = f" {on_cluster}" if on_cluster else ""
3873        only = " ONLY" if expression.args.get("only") else ""
3874        options = self.expressions(expression, key="options")
3875        options = f", {options}" if options else ""
3876        kind = self.sql(expression, "kind")
3877        not_valid = " NOT VALID" if expression.args.get("not_valid") else ""
3878        check = " WITH CHECK" if expression.args.get("check") else ""
3879        cascade = (
3880            " CASCADE"
3881            if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE
3882            else ""
3883        )
3884        this = self.sql(expression, "this")
3885        this = f" {this}" if this else ""
3886
3887        return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
3888
3889    def altersession_sql(self, expression: exp.AlterSession) -> str:
3890        items_sql = self.expressions(expression, flat=True)
3891        keyword = "UNSET" if expression.args.get("unset") else "SET"
3892        return f"{keyword} {items_sql}"
3893
3894    def add_column_sql(self, expression: exp.Expression) -> str:
3895        sql = self.sql(expression)
3896        if isinstance(expression, exp.Schema):
3897            column_text = " COLUMNS"
3898        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
3899            column_text = " COLUMN"
3900        else:
3901            column_text = ""
3902
3903        return f"ADD{column_text} {sql}"
3904
3905    def droppartition_sql(self, expression: exp.DropPartition) -> str:
3906        expressions = self.expressions(expression)
3907        exists = " IF EXISTS " if expression.args.get("exists") else " "
3908        return f"DROP{exists}{expressions}"
3909
3910    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
3911        return f"ADD {self.expressions(expression, indent=False)}"
3912
3913    def addpartition_sql(self, expression: exp.AddPartition) -> str:
3914        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
3915        location = self.sql(expression, "location")
3916        location = f" {location}" if location else ""
3917        return f"ADD {exists}{self.sql(expression.this)}{location}"
3918
3919    def distinct_sql(self, expression: exp.Distinct) -> str:
3920        this = self.expressions(expression, flat=True)
3921
3922        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:
3923            case = exp.case()
3924            for arg in expression.expressions:
3925                case = case.when(arg.is_(exp.null()), exp.null())
3926            this = self.sql(case.else_(f"({this})"))
3927
3928        this = f" {this}" if this else ""
3929
3930        on = self.sql(expression, "on")
3931        on = f" ON {on}" if on else ""
3932        return f"DISTINCT{this}{on}"
3933
3934    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
3935        return self._embed_ignore_nulls(expression, "IGNORE NULLS")
3936
3937    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
3938        return self._embed_ignore_nulls(expression, "RESPECT NULLS")
3939
3940    def havingmax_sql(self, expression: exp.HavingMax) -> str:
3941        this_sql = self.sql(expression, "this")
3942        expression_sql = self.sql(expression, "expression")
3943        kind = "MAX" if expression.args.get("max") else "MIN"
3944        return f"{this_sql} HAVING {kind} {expression_sql}"
3945
3946    def intdiv_sql(self, expression: exp.IntDiv) -> str:
3947        return self.sql(
3948            exp.Cast(
3949                this=exp.Div(this=expression.this, expression=expression.expression),
3950                to=exp.DataType(this=exp.DataType.Type.INT),
3951            )
3952        )
3953
3954    def dpipe_sql(self, expression: exp.DPipe) -> str:
3955        if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"):
3956            return self.func(
3957                "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten())
3958            )
3959        return self.binary(expression, "||")
3960
3961    def div_sql(self, expression: exp.Div) -> str:
3962        l, r = expression.left, expression.right
3963
3964        if not self.dialect.SAFE_DIVISION and expression.args.get("safe"):
3965            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
3966
3967        if self.dialect.TYPED_DIVISION and not expression.args.get("typed"):
3968            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):
3969                l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
3970
3971        elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"):
3972            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
3973                return self.sql(
3974                    exp.cast(
3975                        l / r,
3976                        to=exp.DataType.Type.BIGINT,
3977                    )
3978                )
3979
3980        return self.binary(expression, "/")
3981
3982    def safedivide_sql(self, expression: exp.SafeDivide) -> str:
3983        n = exp._wrap(expression.this, exp.Binary)
3984        d = exp._wrap(expression.expression, exp.Binary)
3985        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))
3986
3987    def overlaps_sql(self, expression: exp.Overlaps) -> str:
3988        return self.binary(expression, "OVERLAPS")
3989
3990    def distance_sql(self, expression: exp.Distance) -> str:
3991        return self.binary(expression, "<->")
3992
3993    def dot_sql(self, expression: exp.Dot) -> str:
3994        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
3995
3996    def eq_sql(self, expression: exp.EQ) -> str:
3997        return self.binary(expression, "=")
3998
3999    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
4000        return self.binary(expression, ":=")
4001
4002    def escape_sql(self, expression: exp.Escape) -> str:
4003        return self.binary(expression, "ESCAPE")
4004
4005    def glob_sql(self, expression: exp.Glob) -> str:
4006        return self.binary(expression, "GLOB")
4007
4008    def gt_sql(self, expression: exp.GT) -> str:
4009        return self.binary(expression, ">")
4010
4011    def gte_sql(self, expression: exp.GTE) -> str:
4012        return self.binary(expression, ">=")
4013
4014    def is_sql(self, expression: exp.Is) -> str:
4015        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
4016            return self.sql(
4017                expression.this if expression.expression.this else exp.not_(expression.this)
4018            )
4019        return self.binary(expression, "IS")
4020
4021    def _like_sql(self, expression: exp.Like | exp.ILike) -> str:
4022        this = expression.this
4023        rhs = expression.expression
4024
4025        if isinstance(expression, exp.Like):
4026            exp_class: t.Type[exp.Like | exp.ILike] = exp.Like
4027            op = "LIKE"
4028        else:
4029            exp_class = exp.ILike
4030            op = "ILIKE"
4031
4032        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:
4033            exprs = rhs.this.unnest()
4034
4035            if isinstance(exprs, exp.Tuple):
4036                exprs = exprs.expressions
4037
4038            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_
4039
4040            like_expr: exp.Expression = exp_class(this=this, expression=exprs[0])
4041            for expr in exprs[1:]:
4042                like_expr = connective(like_expr, exp_class(this=this, expression=expr))
4043
4044            parent = expression.parent
4045            if not isinstance(parent, type(like_expr)) and isinstance(parent, exp.Condition):
4046                like_expr = exp.paren(like_expr, copy=False)
4047
4048            return self.sql(like_expr)
4049
4050        return self.binary(expression, op)
4051
4052    def like_sql(self, expression: exp.Like) -> str:
4053        return self._like_sql(expression)
4054
4055    def ilike_sql(self, expression: exp.ILike) -> str:
4056        return self._like_sql(expression)
4057
4058    def match_sql(self, expression: exp.Match) -> str:
4059        return self.binary(expression, "MATCH")
4060
4061    def similarto_sql(self, expression: exp.SimilarTo) -> str:
4062        return self.binary(expression, "SIMILAR TO")
4063
4064    def lt_sql(self, expression: exp.LT) -> str:
4065        return self.binary(expression, "<")
4066
4067    def lte_sql(self, expression: exp.LTE) -> str:
4068        return self.binary(expression, "<=")
4069
4070    def mod_sql(self, expression: exp.Mod) -> str:
4071        return self.binary(expression, "%")
4072
4073    def mul_sql(self, expression: exp.Mul) -> str:
4074        return self.binary(expression, "*")
4075
4076    def neq_sql(self, expression: exp.NEQ) -> str:
4077        return self.binary(expression, "<>")
4078
4079    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
4080        return self.binary(expression, "IS NOT DISTINCT FROM")
4081
4082    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
4083        return self.binary(expression, "IS DISTINCT FROM")
4084
4085    def sub_sql(self, expression: exp.Sub) -> str:
4086        return self.binary(expression, "-")
4087
4088    def trycast_sql(self, expression: exp.TryCast) -> str:
4089        return self.cast_sql(expression, safe_prefix="TRY_")
4090
4091    def jsoncast_sql(self, expression: exp.JSONCast) -> str:
4092        return self.cast_sql(expression)
4093
4094    def try_sql(self, expression: exp.Try) -> str:
4095        if not self.TRY_SUPPORTED:
4096            self.unsupported("Unsupported TRY function")
4097            return self.sql(expression, "this")
4098
4099        return self.func("TRY", expression.this)
4100
4101    def log_sql(self, expression: exp.Log) -> str:
4102        this = expression.this
4103        expr = expression.expression
4104
4105        if self.dialect.LOG_BASE_FIRST is False:
4106            this, expr = expr, this
4107        elif self.dialect.LOG_BASE_FIRST is None and expr:
4108            if this.name in ("2", "10"):
4109                return self.func(f"LOG{this.name}", expr)
4110
4111            self.unsupported(f"Unsupported logarithm with base {self.sql(this)}")
4112
4113        return self.func("LOG", this, expr)
4114
4115    def use_sql(self, expression: exp.Use) -> str:
4116        kind = self.sql(expression, "kind")
4117        kind = f" {kind}" if kind else ""
4118        this = self.sql(expression, "this") or self.expressions(expression, flat=True)
4119        this = f" {this}" if this else ""
4120        return f"USE{kind}{this}"
4121
4122    def binary(self, expression: exp.Binary, op: str) -> str:
4123        sqls: t.List[str] = []
4124        stack: t.List[t.Union[str, exp.Expression]] = [expression]
4125        binary_type = type(expression)
4126
4127        while stack:
4128            node = stack.pop()
4129
4130            if type(node) is binary_type:
4131                op_func = node.args.get("operator")
4132                if op_func:
4133                    op = f"OPERATOR({self.sql(op_func)})"
4134
4135                stack.append(node.right)
4136                stack.append(f" {self.maybe_comment(op, comments=node.comments)} ")
4137                stack.append(node.left)
4138            else:
4139                sqls.append(self.sql(node))
4140
4141        return "".join(sqls)
4142
4143    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:
4144        to_clause = self.sql(expression, "to")
4145        if to_clause:
4146            return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})"
4147
4148        return self.function_fallback_sql(expression)
4149
4150    def function_fallback_sql(self, expression: exp.Func) -> str:
4151        args = []
4152
4153        for key in expression.arg_types:
4154            arg_value = expression.args.get(key)
4155
4156            if isinstance(arg_value, list):
4157                for value in arg_value:
4158                    args.append(value)
4159            elif arg_value is not None:
4160                args.append(arg_value)
4161
4162        if self.dialect.PRESERVE_ORIGINAL_NAMES:
4163            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
4164        else:
4165            name = expression.sql_name()
4166
4167        return self.func(name, *args)
4168
4169    def func(
4170        self,
4171        name: str,
4172        *args: t.Optional[exp.Expression | str],
4173        prefix: str = "(",
4174        suffix: str = ")",
4175        normalize: bool = True,
4176    ) -> str:
4177        name = self.normalize_func(name) if normalize else name
4178        return f"{name}{prefix}{self.format_args(*args)}{suffix}"
4179
4180    def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str:
4181        arg_sqls = tuple(
4182            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)
4183        )
4184        if self.pretty and self.too_wide(arg_sqls):
4185            return self.indent(
4186                "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True
4187            )
4188        return sep.join(arg_sqls)
4189
4190    def too_wide(self, args: t.Iterable) -> bool:
4191        return sum(len(arg) for arg in args) > self.max_text_width
4192
4193    def format_time(
4194        self,
4195        expression: exp.Expression,
4196        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,
4197        inverse_time_trie: t.Optional[t.Dict] = None,
4198    ) -> t.Optional[str]:
4199        return format_time(
4200            self.sql(expression, "format"),
4201            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,
4202            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,
4203        )
4204
4205    def expressions(
4206        self,
4207        expression: t.Optional[exp.Expression] = None,
4208        key: t.Optional[str] = None,
4209        sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
4210        flat: bool = False,
4211        indent: bool = True,
4212        skip_first: bool = False,
4213        skip_last: bool = False,
4214        sep: str = ", ",
4215        prefix: str = "",
4216        dynamic: bool = False,
4217        new_line: bool = False,
4218    ) -> str:
4219        expressions = expression.args.get(key or "expressions") if expression else sqls
4220
4221        if not expressions:
4222            return ""
4223
4224        if flat:
4225            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
4226
4227        num_sqls = len(expressions)
4228        result_sqls = []
4229
4230        for i, e in enumerate(expressions):
4231            sql = self.sql(e, comment=False)
4232            if not sql:
4233                continue
4234
4235            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
4236
4237            if self.pretty:
4238                if self.leading_comma:
4239                    result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}")
4240                else:
4241                    result_sqls.append(
4242                        f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}"
4243                    )
4244            else:
4245                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
4246
4247        if self.pretty and (not dynamic or self.too_wide(result_sqls)):
4248            if new_line:
4249                result_sqls.insert(0, "")
4250                result_sqls.append("")
4251            result_sql = "\n".join(s.rstrip() for s in result_sqls)
4252        else:
4253            result_sql = "".join(result_sqls)
4254
4255        return (
4256            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)
4257            if indent
4258            else result_sql
4259        )
4260
4261    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
4262        flat = flat or isinstance(expression.parent, exp.Properties)
4263        expressions_sql = self.expressions(expression, flat=flat)
4264        if flat:
4265            return f"{op} {expressions_sql}"
4266        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4267
4268    def naked_property(self, expression: exp.Property) -> str:
4269        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
4270        if not property_name:
4271            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
4272        return f"{property_name} {self.sql(expression, 'this')}"
4273
4274    def tag_sql(self, expression: exp.Tag) -> str:
4275        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
4276
4277    def token_sql(self, token_type: TokenType) -> str:
4278        return self.TOKEN_MAPPING.get(token_type, token_type.name)
4279
4280    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
4281        this = self.sql(expression, "this")
4282        expressions = self.no_identify(self.expressions, expression)
4283        expressions = (
4284            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
4285        )
4286        return f"{this}{expressions}" if expressions.strip() != "" else this
4287
4288    def joinhint_sql(self, expression: exp.JoinHint) -> str:
4289        this = self.sql(expression, "this")
4290        expressions = self.expressions(expression, flat=True)
4291        return f"{this}({expressions})"
4292
4293    def kwarg_sql(self, expression: exp.Kwarg) -> str:
4294        return self.binary(expression, "=>")
4295
4296    def when_sql(self, expression: exp.When) -> str:
4297        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
4298        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
4299        condition = self.sql(expression, "condition")
4300        condition = f" AND {condition}" if condition else ""
4301
4302        then_expression = expression.args.get("then")
4303        if isinstance(then_expression, exp.Insert):
4304            this = self.sql(then_expression, "this")
4305            this = f"INSERT {this}" if this else "INSERT"
4306            then = self.sql(then_expression, "expression")
4307            then = f"{this} VALUES {then}" if then else this
4308        elif isinstance(then_expression, exp.Update):
4309            if isinstance(then_expression.args.get("expressions"), exp.Star):
4310                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
4311            else:
4312                expressions_sql = self.expressions(then_expression)
4313                then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE"
4314
4315        else:
4316            then = self.sql(then_expression)
4317        return f"WHEN {matched}{source}{condition} THEN {then}"
4318
4319    def whens_sql(self, expression: exp.Whens) -> str:
4320        return self.expressions(expression, sep=" ", indent=False)
4321
4322    def merge_sql(self, expression: exp.Merge) -> str:
4323        table = expression.this
4324        table_alias = ""
4325
4326        hints = table.args.get("hints")
4327        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
4328            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
4329            table_alias = f" AS {self.sql(table.args['alias'].pop())}"
4330
4331        this = self.sql(table)
4332        using = f"USING {self.sql(expression, 'using')}"
4333        whens = self.sql(expression, "whens")
4334
4335        on = self.sql(expression, "on")
4336        on = f"ON {on}" if on else ""
4337
4338        if not on:
4339            on = self.expressions(expression, key="using_cond")
4340            on = f"USING ({on})" if on else ""
4341
4342        returning = self.sql(expression, "returning")
4343        if returning:
4344            whens = f"{whens}{returning}"
4345
4346        sep = self.sep()
4347
4348        return self.prepend_ctes(
4349            expression,
4350            f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}",
4351        )
4352
4353    @unsupported_args("format")
4354    def tochar_sql(self, expression: exp.ToChar) -> str:
4355        return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT))
4356
4357    def tonumber_sql(self, expression: exp.ToNumber) -> str:
4358        if not self.SUPPORTS_TO_NUMBER:
4359            self.unsupported("Unsupported TO_NUMBER function")
4360            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4361
4362        fmt = expression.args.get("format")
4363        if not fmt:
4364            self.unsupported("Conversion format is required for TO_NUMBER")
4365            return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE))
4366
4367        return self.func("TO_NUMBER", expression.this, fmt)
4368
4369    def dictproperty_sql(self, expression: exp.DictProperty) -> str:
4370        this = self.sql(expression, "this")
4371        kind = self.sql(expression, "kind")
4372        settings_sql = self.expressions(expression, key="settings", sep=" ")
4373        args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
4374        return f"{this}({kind}{args})"
4375
4376    def dictrange_sql(self, expression: exp.DictRange) -> str:
4377        this = self.sql(expression, "this")
4378        max = self.sql(expression, "max")
4379        min = self.sql(expression, "min")
4380        return f"{this}(MIN {min} MAX {max})"
4381
4382    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
4383        return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
4384
4385    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:
4386        return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})"
4387
4388    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/
4389    def uniquekeyproperty_sql(
4390        self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY"
4391    ) -> str:
4392        return f"{prefix} ({self.expressions(expression, flat=True)})"
4393
4394    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc
4395    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:
4396        expressions = self.expressions(expression, flat=True)
4397        expressions = f" {self.wrap(expressions)}" if expressions else ""
4398        buckets = self.sql(expression, "buckets")
4399        kind = self.sql(expression, "kind")
4400        buckets = f" BUCKETS {buckets}" if buckets else ""
4401        order = self.sql(expression, "order")
4402        return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4403
4404    def oncluster_sql(self, expression: exp.OnCluster) -> str:
4405        return ""
4406
4407    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
4408        expressions = self.expressions(expression, key="expressions", flat=True)
4409        sorted_by = self.expressions(expression, key="sorted_by", flat=True)
4410        sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
4411        buckets = self.sql(expression, "buckets")
4412        return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4413
4414    def anyvalue_sql(self, expression: exp.AnyValue) -> str:
4415        this = self.sql(expression, "this")
4416        having = self.sql(expression, "having")
4417
4418        if having:
4419            this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
4420
4421        return self.func("ANY_VALUE", this)
4422
4423    def querytransform_sql(self, expression: exp.QueryTransform) -> str:
4424        transform = self.func("TRANSFORM", *expression.expressions)
4425        row_format_before = self.sql(expression, "row_format_before")
4426        row_format_before = f" {row_format_before}" if row_format_before else ""
4427        record_writer = self.sql(expression, "record_writer")
4428        record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
4429        using = f" USING {self.sql(expression, 'command_script')}"
4430        schema = self.sql(expression, "schema")
4431        schema = f" AS {schema}" if schema else ""
4432        row_format_after = self.sql(expression, "row_format_after")
4433        row_format_after = f" {row_format_after}" if row_format_after else ""
4434        record_reader = self.sql(expression, "record_reader")
4435        record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
4436        return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4437
4438    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
4439        key_block_size = self.sql(expression, "key_block_size")
4440        if key_block_size:
4441            return f"KEY_BLOCK_SIZE = {key_block_size}"
4442
4443        using = self.sql(expression, "using")
4444        if using:
4445            return f"USING {using}"
4446
4447        parser = self.sql(expression, "parser")
4448        if parser:
4449            return f"WITH PARSER {parser}"
4450
4451        comment = self.sql(expression, "comment")
4452        if comment:
4453            return f"COMMENT {comment}"
4454
4455        visible = expression.args.get("visible")
4456        if visible is not None:
4457            return "VISIBLE" if visible else "INVISIBLE"
4458
4459        engine_attr = self.sql(expression, "engine_attr")
4460        if engine_attr:
4461            return f"ENGINE_ATTRIBUTE = {engine_attr}"
4462
4463        secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
4464        if secondary_engine_attr:
4465            return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
4466
4467        self.unsupported("Unsupported index constraint option.")
4468        return ""
4469
4470    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:
4471        enforced = " ENFORCED" if expression.args.get("enforced") else ""
4472        return f"CHECK ({self.sql(expression, 'this')}){enforced}"
4473
4474    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
4475        kind = self.sql(expression, "kind")
4476        kind = f"{kind} INDEX" if kind else "INDEX"
4477        this = self.sql(expression, "this")
4478        this = f" {this}" if this else ""
4479        index_type = self.sql(expression, "index_type")
4480        index_type = f" USING {index_type}" if index_type else ""
4481        expressions = self.expressions(expression, flat=True)
4482        expressions = f" ({expressions})" if expressions else ""
4483        options = self.expressions(expression, key="options", sep=" ")
4484        options = f" {options}" if options else ""
4485        return f"{kind}{this}{index_type}{expressions}{options}"
4486
4487    def nvl2_sql(self, expression: exp.Nvl2) -> str:
4488        if self.NVL2_SUPPORTED:
4489            return self.function_fallback_sql(expression)
4490
4491        case = exp.Case().when(
4492            expression.this.is_(exp.null()).not_(copy=False),
4493            expression.args["true"],
4494            copy=False,
4495        )
4496        else_cond = expression.args.get("false")
4497        if else_cond:
4498            case.else_(else_cond, copy=False)
4499
4500        return self.sql(case)
4501
4502    def comprehension_sql(self, expression: exp.Comprehension) -> str:
4503        this = self.sql(expression, "this")
4504        expr = self.sql(expression, "expression")
4505        position = self.sql(expression, "position")
4506        position = f", {position}" if position else ""
4507        iterator = self.sql(expression, "iterator")
4508        condition = self.sql(expression, "condition")
4509        condition = f" IF {condition}" if condition else ""
4510        return f"{this} FOR {expr}{position} IN {iterator}{condition}"
4511
4512    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
4513        return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
4514
4515    def opclass_sql(self, expression: exp.Opclass) -> str:
4516        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
4517
4518    def _ml_sql(self, expression: exp.Func, name: str) -> str:
4519        model = self.sql(expression, "this")
4520        model = f"MODEL {model}"
4521        expr = expression.expression
4522        if expr:
4523            expr_sql = self.sql(expression, "expression")
4524            expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql
4525        else:
4526            expr_sql = None
4527
4528        parameters = self.sql(expression, "params_struct") or None
4529
4530        return self.func(name, model, expr_sql, parameters)
4531
4532    def predict_sql(self, expression: exp.Predict) -> str:
4533        return self._ml_sql(expression, "PREDICT")
4534
4535    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:
4536        name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING"
4537        return self._ml_sql(expression, name)
4538
4539    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:
4540        return self._ml_sql(expression, "TRANSLATE")
4541
4542    def mlforecast_sql(self, expression: exp.MLForecast) -> str:
4543        return self._ml_sql(expression, "FORECAST")
4544
4545    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:
4546        this_sql = self.sql(expression, "this")
4547        if isinstance(expression.this, exp.Table):
4548            this_sql = f"TABLE {this_sql}"
4549
4550        return self.func(
4551            "FEATURES_AT_TIME",
4552            this_sql,
4553            expression.args.get("time"),
4554            expression.args.get("num_rows"),
4555            expression.args.get("ignore_feature_nulls"),
4556        )
4557
4558    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:
4559        this_sql = self.sql(expression, "this")
4560        if isinstance(expression.this, exp.Table):
4561            this_sql = f"TABLE {this_sql}"
4562
4563        query_table = self.sql(expression, "query_table")
4564        if isinstance(expression.args["query_table"], exp.Table):
4565            query_table = f"TABLE {query_table}"
4566
4567        return self.func(
4568            "VECTOR_SEARCH",
4569            this_sql,
4570            expression.args.get("column_to_search"),
4571            query_table,
4572            expression.args.get("query_column_to_search"),
4573            expression.args.get("top_k"),
4574            expression.args.get("distance_type"),
4575            expression.args.get("options"),
4576        )
4577
4578    def forin_sql(self, expression: exp.ForIn) -> str:
4579        this = self.sql(expression, "this")
4580        expression_sql = self.sql(expression, "expression")
4581        return f"FOR {this} DO {expression_sql}"
4582
4583    def refresh_sql(self, expression: exp.Refresh) -> str:
4584        this = self.sql(expression, "this")
4585        kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} "
4586        return f"REFRESH {kind}{this}"
4587
4588    def toarray_sql(self, expression: exp.ToArray) -> str:
4589        arg = expression.this
4590        if not arg.type:
4591            from sqlglot.optimizer.annotate_types import annotate_types
4592
4593            arg = annotate_types(arg, dialect=self.dialect)
4594
4595        if arg.is_type(exp.DataType.Type.ARRAY):
4596            return self.sql(arg)
4597
4598        cond_for_null = arg.is_(exp.null())
4599        return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4600
4601    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:
4602        this = expression.this
4603        time_format = self.format_time(expression)
4604
4605        if time_format:
4606            return self.sql(
4607                exp.cast(
4608                    exp.StrToTime(this=this, format=expression.args["format"]),
4609                    exp.DataType.Type.TIME,
4610                )
4611            )
4612
4613        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME):
4614            return self.sql(this)
4615
4616        return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4617
4618    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:
4619        this = expression.this
4620        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP):
4621            return self.sql(this)
4622
4623        return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4624
4625    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:
4626        this = expression.this
4627        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME):
4628            return self.sql(this)
4629
4630        return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4631
4632    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:
4633        this = expression.this
4634        time_format = self.format_time(expression)
4635        safe = expression.args.get("safe")
4636        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):
4637            return self.sql(
4638                exp.cast(
4639                    exp.StrToTime(this=this, format=expression.args["format"], safe=safe),
4640                    exp.DataType.Type.DATE,
4641                )
4642            )
4643
4644        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE):
4645            return self.sql(this)
4646
4647        if safe:
4648            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DataType.Type.DATE)))
4649
4650        return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4651
4652    def unixdate_sql(self, expression: exp.UnixDate) -> str:
4653        return self.sql(
4654            exp.func(
4655                "DATEDIFF",
4656                expression.this,
4657                exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
4658                "day",
4659            )
4660        )
4661
4662    def lastday_sql(self, expression: exp.LastDay) -> str:
4663        if self.LAST_DAY_SUPPORTS_DATE_PART:
4664            return self.function_fallback_sql(expression)
4665
4666        unit = expression.text("unit")
4667        if unit and unit != "MONTH":
4668            self.unsupported("Date parts are not supported in LAST_DAY.")
4669
4670        return self.func("LAST_DAY", expression.this)
4671
4672    def dateadd_sql(self, expression: exp.DateAdd) -> str:
4673        from sqlglot.dialects.dialect import unit_to_str
4674
4675        return self.func(
4676            "DATE_ADD", expression.this, expression.expression, unit_to_str(expression)
4677        )
4678
4679    def arrayany_sql(self, expression: exp.ArrayAny) -> str:
4680        if self.CAN_IMPLEMENT_ARRAY_ANY:
4681            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)
4682            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)
4683            original_is_empty = exp.ArraySize(this=expression.this).eq(0)
4684            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))
4685
4686        from sqlglot.dialects import Dialect
4687
4688        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect
4689        if self.dialect.__class__ != Dialect:
4690            self.unsupported("ARRAY_ANY is unsupported")
4691
4692        return self.function_fallback_sql(expression)
4693
4694    def struct_sql(self, expression: exp.Struct) -> str:
4695        expression.set(
4696            "expressions",
4697            [
4698                exp.alias_(e.expression, e.name if e.this.is_string else e.this)
4699                if isinstance(e, exp.PropertyEQ)
4700                else e
4701                for e in expression.expressions
4702            ],
4703        )
4704
4705        return self.function_fallback_sql(expression)
4706
4707    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:
4708        low = self.sql(expression, "this")
4709        high = self.sql(expression, "expression")
4710
4711        return f"{low} TO {high}"
4712
4713    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
4714        target = "DATABASE" if expression.args.get("is_database") else "TABLE"
4715        tables = f" {self.expressions(expression)}"
4716
4717        exists = " IF EXISTS" if expression.args.get("exists") else ""
4718
4719        on_cluster = self.sql(expression, "cluster")
4720        on_cluster = f" {on_cluster}" if on_cluster else ""
4721
4722        identity = self.sql(expression, "identity")
4723        identity = f" {identity} IDENTITY" if identity else ""
4724
4725        option = self.sql(expression, "option")
4726        option = f" {option}" if option else ""
4727
4728        partition = self.sql(expression, "partition")
4729        partition = f" {partition}" if partition else ""
4730
4731        return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4732
4733    # This transpiles T-SQL's CONVERT function
4734    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
4735    def convert_sql(self, expression: exp.Convert) -> str:
4736        to = expression.this
4737        value = expression.expression
4738        style = expression.args.get("style")
4739        safe = expression.args.get("safe")
4740        strict = expression.args.get("strict")
4741
4742        if not to or not value:
4743            return ""
4744
4745        # Retrieve length of datatype and override to default if not specified
4746        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4747            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)
4748
4749        transformed: t.Optional[exp.Expression] = None
4750        cast = exp.Cast if strict else exp.TryCast
4751
4752        # Check whether a conversion with format (T-SQL calls this 'style') is applicable
4753        if isinstance(style, exp.Literal) and style.is_int:
4754            from sqlglot.dialects.tsql import TSQL
4755
4756            style_value = style.name
4757            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)
4758            if not converted_style:
4759                self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}")
4760
4761            fmt = exp.Literal.string(converted_style)
4762
4763            if to.this == exp.DataType.Type.DATE:
4764                transformed = exp.StrToDate(this=value, format=fmt)
4765            elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2):
4766                transformed = exp.StrToTime(this=value, format=fmt)
4767            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:
4768                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)
4769            elif to.this == exp.DataType.Type.TEXT:
4770                transformed = exp.TimeToStr(this=value, format=fmt)
4771
4772        if not transformed:
4773            transformed = cast(this=value, to=to, safe=safe)
4774
4775        return self.sql(transformed)
4776
4777    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:
4778        this = expression.this
4779        if isinstance(this, exp.JSONPathWildcard):
4780            this = self.json_path_part(this)
4781            return f".{this}" if this else ""
4782
4783        if self.SAFE_JSON_PATH_KEY_RE.match(this):
4784            return f".{this}"
4785
4786        this = self.json_path_part(this)
4787        return (
4788            f"[{this}]"
4789            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED
4790            else f".{this}"
4791        )
4792
4793    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
4794        this = self.json_path_part(expression.this)
4795        return f"[{this}]" if this else ""
4796
4797    def _simplify_unless_literal(self, expression: E) -> E:
4798        if not isinstance(expression, exp.Literal):
4799            from sqlglot.optimizer.simplify import simplify
4800
4801            expression = simplify(expression, dialect=self.dialect)
4802
4803        return expression
4804
4805    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:
4806        this = expression.this
4807        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):
4808            self.unsupported(
4809                f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}"
4810            )
4811            return self.sql(this)
4812
4813        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"):
4814            # The first modifier here will be the one closest to the AggFunc's arg
4815            mods = sorted(
4816                expression.find_all(exp.HavingMax, exp.Order, exp.Limit),
4817                key=lambda x: 0
4818                if isinstance(x, exp.HavingMax)
4819                else (1 if isinstance(x, exp.Order) else 2),
4820            )
4821
4822            if mods:
4823                mod = mods[0]
4824                this = expression.__class__(this=mod.this.copy())
4825                this.meta["inline"] = True
4826                mod.this.replace(this)
4827                return self.sql(expression.this)
4828
4829            agg_func = expression.find(exp.AggFunc)
4830
4831            if agg_func:
4832                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})"
4833                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)
4834
4835        return f"{self.sql(expression, 'this')} {text}"
4836
4837    def _replace_line_breaks(self, string: str) -> str:
4838        """We don't want to extra indent line breaks so we temporarily replace them with sentinels."""
4839        if self.pretty:
4840            return string.replace("\n", self.SENTINEL_LINE_BREAK)
4841        return string
4842
4843    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
4844        option = self.sql(expression, "this")
4845
4846        if expression.expressions:
4847            upper = option.upper()
4848
4849            # Snowflake FILE_FORMAT options are separated by whitespace
4850            sep = " " if upper == "FILE_FORMAT" else ", "
4851
4852            # Databricks copy/format options do not set their list of values with EQ
4853            op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = "
4854            values = self.expressions(expression, flat=True, sep=sep)
4855            return f"{option}{op}({values})"
4856
4857        value = self.sql(expression, "expression")
4858
4859        if not value:
4860            return option
4861
4862        op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " "
4863
4864        return f"{option}{op}{value}"
4865
4866    def credentials_sql(self, expression: exp.Credentials) -> str:
4867        cred_expr = expression.args.get("credentials")
4868        if isinstance(cred_expr, exp.Literal):
4869            # Redshift case: CREDENTIALS <string>
4870            credentials = self.sql(expression, "credentials")
4871            credentials = f"CREDENTIALS {credentials}" if credentials else ""
4872        else:
4873            # Snowflake case: CREDENTIALS = (...)
4874            credentials = self.expressions(expression, key="credentials", flat=True, sep=" ")
4875            credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else ""
4876
4877        storage = self.sql(expression, "storage")
4878        storage = f"STORAGE_INTEGRATION = {storage}" if storage else ""
4879
4880        encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
4881        encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
4882
4883        iam_role = self.sql(expression, "iam_role")
4884        iam_role = f"IAM_ROLE {iam_role}" if iam_role else ""
4885
4886        region = self.sql(expression, "region")
4887        region = f" REGION {region}" if region else ""
4888
4889        return f"{credentials}{storage}{encryption}{iam_role}{region}"
4890
4891    def copy_sql(self, expression: exp.Copy) -> str:
4892        this = self.sql(expression, "this")
4893        this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
4894
4895        credentials = self.sql(expression, "credentials")
4896        credentials = self.seg(credentials) if credentials else ""
4897        files = self.expressions(expression, key="files", flat=True)
4898        kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else ""
4899
4900        sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
4901        params = self.expressions(
4902            expression,
4903            key="params",
4904            sep=sep,
4905            new_line=True,
4906            skip_last=True,
4907            skip_first=True,
4908            indent=self.COPY_PARAMS_ARE_WRAPPED,
4909        )
4910
4911        if params:
4912            if self.COPY_PARAMS_ARE_WRAPPED:
4913                params = f" WITH ({params})"
4914            elif not self.pretty and (files or credentials):
4915                params = f" {params}"
4916
4917        return f"COPY{this}{kind} {files}{credentials}{params}"
4918
4919    def semicolon_sql(self, expression: exp.Semicolon) -> str:
4920        return ""
4921
4922    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
4923        on_sql = "ON" if expression.args.get("on") else "OFF"
4924        filter_col: t.Optional[str] = self.sql(expression, "filter_column")
4925        filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
4926        retention_period: t.Optional[str] = self.sql(expression, "retention_period")
4927        retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
4928
4929        if filter_col or retention_period:
4930            on_sql = self.func("ON", filter_col, retention_period)
4931
4932        return f"DATA_DELETION={on_sql}"
4933
4934    def maskingpolicycolumnconstraint_sql(
4935        self, expression: exp.MaskingPolicyColumnConstraint
4936    ) -> str:
4937        this = self.sql(expression, "this")
4938        expressions = self.expressions(expression, flat=True)
4939        expressions = f" USING ({expressions})" if expressions else ""
4940        return f"MASKING POLICY {this}{expressions}"
4941
4942    def gapfill_sql(self, expression: exp.GapFill) -> str:
4943        this = self.sql(expression, "this")
4944        this = f"TABLE {this}"
4945        return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"])
4946
4947    def scope_resolution(self, rhs: str, scope_name: str) -> str:
4948        return self.func("SCOPE_RESOLUTION", scope_name or None, rhs)
4949
4950    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:
4951        this = self.sql(expression, "this")
4952        expr = expression.expression
4953
4954        if isinstance(expr, exp.Func):
4955            # T-SQL's CLR functions are case sensitive
4956            expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})"
4957        else:
4958            expr = self.sql(expression, "expression")
4959
4960        return self.scope_resolution(expr, this)
4961
4962    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
4963        if self.PARSE_JSON_NAME is None:
4964            return self.sql(expression.this)
4965
4966        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)
4967
4968    def rand_sql(self, expression: exp.Rand) -> str:
4969        lower = self.sql(expression, "lower")
4970        upper = self.sql(expression, "upper")
4971
4972        if lower and upper:
4973            return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}"
4974        return self.func("RAND", expression.this)
4975
4976    def changes_sql(self, expression: exp.Changes) -> str:
4977        information = self.sql(expression, "information")
4978        information = f"INFORMATION => {information}"
4979        at_before = self.sql(expression, "at_before")
4980        at_before = f"{self.seg('')}{at_before}" if at_before else ""
4981        end = self.sql(expression, "end")
4982        end = f"{self.seg('')}{end}" if end else ""
4983
4984        return f"CHANGES ({information}){at_before}{end}"
4985
4986    def pad_sql(self, expression: exp.Pad) -> str:
4987        prefix = "L" if expression.args.get("is_left") else "R"
4988
4989        fill_pattern = self.sql(expression, "fill_pattern") or None
4990        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:
4991            fill_pattern = "' '"
4992
4993        return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
4994
4995    def summarize_sql(self, expression: exp.Summarize) -> str:
4996        table = " TABLE" if expression.args.get("table") else ""
4997        return f"SUMMARIZE{table} {self.sql(expression.this)}"
4998
4999    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:
5000        generate_series = exp.GenerateSeries(**expression.args)
5001
5002        parent = expression.parent
5003        if isinstance(parent, (exp.Alias, exp.TableAlias)):
5004            parent = parent.parent
5005
5006        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):
5007            return self.sql(exp.Unnest(expressions=[generate_series]))
5008
5009        if isinstance(parent, exp.Select):
5010            self.unsupported("GenerateSeries projection unnesting is not supported.")
5011
5012        return self.sql(generate_series)
5013
5014    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:
5015        if self.SUPPORTS_CONVERT_TIMEZONE:
5016            return self.function_fallback_sql(expression)
5017
5018        source_tz = expression.args.get("source_tz")
5019        target_tz = expression.args.get("target_tz")
5020        timestamp = expression.args.get("timestamp")
5021
5022        if source_tz and timestamp:
5023            timestamp = exp.AtTimeZone(
5024                this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz
5025            )
5026
5027        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)
5028
5029        return self.sql(expr)
5030
5031    def json_sql(self, expression: exp.JSON) -> str:
5032        this = self.sql(expression, "this")
5033        this = f" {this}" if this else ""
5034
5035        _with = expression.args.get("with_")
5036
5037        if _with is None:
5038            with_sql = ""
5039        elif not _with:
5040            with_sql = " WITHOUT"
5041        else:
5042            with_sql = " WITH"
5043
5044        unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else ""
5045
5046        return f"JSON{this}{with_sql}{unique_sql}"
5047
5048    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
5049        path = self.sql(expression, "path")
5050        returning = self.sql(expression, "returning")
5051        returning = f" RETURNING {returning}" if returning else ""
5052
5053        on_condition = self.sql(expression, "on_condition")
5054        on_condition = f" {on_condition}" if on_condition else ""
5055
5056        return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5057
5058    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:
5059        else_ = "ELSE " if expression.args.get("else_") else ""
5060        condition = self.sql(expression, "expression")
5061        condition = f"WHEN {condition} THEN " if condition else else_
5062        insert = self.sql(expression, "this")[len("INSERT") :].strip()
5063        return f"{condition}{insert}"
5064
5065    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:
5066        kind = self.sql(expression, "kind")
5067        expressions = self.seg(self.expressions(expression, sep=" "))
5068        res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}"
5069        return res
5070
5071    def oncondition_sql(self, expression: exp.OnCondition) -> str:
5072        # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR"
5073        empty = expression.args.get("empty")
5074        empty = (
5075            f"DEFAULT {empty} ON EMPTY"
5076            if isinstance(empty, exp.Expression)
5077            else self.sql(expression, "empty")
5078        )
5079
5080        error = expression.args.get("error")
5081        error = (
5082            f"DEFAULT {error} ON ERROR"
5083            if isinstance(error, exp.Expression)
5084            else self.sql(expression, "error")
5085        )
5086
5087        if error and empty:
5088            error = (
5089                f"{empty} {error}"
5090                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR
5091                else f"{error} {empty}"
5092            )
5093            empty = ""
5094
5095        null = self.sql(expression, "null")
5096
5097        return f"{empty}{error}{null}"
5098
5099    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:
5100        scalar = " ON SCALAR STRING" if expression.args.get("scalar") else ""
5101        return f"{self.sql(expression, 'option')} QUOTES{scalar}"
5102
5103    def jsonexists_sql(self, expression: exp.JSONExists) -> str:
5104        this = self.sql(expression, "this")
5105        path = self.sql(expression, "path")
5106
5107        passing = self.expressions(expression, "passing")
5108        passing = f" PASSING {passing}" if passing else ""
5109
5110        on_condition = self.sql(expression, "on_condition")
5111        on_condition = f" {on_condition}" if on_condition else ""
5112
5113        path = f"{path}{passing}{on_condition}"
5114
5115        return self.func("JSON_EXISTS", this, path)
5116
5117    def _add_arrayagg_null_filter(
5118        self,
5119        array_agg_sql: str,
5120        array_agg_expr: exp.ArrayAgg,
5121        column_expr: exp.Expression,
5122    ) -> str:
5123        """
5124        Add NULL filter to ARRAY_AGG if dialect requires it.
5125
5126        Args:
5127            array_agg_sql: The generated ARRAY_AGG SQL string
5128            array_agg_expr: The ArrayAgg expression node
5129            column_expr: The column/expression to filter (before ORDER BY wrapping)
5130
5131        Returns:
5132            SQL string with FILTER clause added if needed
5133        """
5134        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls
5135        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)
5136        if not (
5137            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded")
5138        ):
5139            return array_agg_sql
5140
5141        parent = array_agg_expr.parent
5142        if isinstance(parent, exp.Filter):
5143            parent_cond = parent.expression.this
5144            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))
5145        elif column_expr.find(exp.Column):
5146            # Do not add the filter if the input is not a column (e.g. literal, struct etc)
5147            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well
5148            this_sql = (
5149                self.expressions(column_expr)
5150                if isinstance(column_expr, exp.Distinct)
5151                else self.sql(column_expr)
5152            )
5153            array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)"
5154
5155        return array_agg_sql
5156
5157    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
5158        array_agg = self.function_fallback_sql(expression)
5159        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)
5160
5161    def slice_sql(self, expression: exp.Slice) -> str:
5162        step = self.sql(expression, "step")
5163        end = self.sql(expression.expression)
5164        begin = self.sql(expression.this)
5165
5166        sql = f"{end}:{step}" if step else end
5167        return f"{begin}:{sql}" if sql else f"{begin}:"
5168
5169    def apply_sql(self, expression: exp.Apply) -> str:
5170        this = self.sql(expression, "this")
5171        expr = self.sql(expression, "expression")
5172
5173        return f"{this} APPLY({expr})"
5174
5175    def _grant_or_revoke_sql(
5176        self,
5177        expression: exp.Grant | exp.Revoke,
5178        keyword: str,
5179        preposition: str,
5180        grant_option_prefix: str = "",
5181        grant_option_suffix: str = "",
5182    ) -> str:
5183        privileges_sql = self.expressions(expression, key="privileges", flat=True)
5184
5185        kind = self.sql(expression, "kind")
5186        kind = f" {kind}" if kind else ""
5187
5188        securable = self.sql(expression, "securable")
5189        securable = f" {securable}" if securable else ""
5190
5191        principals = self.expressions(expression, key="principals", flat=True)
5192
5193        if not expression.args.get("grant_option"):
5194            grant_option_prefix = grant_option_suffix = ""
5195
5196        # cascade for revoke only
5197        cascade = self.sql(expression, "cascade")
5198        cascade = f" {cascade}" if cascade else ""
5199
5200        return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}"
5201
5202    def grant_sql(self, expression: exp.Grant) -> str:
5203        return self._grant_or_revoke_sql(
5204            expression,
5205            keyword="GRANT",
5206            preposition="TO",
5207            grant_option_suffix=" WITH GRANT OPTION",
5208        )
5209
5210    def revoke_sql(self, expression: exp.Revoke) -> str:
5211        return self._grant_or_revoke_sql(
5212            expression,
5213            keyword="REVOKE",
5214            preposition="FROM",
5215            grant_option_prefix="GRANT OPTION FOR ",
5216        )
5217
5218    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:
5219        this = self.sql(expression, "this")
5220        columns = self.expressions(expression, flat=True)
5221        columns = f"({columns})" if columns else ""
5222
5223        return f"{this}{columns}"
5224
5225    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:
5226        this = self.sql(expression, "this")
5227
5228        kind = self.sql(expression, "kind")
5229        kind = f"{kind} " if kind else ""
5230
5231        return f"{kind}{this}"
5232
5233    def columns_sql(self, expression: exp.Columns) -> str:
5234        func = self.function_fallback_sql(expression)
5235        if expression.args.get("unpack"):
5236            func = f"*{func}"
5237
5238        return func
5239
5240    def overlay_sql(self, expression: exp.Overlay) -> str:
5241        this = self.sql(expression, "this")
5242        expr = self.sql(expression, "expression")
5243        from_sql = self.sql(expression, "from_")
5244        for_sql = self.sql(expression, "for_")
5245        for_sql = f" FOR {for_sql}" if for_sql else ""
5246
5247        return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
5248
5249    @unsupported_args("format")
5250    def todouble_sql(self, expression: exp.ToDouble) -> str:
5251        cast = exp.TryCast if expression.args.get("safe") else exp.Cast
5252        return self.sql(cast(this=expression.this, to=exp.DataType.build(exp.DataType.Type.DOUBLE)))
5253
5254    def string_sql(self, expression: exp.String) -> str:
5255        this = expression.this
5256        zone = expression.args.get("zone")
5257
5258        if zone:
5259            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)
5260            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC
5261            # set for source_tz to transpile the time conversion before the STRING cast
5262            this = exp.ConvertTimezone(
5263                source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this
5264            )
5265
5266        return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
5267
5268    def median_sql(self, expression: exp.Median) -> str:
5269        if not self.SUPPORTS_MEDIAN:
5270            return self.sql(
5271                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))
5272            )
5273
5274        return self.function_fallback_sql(expression)
5275
5276    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:
5277        filler = self.sql(expression, "this")
5278        filler = f" {filler}" if filler else ""
5279        with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT"
5280        return f"TRUNCATE{filler} {with_count}"
5281
5282    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:
5283        if self.SUPPORTS_UNIX_SECONDS:
5284            return self.function_fallback_sql(expression)
5285
5286        start_ts = exp.cast(
5287            exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ
5288        )
5289
5290        return self.sql(
5291            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS"))
5292        )
5293
5294    def arraysize_sql(self, expression: exp.ArraySize) -> str:
5295        dim = expression.expression
5296
5297        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)
5298        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:
5299            if not (dim.is_int and dim.name == "1"):
5300                self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
5301            dim = None
5302
5303        # If dimension is required but not specified, default initialize it
5304        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:
5305            dim = exp.Literal.number(1)
5306
5307        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5308
5309    def attach_sql(self, expression: exp.Attach) -> str:
5310        this = self.sql(expression, "this")
5311        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
5312        expressions = self.expressions(expression)
5313        expressions = f" ({expressions})" if expressions else ""
5314
5315        return f"ATTACH{exists_sql} {this}{expressions}"
5316
5317    def detach_sql(self, expression: exp.Detach) -> str:
5318        this = self.sql(expression, "this")
5319        # the DATABASE keyword is required if IF EXISTS is set
5320        # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1)
5321        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax
5322        exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else ""
5323
5324        return f"DETACH{exists_sql} {this}"
5325
5326    def attachoption_sql(self, expression: exp.AttachOption) -> str:
5327        this = self.sql(expression, "this")
5328        value = self.sql(expression, "expression")
5329        value = f" {value}" if value else ""
5330        return f"{this}{value}"
5331
5332    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:
5333        return (
5334            f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
5335        )
5336
5337    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:
5338        encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE"
5339        encode = f"{encode} {self.sql(expression, 'this')}"
5340
5341        properties = expression.args.get("properties")
5342        if properties:
5343            encode = f"{encode} {self.properties(properties)}"
5344
5345        return encode
5346
5347    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:
5348        this = self.sql(expression, "this")
5349        include = f"INCLUDE {this}"
5350
5351        column_def = self.sql(expression, "column_def")
5352        if column_def:
5353            include = f"{include} {column_def}"
5354
5355        alias = self.sql(expression, "alias")
5356        if alias:
5357            include = f"{include} AS {alias}"
5358
5359        return include
5360
5361    def xmlelement_sql(self, expression: exp.XMLElement) -> str:
5362        prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
5363        name = f"{prefix} {self.sql(expression, 'this')}"
5364        return self.func("XMLELEMENT", name, *expression.expressions)
5365
5366    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:
5367        this = self.sql(expression, "this")
5368        expr = self.sql(expression, "expression")
5369        expr = f"({expr})" if expr else ""
5370        return f"{this}{expr}"
5371
5372    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:
5373        partitions = self.expressions(expression, "partition_expressions")
5374        create = self.expressions(expression, "create_expressions")
5375        return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
5376
5377    def partitionbyrangepropertydynamic_sql(
5378        self, expression: exp.PartitionByRangePropertyDynamic
5379    ) -> str:
5380        start = self.sql(expression, "start")
5381        end = self.sql(expression, "end")
5382
5383        every = expression.args["every"]
5384        if isinstance(every, exp.Interval) and every.this.is_string:
5385            every.this.replace(exp.Literal.number(every.name))
5386
5387        return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5388
5389    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:
5390        name = self.sql(expression, "this")
5391        values = self.expressions(expression, flat=True)
5392
5393        return f"NAME {name} VALUE {values}"
5394
5395    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:
5396        kind = self.sql(expression, "kind")
5397        sample = self.sql(expression, "sample")
5398        return f"SAMPLE {sample} {kind}"
5399
5400    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:
5401        kind = self.sql(expression, "kind")
5402        option = self.sql(expression, "option")
5403        option = f" {option}" if option else ""
5404        this = self.sql(expression, "this")
5405        this = f" {this}" if this else ""
5406        columns = self.expressions(expression)
5407        columns = f" {columns}" if columns else ""
5408        return f"{kind}{option} STATISTICS{this}{columns}"
5409
5410    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:
5411        this = self.sql(expression, "this")
5412        columns = self.expressions(expression)
5413        inner_expression = self.sql(expression, "expression")
5414        inner_expression = f" {inner_expression}" if inner_expression else ""
5415        update_options = self.sql(expression, "update_options")
5416        update_options = f" {update_options} UPDATE" if update_options else ""
5417        return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
5418
5419    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:
5420        kind = self.sql(expression, "kind")
5421        kind = f" {kind}" if kind else ""
5422        return f"DELETE{kind} STATISTICS"
5423
5424    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:
5425        inner_expression = self.sql(expression, "expression")
5426        return f"LIST CHAINED ROWS{inner_expression}"
5427
5428    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:
5429        kind = self.sql(expression, "kind")
5430        this = self.sql(expression, "this")
5431        this = f" {this}" if this else ""
5432        inner_expression = self.sql(expression, "expression")
5433        return f"VALIDATE {kind}{this}{inner_expression}"
5434
5435    def analyze_sql(self, expression: exp.Analyze) -> str:
5436        options = self.expressions(expression, key="options", sep=" ")
5437        options = f" {options}" if options else ""
5438        kind = self.sql(expression, "kind")
5439        kind = f" {kind}" if kind else ""
5440        this = self.sql(expression, "this")
5441        this = f" {this}" if this else ""
5442        mode = self.sql(expression, "mode")
5443        mode = f" {mode}" if mode else ""
5444        properties = self.sql(expression, "properties")
5445        properties = f" {properties}" if properties else ""
5446        partition = self.sql(expression, "partition")
5447        partition = f" {partition}" if partition else ""
5448        inner_expression = self.sql(expression, "expression")
5449        inner_expression = f" {inner_expression}" if inner_expression else ""
5450        return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5451
5452    def xmltable_sql(self, expression: exp.XMLTable) -> str:
5453        this = self.sql(expression, "this")
5454        namespaces = self.expressions(expression, key="namespaces")
5455        namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
5456        passing = self.expressions(expression, key="passing")
5457        passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
5458        columns = self.expressions(expression, key="columns")
5459        columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
5460        by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
5461        return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5462
5463    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
5464        this = self.sql(expression, "this")
5465        return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"
5466
5467    def export_sql(self, expression: exp.Export) -> str:
5468        this = self.sql(expression, "this")
5469        connection = self.sql(expression, "connection")
5470        connection = f"WITH CONNECTION {connection} " if connection else ""
5471        options = self.sql(expression, "options")
5472        return f"EXPORT DATA {connection}{options} AS {this}"
5473
5474    def declare_sql(self, expression: exp.Declare) -> str:
5475        return f"DECLARE {self.expressions(expression, flat=True)}"
5476
5477    def declareitem_sql(self, expression: exp.DeclareItem) -> str:
5478        variables = self.expressions(expression, "this")
5479        default = self.sql(expression, "default")
5480        default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else ""
5481
5482        kind = self.sql(expression, "kind")
5483        if isinstance(expression.args.get("kind"), exp.Schema):
5484            kind = f"TABLE {kind}"
5485
5486        kind = f" {kind}" if kind else ""
5487
5488        return f"{variables}{kind}{default}"
5489
5490    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:
5491        kind = self.sql(expression, "kind")
5492        this = self.sql(expression, "this")
5493        set = self.sql(expression, "expression")
5494        using = self.sql(expression, "using")
5495        using = f" USING {using}" if using else ""
5496
5497        kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY"
5498
5499        return f"{kind_sql} {this} SET {set}{using}"
5500
5501    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:
5502        params = self.expressions(expression, key="params", flat=True)
5503        return self.func(expression.name, *expression.expressions) + f"({params})"
5504
5505    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:
5506        return self.func(expression.name, *expression.expressions)
5507
5508    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:
5509        return self.anonymousaggfunc_sql(expression)
5510
5511    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:
5512        return self.parameterizedagg_sql(expression)
5513
5514    def show_sql(self, expression: exp.Show) -> str:
5515        self.unsupported("Unsupported SHOW statement")
5516        return ""
5517
5518    def install_sql(self, expression: exp.Install) -> str:
5519        self.unsupported("Unsupported INSTALL statement")
5520        return ""
5521
5522    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:
5523        # Snowflake GET/PUT statements:
5524        #   PUT <file> <internalStage> <properties>
5525        #   GET <internalStage> <file> <properties>
5526        props = expression.args.get("properties")
5527        props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else ""
5528        this = self.sql(expression, "this")
5529        target = self.sql(expression, "target")
5530
5531        if isinstance(expression, exp.Put):
5532            return f"PUT {this} {target}{props_sql}"
5533        else:
5534            return f"GET {target} {this}{props_sql}"
5535
5536    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:
5537        this = self.sql(expression, "this")
5538        expr = self.sql(expression, "expression")
5539        with_error = " WITH ERROR" if expression.args.get("with_error") else ""
5540        return f"TRANSLATE({this} USING {expr}{with_error})"
5541
5542    def decodecase_sql(self, expression: exp.DecodeCase) -> str:
5543        if self.SUPPORTS_DECODE_CASE:
5544            return self.func("DECODE", *expression.expressions)
5545
5546        expression, *expressions = expression.expressions
5547
5548        ifs = []
5549        for search, result in zip(expressions[::2], expressions[1::2]):
5550            if isinstance(search, exp.Literal):
5551                ifs.append(exp.If(this=expression.eq(search), true=result))
5552            elif isinstance(search, exp.Null):
5553                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))
5554            else:
5555                if isinstance(search, exp.Binary):
5556                    search = exp.paren(search)
5557
5558                cond = exp.or_(
5559                    expression.eq(search),
5560                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),
5561                    copy=False,
5562                )
5563                ifs.append(exp.If(this=cond, true=result))
5564
5565        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)
5566        return self.sql(case)
5567
5568    def semanticview_sql(self, expression: exp.SemanticView) -> str:
5569        this = self.sql(expression, "this")
5570        this = self.seg(this, sep="")
5571        dimensions = self.expressions(
5572            expression, "dimensions", dynamic=True, skip_first=True, skip_last=True
5573        )
5574        dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else ""
5575        metrics = self.expressions(
5576            expression, "metrics", dynamic=True, skip_first=True, skip_last=True
5577        )
5578        metrics = self.seg(f"METRICS {metrics}") if metrics else ""
5579        facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True)
5580        facts = self.seg(f"FACTS {facts}") if facts else ""
5581        where = self.sql(expression, "where")
5582        where = self.seg(f"WHERE {where}") if where else ""
5583        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)
5584        return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5585
5586    def getextract_sql(self, expression: exp.GetExtract) -> str:
5587        this = expression.this
5588        expr = expression.expression
5589
5590        if not this.type or not expression.type:
5591            from sqlglot.optimizer.annotate_types import annotate_types
5592
5593            this = annotate_types(this, dialect=self.dialect)
5594
5595        if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)):
5596            return self.sql(exp.Bracket(this=this, expressions=[expr]))
5597
5598        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
5599
5600    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:
5601        return self.sql(
5602            exp.DateAdd(
5603                this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE),
5604                expression=expression.this,
5605                unit=exp.var("DAY"),
5606            )
5607        )
5608
5609    def space_sql(self: Generator, expression: exp.Space) -> str:
5610        return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
5611
5612    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:
5613        return f"BUILD {self.sql(expression, 'this')}"
5614
5615    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:
5616        method = self.sql(expression, "method")
5617        kind = expression.args.get("kind")
5618        if not kind:
5619            return f"REFRESH {method}"
5620
5621        every = self.sql(expression, "every")
5622        unit = self.sql(expression, "unit")
5623        every = f" EVERY {every} {unit}" if every else ""
5624        starts = self.sql(expression, "starts")
5625        starts = f" STARTS {starts}" if starts else ""
5626
5627        return f"REFRESH {method} ON {kind}{every}{starts}"
5628
5629    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
5630        self.unsupported("The model!attribute syntax is not supported")
5631        return ""
5632
5633    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:
5634        return self.func("DIRECTORY", expression.this)
5635
5636    def uuid_sql(self, expression: exp.Uuid) -> str:
5637        is_string = expression.args.get("is_string", False)
5638        uuid_func_sql = self.func("UUID")
5639
5640        if is_string and not self.dialect.UUID_IS_STRING_TYPE:
5641            return self.sql(
5642                exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect)
5643            )
5644
5645        return uuid_func_sql
5646
5647    def initcap_sql(self, expression: exp.Initcap) -> str:
5648        delimiters = expression.expression
5649
5650        if delimiters:
5651            # do not generate delimiters arg if we are round-tripping from default delimiters
5652            if (
5653                delimiters.is_string
5654                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
5655            ):
5656                delimiters = None
5657            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:
5658                self.unsupported("INITCAP does not support custom delimiters")
5659                delimiters = None
5660
5661        return self.func("INITCAP", expression.this, delimiters)
5662
5663    def localtime_sql(self, expression: exp.Localtime) -> str:
5664        this = expression.this
5665        return self.func("LOCALTIME", this) if this else "LOCALTIME"
5666
5667    def localtimestamp_sql(self, expression: exp.Localtime) -> str:
5668        this = expression.this
5669        return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP"
5670
5671    def weekstart_sql(self, expression: exp.WeekStart) -> str:
5672        this = expression.this.name.upper()
5673        if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY":
5674            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
5675            return "WEEK"
5676
5677        return self.func("WEEK", expression.this)
5678
5679    def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str:
5680        this = self.expressions(expression)
5681        charset = self.sql(expression, "charset")
5682        using = f" USING {charset}" if charset else ""
5683        return self.func(name, this + using)
5684
5685    def block_sql(self, expression: exp.Block) -> str:
5686        expressions = self.expressions(expression, sep="; ", flat=True)
5687        return f"{expressions}" if expressions else ""
5688
5689    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
5690        self.unsupported("Unsupported Stored Procedure syntax")
5691        return ""
5692
5693    def ifblock_sql(self, expression: exp.IfBlock) -> str:
5694        self.unsupported("Unsupported If block syntax")
5695        return ""
5696
5697    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
5698        self.unsupported("Unsupported While block syntax")
5699        return ""
5700
5701    def execute_sql(self, expression: exp.Execute) -> str:
5702        self.unsupported("Unsupported Execute syntax")
5703        return ""
5704
5705    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
5706        self.unsupported("Unsupported Execute syntax")
5707        return ""

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)
771    def __init__(
772        self,
773        pretty: t.Optional[bool] = None,
774        identify: str | bool = False,
775        normalize: bool = False,
776        pad: int = 2,
777        indent: int = 2,
778        normalize_functions: t.Optional[str | bool] = None,
779        unsupported_level: ErrorLevel = ErrorLevel.WARN,
780        max_unsupported: int = 3,
781        leading_comma: bool = False,
782        max_text_width: int = 80,
783        comments: bool = True,
784        dialect: DialectType = None,
785    ):
786        import sqlglot
787        from sqlglot.dialects import Dialect
788
789        self.pretty = pretty if pretty is not None else sqlglot.pretty
790        self.identify = identify
791        self.normalize = normalize
792        self.pad = pad
793        self._indent = indent
794        self.unsupported_level = unsupported_level
795        self.max_unsupported = max_unsupported
796        self.leading_comma = leading_comma
797        self.max_text_width = max_text_width
798        self.comments = comments
799        self.dialect = Dialect.get_or_raise(dialect)
800
801        # This is both a Dialect property and a Generator argument, so we prioritize the latter
802        self.normalize_functions = (
803            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
804        )
805
806        self.unsupported_messages: t.List[str] = []
807        self._escaped_quote_end: str = (
808            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END
809        )
810        self._escaped_byte_quote_end: str = (
811            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END
812            if self.dialect.BYTE_END
813            else ""
814        )
815        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2
816
817        self._next_name = name_sequence("_t")
818
819        self._identifier_start = self.dialect.IDENTIFIER_START
820        self._identifier_end = self.dialect.IDENTIFIER_END
821
822        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.EndStatement'>: <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.TriggerExecute'>: <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
DECLARE_DEFAULT_ASSIGNMENT = '='
UPDATE_STATEMENT_SUPPORTS_FROM = True
STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = 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.tokenizer_core.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.TriggerProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
RESERVED_KEYWORDS: Set[str] = set()
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] = (<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES = {<Type.NVARCHAR: 'NVARCHAR'>, <Type.VARCHAR: 'VARCHAR'>, <Type.CHAR: 'CHAR'>, <Type.NCHAR: 'NCHAR'>}
EXPRESSIONS_WITHOUT_NESTED_CTES: Set[Type[sqlglot.expressions.Expression]] = set()
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: Tuple[Type[sqlglot.expressions.Expression], ...] = ()
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:
824    def generate(self, expression: exp.Expression, copy: bool = True) -> str:
825        """
826        Generates the SQL string corresponding to the given syntax tree.
827
828        Args:
829            expression: The syntax tree.
830            copy: Whether to copy the expression. The generator performs mutations so
831                it is safer to copy.
832
833        Returns:
834            The SQL string corresponding to `expression`.
835        """
836        if copy:
837            expression = expression.copy()
838
839        expression = self.preprocess(expression)
840
841        self.unsupported_messages = []
842        sql = self.sql(expression).strip()
843
844        if self.pretty:
845            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
846
847        if self.unsupported_level == ErrorLevel.IGNORE:
848            return sql
849
850        if self.unsupported_level == ErrorLevel.WARN:
851            for msg in self.unsupported_messages:
852                logger.warning(msg)
853        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
854            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
855
856        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:
858    def preprocess(self, expression: exp.Expression) -> exp.Expression:
859        """Apply generic preprocessing transformations to a given expression."""
860        expression = self._move_ctes_to_top_level(expression)
861
862        if self.ENSURE_BOOLS:
863            from sqlglot.transforms import ensure_bools
864
865            expression = ensure_bools(expression)
866
867        return expression

Apply generic preprocessing transformations to a given expression.

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