Edit on GitHub

sqlglot.generator

   1from __future__ import annotations
   2
   3import logging
   4import typing as t
   5
   6from sqlglot import exp
   7from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages
   8from sqlglot.helper import apply_index_offset, csv, seq_get, should_identify
   9from sqlglot.time import format_time
  10from sqlglot.tokens import TokenType
  11
  12logger = logging.getLogger("sqlglot")
  13
  14
  15class Generator:
  16    """
  17    Generator interprets the given syntax tree and produces a SQL string as an output.
  18
  19    Args:
  20        time_mapping (dict): the dictionary of custom time mappings in which the key
  21            represents a python time format and the output the target time format
  22        time_trie (trie): a trie of the time_mapping keys
  23        pretty (bool): if set to True the returned string will be formatted. Default: False.
  24        quote_start (str): specifies which starting character to use to delimit quotes. Default: '.
  25        quote_end (str): specifies which ending character to use to delimit quotes. Default: '.
  26        identifier_start (str): specifies which starting character to use to delimit identifiers. Default: ".
  27        identifier_end (str): specifies which ending character to use to delimit identifiers. Default: ".
  28        identify (bool | str): 'always': always quote, 'safe': quote identifiers if they don't contain an upcase, True defaults to always.
  29        normalize (bool): if set to True all identifiers will lower cased
  30        string_escape (str): specifies a string escape character. Default: '.
  31        identifier_escape (str): specifies an identifier escape character. Default: ".
  32        pad (int): determines padding in a formatted string. Default: 2.
  33        indent (int): determines the size of indentation in a formatted string. Default: 4.
  34        unnest_column_only (bool): if true unnest table aliases are considered only as column aliases
  35        normalize_functions (str): normalize function names, "upper", "lower", or None
  36            Default: "upper"
  37        alias_post_tablesample (bool): if the table alias comes after tablesample
  38            Default: False
  39        unsupported_level (ErrorLevel): determines the generator's behavior when it encounters
  40            unsupported expressions. Default ErrorLevel.WARN.
  41        null_ordering (str): Indicates the default null ordering method to use if not explicitly set.
  42            Options are "nulls_are_small", "nulls_are_large", "nulls_are_last".
  43            Default: "nulls_are_small"
  44        max_unsupported (int): Maximum number of unsupported messages to include in a raised UnsupportedError.
  45            This is only relevant if unsupported_level is ErrorLevel.RAISE.
  46            Default: 3
  47        leading_comma (bool): if the the comma is leading or trailing in select statements
  48            Default: False
  49        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
  50            The default is on the smaller end because the length only represents a segment and not the true
  51            line length.
  52            Default: 80
  53        comments: Whether or not to preserve comments in the output SQL code.
  54            Default: True
  55    """
  56
  57    TRANSFORMS = {
  58        exp.DateAdd: lambda self, e: self.func(
  59            "DATE_ADD", e.this, e.expression, exp.Literal.string(e.text("unit"))
  60        ),
  61        exp.TsOrDsAdd: lambda self, e: self.func(
  62            "TS_OR_DS_ADD", e.this, e.expression, exp.Literal.string(e.text("unit"))
  63        ),
  64        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
  65        exp.CharacterSetProperty: lambda self, e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}",
  66        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
  67        exp.ExternalProperty: lambda self, e: "EXTERNAL",
  68        exp.LanguageProperty: lambda self, e: self.naked_property(e),
  69        exp.LocationProperty: lambda self, e: self.naked_property(e),
  70        exp.LogProperty: lambda self, e: f"{'NO ' if e.args.get('no') else ''}LOG",
  71        exp.MaterializedProperty: lambda self, e: "MATERIALIZED",
  72        exp.NoPrimaryIndexProperty: lambda self, e: "NO PRIMARY INDEX",
  73        exp.OnCommitProperty: lambda self, e: "ON COMMIT PRESERVE ROWS",
  74        exp.ReturnsProperty: lambda self, e: self.naked_property(e),
  75        exp.SetProperty: lambda self, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
  76        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}",
  77        exp.TemporaryProperty: lambda self, e: f"{'GLOBAL ' if e.args.get('global_') else ''}TEMPORARY",
  78        exp.TransientProperty: lambda self, e: "TRANSIENT",
  79        exp.VolatilityProperty: lambda self, e: e.name,
  80        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
  81        exp.CaseSpecificColumnConstraint: lambda self, e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC",
  82        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
  83        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
  84        exp.UppercaseColumnConstraint: lambda self, e: f"UPPERCASE",
  85        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
  86        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
  87        exp.CheckColumnConstraint: lambda self, e: f"CHECK ({self.sql(e, 'this')})",
  88        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
  89        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
  90        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
  91        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
  92        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
  93    }
  94
  95    # Whether or not null ordering is supported in order by
  96    NULL_ORDERING_SUPPORTED = True
  97
  98    # Whether or not locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
  99    LOCKING_READS_SUPPORTED = False
 100
 101    # Always do union distinct or union all
 102    EXPLICIT_UNION = False
 103
 104    # Wrap derived values in parens, usually standard but spark doesn't support it
 105    WRAP_DERIVED_VALUES = True
 106
 107    # Whether or not create function uses an AS before the RETURN
 108    CREATE_FUNCTION_RETURN_AS = True
 109
 110    # Whether or not MERGE ... WHEN MATCHED BY SOURCE is allowed
 111    MATCHED_BY_SOURCE = True
 112
 113    TYPE_MAPPING = {
 114        exp.DataType.Type.NCHAR: "CHAR",
 115        exp.DataType.Type.NVARCHAR: "VARCHAR",
 116        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 117        exp.DataType.Type.LONGTEXT: "TEXT",
 118        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 119        exp.DataType.Type.LONGBLOB: "BLOB",
 120        exp.DataType.Type.INET: "INET",
 121    }
 122
 123    STAR_MAPPING = {
 124        "except": "EXCEPT",
 125        "replace": "REPLACE",
 126    }
 127
 128    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 129
 130    STRUCT_DELIMITER = ("<", ">")
 131
 132    PARAMETER_TOKEN = "@"
 133
 134    PROPERTIES_LOCATION = {
 135        exp.AfterJournalProperty: exp.Properties.Location.POST_NAME,
 136        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 137        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 138        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 139        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 140        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 141        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 142        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 143        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 144        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 145        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 146        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 147        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 148        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 149        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 150        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 151        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 152        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 153        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 154        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 155        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 156        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 157        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 158        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 159        exp.LogProperty: exp.Properties.Location.POST_NAME,
 160        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 161        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 162        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 163        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 164        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 165        exp.Property: exp.Properties.Location.POST_WITH,
 166        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 167        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 168        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 169        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 170        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 171        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 172        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 173        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 174        exp.TableFormatProperty: exp.Properties.Location.POST_WITH,
 175        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 176        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 177        exp.VolatilityProperty: exp.Properties.Location.POST_SCHEMA,
 178        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 179        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 180    }
 181
 182    WITH_SEPARATED_COMMENTS = (exp.Select, exp.From, exp.Where, exp.Binary)
 183    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 184
 185    __slots__ = (
 186        "time_mapping",
 187        "time_trie",
 188        "pretty",
 189        "quote_start",
 190        "quote_end",
 191        "identifier_start",
 192        "identifier_end",
 193        "identify",
 194        "normalize",
 195        "string_escape",
 196        "identifier_escape",
 197        "pad",
 198        "index_offset",
 199        "unnest_column_only",
 200        "alias_post_tablesample",
 201        "normalize_functions",
 202        "unsupported_level",
 203        "unsupported_messages",
 204        "null_ordering",
 205        "max_unsupported",
 206        "_indent",
 207        "_escaped_quote_end",
 208        "_escaped_identifier_end",
 209        "_leading_comma",
 210        "_max_text_width",
 211        "_comments",
 212    )
 213
 214    def __init__(
 215        self,
 216        time_mapping=None,
 217        time_trie=None,
 218        pretty=None,
 219        quote_start=None,
 220        quote_end=None,
 221        identifier_start=None,
 222        identifier_end=None,
 223        identify=False,
 224        normalize=False,
 225        string_escape=None,
 226        identifier_escape=None,
 227        pad=2,
 228        indent=2,
 229        index_offset=0,
 230        unnest_column_only=False,
 231        alias_post_tablesample=False,
 232        normalize_functions="upper",
 233        unsupported_level=ErrorLevel.WARN,
 234        null_ordering=None,
 235        max_unsupported=3,
 236        leading_comma=False,
 237        max_text_width=80,
 238        comments=True,
 239    ):
 240        import sqlglot
 241
 242        self.time_mapping = time_mapping or {}
 243        self.time_trie = time_trie
 244        self.pretty = pretty if pretty is not None else sqlglot.pretty
 245        self.quote_start = quote_start or "'"
 246        self.quote_end = quote_end or "'"
 247        self.identifier_start = identifier_start or '"'
 248        self.identifier_end = identifier_end or '"'
 249        self.identify = identify
 250        self.normalize = normalize
 251        self.string_escape = string_escape or "'"
 252        self.identifier_escape = identifier_escape or '"'
 253        self.pad = pad
 254        self.index_offset = index_offset
 255        self.unnest_column_only = unnest_column_only
 256        self.alias_post_tablesample = alias_post_tablesample
 257        self.normalize_functions = normalize_functions
 258        self.unsupported_level = unsupported_level
 259        self.unsupported_messages = []
 260        self.max_unsupported = max_unsupported
 261        self.null_ordering = null_ordering
 262        self._indent = indent
 263        self._escaped_quote_end = self.string_escape + self.quote_end
 264        self._escaped_identifier_end = self.identifier_escape + self.identifier_end
 265        self._leading_comma = leading_comma
 266        self._max_text_width = max_text_width
 267        self._comments = comments
 268
 269    def generate(self, expression: t.Optional[exp.Expression]) -> str:
 270        """
 271        Generates a SQL string by interpreting the given syntax tree.
 272
 273        Args
 274            expression: the syntax tree.
 275
 276        Returns
 277            the SQL string.
 278        """
 279        self.unsupported_messages = []
 280        sql = self.sql(expression).strip()
 281
 282        if self.unsupported_level == ErrorLevel.IGNORE:
 283            return sql
 284
 285        if self.unsupported_level == ErrorLevel.WARN:
 286            for msg in self.unsupported_messages:
 287                logger.warning(msg)
 288        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 289            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 290
 291        if self.pretty:
 292            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 293        return sql
 294
 295    def unsupported(self, message: str) -> None:
 296        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 297            raise UnsupportedError(message)
 298        self.unsupported_messages.append(message)
 299
 300    def sep(self, sep: str = " ") -> str:
 301        return f"{sep.strip()}\n" if self.pretty else sep
 302
 303    def seg(self, sql: str, sep: str = " ") -> str:
 304        return f"{self.sep(sep)}{sql}"
 305
 306    def pad_comment(self, comment: str) -> str:
 307        comment = " " + comment if comment[0].strip() else comment
 308        comment = comment + " " if comment[-1].strip() else comment
 309        return comment
 310
 311    def maybe_comment(self, sql: str, expression: exp.Expression) -> str:
 312        comments = expression.comments if self._comments else None
 313
 314        if not comments:
 315            return sql
 316
 317        sep = "\n" if self.pretty else " "
 318        comments_sql = sep.join(
 319            f"/*{self.pad_comment(comment)}*/" for comment in comments if comment
 320        )
 321
 322        if not comments_sql:
 323            return sql
 324
 325        if isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 326            return f"{comments_sql}{self.sep()}{sql}"
 327
 328        return f"{sql} {comments_sql}"
 329
 330    def wrap(self, expression: exp.Expression | str) -> str:
 331        this_sql = self.indent(
 332            self.sql(expression)
 333            if isinstance(expression, (exp.Select, exp.Union))
 334            else self.sql(expression, "this"),
 335            level=1,
 336            pad=0,
 337        )
 338        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 339
 340    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 341        original = self.identify
 342        self.identify = False
 343        result = func(*args, **kwargs)
 344        self.identify = original
 345        return result
 346
 347    def normalize_func(self, name: str) -> str:
 348        if self.normalize_functions == "upper":
 349            return name.upper()
 350        if self.normalize_functions == "lower":
 351            return name.lower()
 352        return name
 353
 354    def indent(
 355        self,
 356        sql: str,
 357        level: int = 0,
 358        pad: t.Optional[int] = None,
 359        skip_first: bool = False,
 360        skip_last: bool = False,
 361    ) -> str:
 362        if not self.pretty:
 363            return sql
 364
 365        pad = self.pad if pad is None else pad
 366        lines = sql.split("\n")
 367
 368        return "\n".join(
 369            line
 370            if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 371            else f"{' ' * (level * self._indent + pad)}{line}"
 372            for i, line in enumerate(lines)
 373        )
 374
 375    def sql(
 376        self,
 377        expression: t.Optional[str | exp.Expression],
 378        key: t.Optional[str] = None,
 379        comment: bool = True,
 380    ) -> str:
 381        if not expression:
 382            return ""
 383
 384        if isinstance(expression, str):
 385            return expression
 386
 387        if key:
 388            return self.sql(expression.args.get(key))
 389
 390        transform = self.TRANSFORMS.get(expression.__class__)
 391
 392        if callable(transform):
 393            sql = transform(self, expression)
 394        elif transform:
 395            sql = transform
 396        elif isinstance(expression, exp.Expression):
 397            exp_handler_name = f"{expression.key}_sql"
 398
 399            if hasattr(self, exp_handler_name):
 400                sql = getattr(self, exp_handler_name)(expression)
 401            elif isinstance(expression, exp.Func):
 402                sql = self.function_fallback_sql(expression)
 403            elif isinstance(expression, exp.Property):
 404                sql = self.property_sql(expression)
 405            else:
 406                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
 407        else:
 408            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
 409
 410        return self.maybe_comment(sql, expression) if self._comments and comment else sql
 411
 412    def uncache_sql(self, expression: exp.Uncache) -> str:
 413        table = self.sql(expression, "this")
 414        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
 415        return f"UNCACHE TABLE{exists_sql} {table}"
 416
 417    def cache_sql(self, expression: exp.Cache) -> str:
 418        lazy = " LAZY" if expression.args.get("lazy") else ""
 419        table = self.sql(expression, "this")
 420        options = expression.args.get("options")
 421        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
 422        sql = self.sql(expression, "expression")
 423        sql = f" AS{self.sep()}{sql}" if sql else ""
 424        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
 425        return self.prepend_ctes(expression, sql)
 426
 427    def characterset_sql(self, expression: exp.CharacterSet) -> str:
 428        if isinstance(expression.parent, exp.Cast):
 429            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
 430        default = "DEFAULT " if expression.args.get("default") else ""
 431        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
 432
 433    def column_sql(self, expression: exp.Column) -> str:
 434        return ".".join(
 435            self.sql(part)
 436            for part in (
 437                expression.args.get("catalog"),
 438                expression.args.get("db"),
 439                expression.args.get("table"),
 440                expression.args.get("this"),
 441            )
 442            if part
 443        )
 444
 445    def columndef_sql(self, expression: exp.ColumnDef) -> str:
 446        column = self.sql(expression, "this")
 447        kind = self.sql(expression, "kind")
 448        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
 449        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
 450        kind = f" {kind}" if kind else ""
 451        constraints = f" {constraints}" if constraints else ""
 452
 453        return f"{exists}{column}{kind}{constraints}"
 454
 455    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
 456        this = self.sql(expression, "this")
 457        kind_sql = self.sql(expression, "kind")
 458        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
 459
 460    def autoincrementcolumnconstraint_sql(self, _) -> str:
 461        return self.token_sql(TokenType.AUTO_INCREMENT)
 462
 463    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
 464        if isinstance(expression.this, list):
 465            this = self.wrap(self.expressions(expression, key="this", flat=True))
 466        else:
 467            this = self.sql(expression, "this")
 468
 469        return f"COMPRESS {this}"
 470
 471    def generatedasidentitycolumnconstraint_sql(
 472        self, expression: exp.GeneratedAsIdentityColumnConstraint
 473    ) -> str:
 474        this = ""
 475        if expression.this is not None:
 476            this = " ALWAYS " if expression.this else " BY DEFAULT "
 477        start = expression.args.get("start")
 478        start = f"START WITH {start}" if start else ""
 479        increment = expression.args.get("increment")
 480        increment = f" INCREMENT BY {increment}" if increment else ""
 481        minvalue = expression.args.get("minvalue")
 482        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
 483        maxvalue = expression.args.get("maxvalue")
 484        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
 485        cycle = expression.args.get("cycle")
 486        cycle_sql = ""
 487        if cycle is not None:
 488            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
 489            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
 490        sequence_opts = ""
 491        if start or increment or cycle_sql:
 492            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
 493            sequence_opts = f" ({sequence_opts.strip()})"
 494        return f"GENERATED{this}AS IDENTITY{sequence_opts}"
 495
 496    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
 497        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
 498
 499    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
 500        desc = expression.args.get("desc")
 501        if desc is not None:
 502            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
 503        return f"PRIMARY KEY"
 504
 505    def uniquecolumnconstraint_sql(self, _) -> str:
 506        return "UNIQUE"
 507
 508    def create_sql(self, expression: exp.Create) -> str:
 509        kind = self.sql(expression, "kind").upper()
 510        properties = expression.args.get("properties")
 511        properties_exp = expression.copy()
 512        properties_locs = self.locate_properties(properties) if properties else {}
 513        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
 514            exp.Properties.Location.POST_WITH
 515        ):
 516            properties_exp.set(
 517                "properties",
 518                exp.Properties(
 519                    expressions=[
 520                        *properties_locs[exp.Properties.Location.POST_SCHEMA],
 521                        *properties_locs[exp.Properties.Location.POST_WITH],
 522                    ]
 523                ),
 524            )
 525        if kind == "TABLE" and properties_locs.get(exp.Properties.Location.POST_NAME):
 526            this_name = self.sql(expression.this, "this")
 527            this_properties = self.properties(
 528                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_NAME]),
 529                wrapped=False,
 530            )
 531            this_schema = f"({self.expressions(expression.this)})"
 532            this = f"{this_name}, {this_properties} {this_schema}"
 533            properties_sql = ""
 534        else:
 535            this = self.sql(expression, "this")
 536            properties_sql = self.sql(properties_exp, "properties")
 537        begin = " BEGIN" if expression.args.get("begin") else ""
 538        expression_sql = self.sql(expression, "expression")
 539        if expression_sql:
 540            expression_sql = f"{begin}{self.sep()}{expression_sql}"
 541
 542            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
 543                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
 544                    postalias_props_sql = self.properties(
 545                        exp.Properties(
 546                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
 547                        ),
 548                        wrapped=False,
 549                    )
 550                    expression_sql = f" AS {postalias_props_sql}{expression_sql}"
 551                else:
 552                    expression_sql = f" AS{expression_sql}"
 553
 554        postindex_props_sql = ""
 555        if properties_locs.get(exp.Properties.Location.POST_INDEX):
 556            postindex_props_sql = self.properties(
 557                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
 558                wrapped=False,
 559                prefix=" ",
 560            )
 561
 562        indexes = expression.args.get("indexes")
 563        if indexes:
 564            indexes_sql: t.List[str] = []
 565            for index in indexes:
 566                ind_unique = " UNIQUE" if index.args.get("unique") else ""
 567                ind_primary = " PRIMARY" if index.args.get("primary") else ""
 568                ind_amp = " AMP" if index.args.get("amp") else ""
 569                ind_name = f" {index.name}" if index.name else ""
 570                ind_columns = (
 571                    f' ({self.expressions(index, key="columns", flat=True)})'
 572                    if index.args.get("columns")
 573                    else ""
 574                )
 575                ind_sql = f"{ind_unique}{ind_primary}{ind_amp} INDEX{ind_name}{ind_columns}"
 576
 577                if indexes_sql:
 578                    indexes_sql.append(ind_sql)
 579                else:
 580                    indexes_sql.append(
 581                        f"{ind_sql}{postindex_props_sql}"
 582                        if index.args.get("primary")
 583                        else f"{postindex_props_sql}{ind_sql}"
 584                    )
 585
 586            index_sql = "".join(indexes_sql)
 587        else:
 588            index_sql = postindex_props_sql
 589
 590        replace = " OR REPLACE" if expression.args.get("replace") else ""
 591        unique = " UNIQUE" if expression.args.get("unique") else ""
 592        volatile = " VOLATILE" if expression.args.get("volatile") else ""
 593
 594        postcreate_props_sql = ""
 595        if properties_locs.get(exp.Properties.Location.POST_CREATE):
 596            postcreate_props_sql = self.properties(
 597                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
 598                sep=" ",
 599                prefix=" ",
 600                wrapped=False,
 601            )
 602
 603        modifiers = "".join((replace, unique, volatile, postcreate_props_sql))
 604
 605        postexpression_props_sql = ""
 606        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
 607            postexpression_props_sql = self.properties(
 608                exp.Properties(
 609                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
 610                ),
 611                sep=" ",
 612                prefix=" ",
 613                wrapped=False,
 614            )
 615
 616        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
 617        no_schema_binding = (
 618            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
 619        )
 620
 621        expression_sql = f"CREATE{modifiers} {kind}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}"
 622        return self.prepend_ctes(expression, expression_sql)
 623
 624    def describe_sql(self, expression: exp.Describe) -> str:
 625        return f"DESCRIBE {self.sql(expression, 'this')}"
 626
 627    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
 628        with_ = self.sql(expression, "with")
 629        if with_:
 630            sql = f"{with_}{self.sep()}{sql}"
 631        return sql
 632
 633    def with_sql(self, expression: exp.With) -> str:
 634        sql = self.expressions(expression, flat=True)
 635        recursive = "RECURSIVE " if expression.args.get("recursive") else ""
 636
 637        return f"WITH {recursive}{sql}"
 638
 639    def cte_sql(self, expression: exp.CTE) -> str:
 640        alias = self.sql(expression, "alias")
 641        return f"{alias} AS {self.wrap(expression)}"
 642
 643    def tablealias_sql(self, expression: exp.TableAlias) -> str:
 644        alias = self.sql(expression, "this")
 645        columns = self.expressions(expression, key="columns", flat=True)
 646        columns = f"({columns})" if columns else ""
 647        return f"{alias}{columns}"
 648
 649    def bitstring_sql(self, expression: exp.BitString) -> str:
 650        return self.sql(expression, "this")
 651
 652    def hexstring_sql(self, expression: exp.HexString) -> str:
 653        return self.sql(expression, "this")
 654
 655    def datatype_sql(self, expression: exp.DataType) -> str:
 656        type_value = expression.this
 657        type_sql = self.TYPE_MAPPING.get(type_value, type_value.value)
 658        nested = ""
 659        interior = self.expressions(expression, flat=True)
 660        values = ""
 661        if interior:
 662            if expression.args.get("nested"):
 663                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
 664                if expression.args.get("values") is not None:
 665                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
 666                    values = (
 667                        f"{delimiters[0]}{self.expressions(expression, 'values')}{delimiters[1]}"
 668                    )
 669            else:
 670                nested = f"({interior})"
 671
 672        return f"{type_sql}{nested}{values}"
 673
 674    def directory_sql(self, expression: exp.Directory) -> str:
 675        local = "LOCAL " if expression.args.get("local") else ""
 676        row_format = self.sql(expression, "row_format")
 677        row_format = f" {row_format}" if row_format else ""
 678        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
 679
 680    def delete_sql(self, expression: exp.Delete) -> str:
 681        this = self.sql(expression, "this")
 682        this = f" FROM {this}" if this else ""
 683        using_sql = (
 684            f" USING {self.expressions(expression, 'using', sep=', USING ')}"
 685            if expression.args.get("using")
 686            else ""
 687        )
 688        where_sql = self.sql(expression, "where")
 689        returning = self.sql(expression, "returning")
 690        sql = f"DELETE{this}{using_sql}{where_sql}{returning}"
 691        return self.prepend_ctes(expression, sql)
 692
 693    def drop_sql(self, expression: exp.Drop) -> str:
 694        this = self.sql(expression, "this")
 695        kind = expression.args["kind"]
 696        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
 697        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
 698        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
 699        cascade = " CASCADE" if expression.args.get("cascade") else ""
 700        return f"DROP{temporary}{materialized} {kind}{exists_sql}{this}{cascade}"
 701
 702    def except_sql(self, expression: exp.Except) -> str:
 703        return self.prepend_ctes(
 704            expression,
 705            self.set_operation(expression, self.except_op(expression)),
 706        )
 707
 708    def except_op(self, expression: exp.Except) -> str:
 709        return f"EXCEPT{'' if expression.args.get('distinct') else ' ALL'}"
 710
 711    def fetch_sql(self, expression: exp.Fetch) -> str:
 712        direction = expression.args.get("direction")
 713        direction = f" {direction.upper()}" if direction else ""
 714        count = expression.args.get("count")
 715        count = f" {count}" if count else ""
 716        return f"{self.seg('FETCH')}{direction}{count} ROWS ONLY"
 717
 718    def filter_sql(self, expression: exp.Filter) -> str:
 719        this = self.sql(expression, "this")
 720        where = self.sql(expression, "expression")[1:]  # where has a leading space
 721        return f"{this} FILTER({where})"
 722
 723    def hint_sql(self, expression: exp.Hint) -> str:
 724        if self.sql(expression, "this"):
 725            self.unsupported("Hints are not supported")
 726        return ""
 727
 728    def index_sql(self, expression: exp.Index) -> str:
 729        this = self.sql(expression, "this")
 730        table = self.sql(expression, "table")
 731        columns = self.sql(expression, "columns")
 732        return f"{this} ON {table} {columns}"
 733
 734    def identifier_sql(self, expression: exp.Identifier) -> str:
 735        text = expression.name
 736        text = text.lower() if self.normalize else text
 737        text = text.replace(self.identifier_end, self._escaped_identifier_end)
 738        if expression.args.get("quoted") or should_identify(text, self.identify):
 739            text = f"{self.identifier_start}{text}{self.identifier_end}"
 740        return text
 741
 742    def national_sql(self, expression: exp.National) -> str:
 743        return f"N{self.sql(expression, 'this')}"
 744
 745    def partition_sql(self, expression: exp.Partition) -> str:
 746        return f"PARTITION({self.expressions(expression)})"
 747
 748    def properties_sql(self, expression: exp.Properties) -> str:
 749        root_properties = []
 750        with_properties = []
 751
 752        for p in expression.expressions:
 753            p_loc = self.PROPERTIES_LOCATION[p.__class__]
 754            if p_loc == exp.Properties.Location.POST_WITH:
 755                with_properties.append(p)
 756            elif p_loc == exp.Properties.Location.POST_SCHEMA:
 757                root_properties.append(p)
 758
 759        return self.root_properties(
 760            exp.Properties(expressions=root_properties)
 761        ) + self.with_properties(exp.Properties(expressions=with_properties))
 762
 763    def root_properties(self, properties: exp.Properties) -> str:
 764        if properties.expressions:
 765            return self.sep() + self.expressions(properties, indent=False, sep=" ")
 766        return ""
 767
 768    def properties(
 769        self,
 770        properties: exp.Properties,
 771        prefix: str = "",
 772        sep: str = ", ",
 773        suffix: str = "",
 774        wrapped: bool = True,
 775    ) -> str:
 776        if properties.expressions:
 777            expressions = self.expressions(properties, sep=sep, indent=False)
 778            expressions = self.wrap(expressions) if wrapped else expressions
 779            return f"{prefix}{' ' if prefix and prefix != ' ' else ''}{expressions}{suffix}"
 780        return ""
 781
 782    def with_properties(self, properties: exp.Properties) -> str:
 783        return self.properties(properties, prefix=self.seg("WITH"))
 784
 785    def locate_properties(
 786        self, properties: exp.Properties
 787    ) -> t.Dict[exp.Properties.Location, list[exp.Property]]:
 788        properties_locs: t.Dict[exp.Properties.Location, list[exp.Property]] = {
 789            key: [] for key in exp.Properties.Location
 790        }
 791
 792        for p in properties.expressions:
 793            p_loc = self.PROPERTIES_LOCATION[p.__class__]
 794            if p_loc == exp.Properties.Location.POST_NAME:
 795                properties_locs[exp.Properties.Location.POST_NAME].append(p)
 796            elif p_loc == exp.Properties.Location.POST_INDEX:
 797                properties_locs[exp.Properties.Location.POST_INDEX].append(p)
 798            elif p_loc == exp.Properties.Location.POST_SCHEMA:
 799                properties_locs[exp.Properties.Location.POST_SCHEMA].append(p)
 800            elif p_loc == exp.Properties.Location.POST_WITH:
 801                properties_locs[exp.Properties.Location.POST_WITH].append(p)
 802            elif p_loc == exp.Properties.Location.POST_CREATE:
 803                properties_locs[exp.Properties.Location.POST_CREATE].append(p)
 804            elif p_loc == exp.Properties.Location.POST_ALIAS:
 805                properties_locs[exp.Properties.Location.POST_ALIAS].append(p)
 806            elif p_loc == exp.Properties.Location.POST_EXPRESSION:
 807                properties_locs[exp.Properties.Location.POST_EXPRESSION].append(p)
 808            elif p_loc == exp.Properties.Location.UNSUPPORTED:
 809                self.unsupported(f"Unsupported property {p.key}")
 810
 811        return properties_locs
 812
 813    def property_sql(self, expression: exp.Property) -> str:
 814        property_cls = expression.__class__
 815        if property_cls == exp.Property:
 816            return f"{expression.name}={self.sql(expression, 'value')}"
 817
 818        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
 819        if not property_name:
 820            self.unsupported(f"Unsupported property {expression.key}")
 821
 822        return f"{property_name}={self.sql(expression, 'this')}"
 823
 824    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
 825        options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
 826        options = f" {options}" if options else ""
 827        return f"LIKE {self.sql(expression, 'this')}{options}"
 828
 829    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
 830        no = "NO " if expression.args.get("no") else ""
 831        protection = " PROTECTION" if expression.args.get("protection") else ""
 832        return f"{no}FALLBACK{protection}"
 833
 834    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
 835        no = "NO " if expression.args.get("no") else ""
 836        dual = "DUAL " if expression.args.get("dual") else ""
 837        before = "BEFORE " if expression.args.get("before") else ""
 838        return f"{no}{dual}{before}JOURNAL"
 839
 840    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
 841        freespace = self.sql(expression, "this")
 842        percent = " PERCENT" if expression.args.get("percent") else ""
 843        return f"FREESPACE={freespace}{percent}"
 844
 845    def afterjournalproperty_sql(self, expression: exp.AfterJournalProperty) -> str:
 846        no = "NO " if expression.args.get("no") else ""
 847        dual = "DUAL " if expression.args.get("dual") else ""
 848        local = ""
 849        if expression.args.get("local") is not None:
 850            local = "LOCAL " if expression.args.get("local") else "NOT LOCAL "
 851        return f"{no}{dual}{local}AFTER JOURNAL"
 852
 853    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
 854        if expression.args.get("default"):
 855            property = "DEFAULT"
 856        elif expression.args.get("on"):
 857            property = "ON"
 858        else:
 859            property = "OFF"
 860        return f"CHECKSUM={property}"
 861
 862    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
 863        if expression.args.get("no"):
 864            return "NO MERGEBLOCKRATIO"
 865        if expression.args.get("default"):
 866            return "DEFAULT MERGEBLOCKRATIO"
 867
 868        percent = " PERCENT" if expression.args.get("percent") else ""
 869        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
 870
 871    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
 872        default = expression.args.get("default")
 873        min = expression.args.get("min")
 874        if default is not None or min is not None:
 875            if default:
 876                property = "DEFAULT"
 877            elif min:
 878                property = "MINIMUM"
 879            else:
 880                property = "MAXIMUM"
 881            return f"{property} DATABLOCKSIZE"
 882        else:
 883            units = expression.args.get("units")
 884            units = f" {units}" if units else ""
 885            return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
 886
 887    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
 888        autotemp = expression.args.get("autotemp")
 889        always = expression.args.get("always")
 890        default = expression.args.get("default")
 891        manual = expression.args.get("manual")
 892        never = expression.args.get("never")
 893
 894        if autotemp is not None:
 895            property = f"AUTOTEMP({self.expressions(autotemp)})"
 896        elif always:
 897            property = "ALWAYS"
 898        elif default:
 899            property = "DEFAULT"
 900        elif manual:
 901            property = "MANUAL"
 902        elif never:
 903            property = "NEVER"
 904        return f"BLOCKCOMPRESSION={property}"
 905
 906    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
 907        no = expression.args.get("no")
 908        no = " NO" if no else ""
 909        concurrent = expression.args.get("concurrent")
 910        concurrent = " CONCURRENT" if concurrent else ""
 911
 912        for_ = ""
 913        if expression.args.get("for_all"):
 914            for_ = " FOR ALL"
 915        elif expression.args.get("for_insert"):
 916            for_ = " FOR INSERT"
 917        elif expression.args.get("for_none"):
 918            for_ = " FOR NONE"
 919        return f"WITH{no}{concurrent} ISOLATED LOADING{for_}"
 920
 921    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
 922        kind = expression.args.get("kind")
 923        this: str = f" {this}" if expression.this else ""
 924        for_or_in = expression.args.get("for_or_in")
 925        lock_type = expression.args.get("lock_type")
 926        override = " OVERRIDE" if expression.args.get("override") else ""
 927        return f"LOCKING {kind}{this} {for_or_in} {lock_type}{override}"
 928
 929    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
 930        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
 931        statistics = expression.args.get("statistics")
 932        statistics_sql = ""
 933        if statistics is not None:
 934            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
 935        return f"{data_sql}{statistics_sql}"
 936
 937    def insert_sql(self, expression: exp.Insert) -> str:
 938        overwrite = expression.args.get("overwrite")
 939
 940        if isinstance(expression.this, exp.Directory):
 941            this = "OVERWRITE " if overwrite else "INTO "
 942        else:
 943            this = "OVERWRITE TABLE " if overwrite else "INTO "
 944
 945        alternative = expression.args.get("alternative")
 946        alternative = f" OR {alternative} " if alternative else " "
 947        this = f"{this}{self.sql(expression, 'this')}"
 948
 949        exists = " IF EXISTS " if expression.args.get("exists") else " "
 950        partition_sql = (
 951            self.sql(expression, "partition") if expression.args.get("partition") else ""
 952        )
 953        expression_sql = self.sql(expression, "expression")
 954        returning = self.sql(expression, "returning")
 955        sep = self.sep() if partition_sql else ""
 956        sql = f"INSERT{alternative}{this}{exists}{partition_sql}{sep}{expression_sql}{returning}"
 957        return self.prepend_ctes(expression, sql)
 958
 959    def intersect_sql(self, expression: exp.Intersect) -> str:
 960        return self.prepend_ctes(
 961            expression,
 962            self.set_operation(expression, self.intersect_op(expression)),
 963        )
 964
 965    def intersect_op(self, expression: exp.Intersect) -> str:
 966        return f"INTERSECT{'' if expression.args.get('distinct') else ' ALL'}"
 967
 968    def introducer_sql(self, expression: exp.Introducer) -> str:
 969        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
 970
 971    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
 972        return expression.name.upper()
 973
 974    def returning_sql(self, expression: exp.Returning) -> str:
 975        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
 976
 977    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
 978        fields = expression.args.get("fields")
 979        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
 980        escaped = expression.args.get("escaped")
 981        escaped = f" ESCAPED BY {escaped}" if escaped else ""
 982        items = expression.args.get("collection_items")
 983        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
 984        keys = expression.args.get("map_keys")
 985        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
 986        lines = expression.args.get("lines")
 987        lines = f" LINES TERMINATED BY {lines}" if lines else ""
 988        null = expression.args.get("null")
 989        null = f" NULL DEFINED AS {null}" if null else ""
 990        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
 991
 992    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
 993        table = ".".join(
 994            part
 995            for part in [
 996                self.sql(expression, "catalog"),
 997                self.sql(expression, "db"),
 998                self.sql(expression, "this"),
 999            ]
1000            if part
1001        )
1002
1003        alias = self.sql(expression, "alias")
1004        alias = f"{sep}{alias}" if alias else ""
1005        hints = self.expressions(expression, key="hints", sep=", ", flat=True)
1006        hints = f" WITH ({hints})" if hints else ""
1007        laterals = self.expressions(expression, key="laterals", sep="")
1008        joins = self.expressions(expression, key="joins", sep="")
1009        pivots = self.expressions(expression, key="pivots", sep="")
1010        system_time = expression.args.get("system_time")
1011        system_time = f" {self.sql(expression, 'system_time')}" if system_time else ""
1012
1013        return f"{table}{system_time}{alias}{hints}{laterals}{joins}{pivots}"
1014
1015    def tablesample_sql(self, expression: exp.TableSample, seed_prefix: str = "SEED") -> str:
1016        if self.alias_post_tablesample and expression.this.alias:
1017            this = self.sql(expression.this, "this")
1018            alias = f" AS {self.sql(expression.this, 'alias')}"
1019        else:
1020            this = self.sql(expression, "this")
1021            alias = ""
1022        method = self.sql(expression, "method")
1023        method = f"{method.upper()} " if method else ""
1024        numerator = self.sql(expression, "bucket_numerator")
1025        denominator = self.sql(expression, "bucket_denominator")
1026        field = self.sql(expression, "bucket_field")
1027        field = f" ON {field}" if field else ""
1028        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
1029        percent = self.sql(expression, "percent")
1030        percent = f"{percent} PERCENT" if percent else ""
1031        rows = self.sql(expression, "rows")
1032        rows = f"{rows} ROWS" if rows else ""
1033        size = self.sql(expression, "size")
1034        seed = self.sql(expression, "seed")
1035        seed = f" {seed_prefix} ({seed})" if seed else ""
1036        kind = expression.args.get("kind", "TABLESAMPLE")
1037        return f"{this} {kind} {method}({bucket}{percent}{rows}{size}){seed}{alias}"
1038
1039    def pivot_sql(self, expression: exp.Pivot) -> str:
1040        this = self.sql(expression, "this")
1041        alias = self.sql(expression, "alias")
1042        alias = f" AS {alias}" if alias else ""
1043        unpivot = expression.args.get("unpivot")
1044        direction = "UNPIVOT" if unpivot else "PIVOT"
1045        expressions = self.expressions(expression, key="expressions")
1046        field = self.sql(expression, "field")
1047        return f"{this} {direction}({expressions} FOR {field}){alias}"
1048
1049    def tuple_sql(self, expression: exp.Tuple) -> str:
1050        return f"({self.expressions(expression, flat=True)})"
1051
1052    def update_sql(self, expression: exp.Update) -> str:
1053        this = self.sql(expression, "this")
1054        set_sql = self.expressions(expression, flat=True)
1055        from_sql = self.sql(expression, "from")
1056        where_sql = self.sql(expression, "where")
1057        returning = self.sql(expression, "returning")
1058        sql = f"UPDATE {this} SET {set_sql}{from_sql}{where_sql}{returning}"
1059        return self.prepend_ctes(expression, sql)
1060
1061    def values_sql(self, expression: exp.Values) -> str:
1062        args = self.expressions(expression)
1063        alias = self.sql(expression, "alias")
1064        values = f"VALUES{self.seg('')}{args}"
1065        values = (
1066            f"({values})"
1067            if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From))
1068            else values
1069        )
1070        return f"{values} AS {alias}" if alias else values
1071
1072    def var_sql(self, expression: exp.Var) -> str:
1073        return self.sql(expression, "this")
1074
1075    def into_sql(self, expression: exp.Into) -> str:
1076        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1077        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
1078        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
1079
1080    def from_sql(self, expression: exp.From) -> str:
1081        expressions = self.expressions(expression, flat=True)
1082        return f"{self.seg('FROM')} {expressions}"
1083
1084    def group_sql(self, expression: exp.Group) -> str:
1085        group_by = self.op_expressions("GROUP BY", expression)
1086        grouping_sets = self.expressions(expression, key="grouping_sets", indent=False)
1087        grouping_sets = (
1088            f"{self.seg('GROUPING SETS')} {self.wrap(grouping_sets)}" if grouping_sets else ""
1089        )
1090
1091        cube = expression.args.get("cube", [])
1092        if seq_get(cube, 0) is True:
1093            return f"{group_by}{self.seg('WITH CUBE')}"
1094        else:
1095            cube_sql = self.expressions(expression, key="cube", indent=False)
1096            cube_sql = f"{self.seg('CUBE')} {self.wrap(cube_sql)}" if cube_sql else ""
1097
1098        rollup = expression.args.get("rollup", [])
1099        if seq_get(rollup, 0) is True:
1100            return f"{group_by}{self.seg('WITH ROLLUP')}"
1101        else:
1102            rollup_sql = self.expressions(expression, key="rollup", indent=False)
1103            rollup_sql = f"{self.seg('ROLLUP')} {self.wrap(rollup_sql)}" if rollup_sql else ""
1104
1105        groupings = csv(grouping_sets, cube_sql, rollup_sql, sep=",")
1106
1107        if expression.args.get("expressions") and groupings:
1108            group_by = f"{group_by},"
1109
1110        return f"{group_by}{groupings}"
1111
1112    def having_sql(self, expression: exp.Having) -> str:
1113        this = self.indent(self.sql(expression, "this"))
1114        return f"{self.seg('HAVING')}{self.sep()}{this}"
1115
1116    def join_sql(self, expression: exp.Join) -> str:
1117        op_sql = self.seg(
1118            " ".join(
1119                op
1120                for op in (
1121                    "NATURAL" if expression.args.get("natural") else None,
1122                    expression.side,
1123                    expression.kind,
1124                    "JOIN",
1125                )
1126                if op
1127            )
1128        )
1129        on_sql = self.sql(expression, "on")
1130        using = expression.args.get("using")
1131
1132        if not on_sql and using:
1133            on_sql = csv(*(self.sql(column) for column in using))
1134
1135        if on_sql:
1136            on_sql = self.indent(on_sql, skip_first=True)
1137            space = self.seg(" " * self.pad) if self.pretty else " "
1138            if using:
1139                on_sql = f"{space}USING ({on_sql})"
1140            else:
1141                on_sql = f"{space}ON {on_sql}"
1142
1143        expression_sql = self.sql(expression, "expression")
1144        this_sql = self.sql(expression, "this")
1145        return f"{expression_sql}{op_sql} {this_sql}{on_sql}"
1146
1147    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str:
1148        args = self.expressions(expression, flat=True)
1149        args = f"({args})" if len(args.split(",")) > 1 else args
1150        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
1151
1152    def lateral_sql(self, expression: exp.Lateral) -> str:
1153        this = self.sql(expression, "this")
1154
1155        if isinstance(expression.this, exp.Subquery):
1156            return f"LATERAL {this}"
1157
1158        if expression.args.get("view"):
1159            alias = expression.args["alias"]
1160            columns = self.expressions(alias, key="columns", flat=True)
1161            table = f" {alias.name}" if alias.name else ""
1162            columns = f" AS {columns}" if columns else ""
1163            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
1164            return f"{op_sql}{self.sep()}{this}{table}{columns}"
1165
1166        alias = self.sql(expression, "alias")
1167        alias = f" AS {alias}" if alias else ""
1168        return f"LATERAL {this}{alias}"
1169
1170    def limit_sql(self, expression: exp.Limit) -> str:
1171        this = self.sql(expression, "this")
1172        return f"{this}{self.seg('LIMIT')} {self.sql(expression, 'expression')}"
1173
1174    def offset_sql(self, expression: exp.Offset) -> str:
1175        this = self.sql(expression, "this")
1176        return f"{this}{self.seg('OFFSET')} {self.sql(expression, 'expression')}"
1177
1178    def setitem_sql(self, expression: exp.SetItem) -> str:
1179        kind = self.sql(expression, "kind")
1180        kind = f"{kind} " if kind else ""
1181        this = self.sql(expression, "this")
1182        expressions = self.expressions(expression)
1183        collate = self.sql(expression, "collate")
1184        collate = f" COLLATE {collate}" if collate else ""
1185        global_ = "GLOBAL " if expression.args.get("global") else ""
1186        return f"{global_}{kind}{this}{expressions}{collate}"
1187
1188    def set_sql(self, expression: exp.Set) -> str:
1189        expressions = (
1190            f" {self.expressions(expression, flat=True)}" if expression.expressions else ""
1191        )
1192        return f"SET{expressions}"
1193
1194    def lock_sql(self, expression: exp.Lock) -> str:
1195        if self.LOCKING_READS_SUPPORTED:
1196            lock_type = "UPDATE" if expression.args["update"] else "SHARE"
1197            return self.seg(f"FOR {lock_type}")
1198
1199        self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
1200        return ""
1201
1202    def literal_sql(self, expression: exp.Literal) -> str:
1203        text = expression.this or ""
1204        if expression.is_string:
1205            text = text.replace(self.quote_end, self._escaped_quote_end)
1206            if self.pretty:
1207                text = text.replace("\n", self.SENTINEL_LINE_BREAK)
1208            text = f"{self.quote_start}{text}{self.quote_end}"
1209        return text
1210
1211    def loaddata_sql(self, expression: exp.LoadData) -> str:
1212        local = " LOCAL" if expression.args.get("local") else ""
1213        inpath = f" INPATH {self.sql(expression, 'inpath')}"
1214        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
1215        this = f" INTO TABLE {self.sql(expression, 'this')}"
1216        partition = self.sql(expression, "partition")
1217        partition = f" {partition}" if partition else ""
1218        input_format = self.sql(expression, "input_format")
1219        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
1220        serde = self.sql(expression, "serde")
1221        serde = f" SERDE {serde}" if serde else ""
1222        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
1223
1224    def null_sql(self, *_) -> str:
1225        return "NULL"
1226
1227    def boolean_sql(self, expression: exp.Boolean) -> str:
1228        return "TRUE" if expression.this else "FALSE"
1229
1230    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
1231        this = self.sql(expression, "this")
1232        this = f"{this} " if this else this
1233        return self.op_expressions(f"{this}ORDER BY", expression, flat=this or flat)  # type: ignore
1234
1235    def cluster_sql(self, expression: exp.Cluster) -> str:
1236        return self.op_expressions("CLUSTER BY", expression)
1237
1238    def distribute_sql(self, expression: exp.Distribute) -> str:
1239        return self.op_expressions("DISTRIBUTE BY", expression)
1240
1241    def sort_sql(self, expression: exp.Sort) -> str:
1242        return self.op_expressions("SORT BY", expression)
1243
1244    def ordered_sql(self, expression: exp.Ordered) -> str:
1245        desc = expression.args.get("desc")
1246        asc = not desc
1247
1248        nulls_first = expression.args.get("nulls_first")
1249        nulls_last = not nulls_first
1250        nulls_are_large = self.null_ordering == "nulls_are_large"
1251        nulls_are_small = self.null_ordering == "nulls_are_small"
1252        nulls_are_last = self.null_ordering == "nulls_are_last"
1253
1254        sort_order = " DESC" if desc else ""
1255        nulls_sort_change = ""
1256        if nulls_first and (
1257            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
1258        ):
1259            nulls_sort_change = " NULLS FIRST"
1260        elif (
1261            nulls_last
1262            and ((asc and nulls_are_small) or (desc and nulls_are_large))
1263            and not nulls_are_last
1264        ):
1265            nulls_sort_change = " NULLS LAST"
1266
1267        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
1268            self.unsupported(
1269                "Sorting in an ORDER BY on NULLS FIRST/NULLS LAST is not supported by this dialect"
1270            )
1271            nulls_sort_change = ""
1272
1273        return f"{self.sql(expression, 'this')}{sort_order}{nulls_sort_change}"
1274
1275    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
1276        partition = self.partition_by_sql(expression)
1277        order = self.sql(expression, "order")
1278        measures = self.sql(expression, "measures")
1279        measures = self.seg(f"MEASURES {measures}") if measures else ""
1280        rows = self.sql(expression, "rows")
1281        rows = self.seg(rows) if rows else ""
1282        after = self.sql(expression, "after")
1283        after = self.seg(after) if after else ""
1284        pattern = self.sql(expression, "pattern")
1285        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
1286        define = self.sql(expression, "define")
1287        define = self.seg(f"DEFINE {define}") if define else ""
1288        body = "".join(
1289            (
1290                partition,
1291                order,
1292                measures,
1293                rows,
1294                after,
1295                pattern,
1296                define,
1297            )
1298        )
1299        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}"
1300
1301    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
1302        return csv(
1303            *sqls,
1304            *[self.sql(sql) for sql in expression.args.get("joins") or []],
1305            self.sql(expression, "match"),
1306            *[self.sql(sql) for sql in expression.args.get("laterals") or []],
1307            self.sql(expression, "where"),
1308            self.sql(expression, "group"),
1309            self.sql(expression, "having"),
1310            self.sql(expression, "qualify"),
1311            self.seg("WINDOW ") + self.expressions(expression, "windows", flat=True)
1312            if expression.args.get("windows")
1313            else "",
1314            self.sql(expression, "distribute"),
1315            self.sql(expression, "sort"),
1316            self.sql(expression, "cluster"),
1317            self.sql(expression, "order"),
1318            self.sql(expression, "limit"),
1319            self.sql(expression, "offset"),
1320            self.sql(expression, "lock"),
1321            self.sql(expression, "sample"),
1322            sep="",
1323        )
1324
1325    def select_sql(self, expression: exp.Select) -> str:
1326        hint = self.sql(expression, "hint")
1327        distinct = self.sql(expression, "distinct")
1328        distinct = f" {distinct}" if distinct else ""
1329        expressions = self.expressions(expression)
1330        expressions = f"{self.sep()}{expressions}" if expressions else expressions
1331        sql = self.query_modifiers(
1332            expression,
1333            f"SELECT{hint}{distinct}{expressions}",
1334            self.sql(expression, "into", comment=False),
1335            self.sql(expression, "from", comment=False),
1336        )
1337        return self.prepend_ctes(expression, sql)
1338
1339    def schema_sql(self, expression: exp.Schema) -> str:
1340        this = self.sql(expression, "this")
1341        this = f"{this} " if this else ""
1342        sql = f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
1343        return f"{this}{sql}"
1344
1345    def star_sql(self, expression: exp.Star) -> str:
1346        except_ = self.expressions(expression, key="except", flat=True)
1347        except_ = f"{self.seg(self.STAR_MAPPING['except'])} ({except_})" if except_ else ""
1348        replace = self.expressions(expression, key="replace", flat=True)
1349        replace = f"{self.seg(self.STAR_MAPPING['replace'])} ({replace})" if replace else ""
1350        return f"*{except_}{replace}"
1351
1352    def structkwarg_sql(self, expression: exp.StructKwarg) -> str:
1353        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
1354
1355    def parameter_sql(self, expression: exp.Parameter) -> str:
1356        this = self.sql(expression, "this")
1357        this = f"{{{this}}}" if expression.args.get("wrapped") else f"{this}"
1358        return f"{self.PARAMETER_TOKEN}{this}"
1359
1360    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
1361        this = self.sql(expression, "this")
1362        kind = expression.text("kind")
1363        if kind:
1364            kind = f"{kind}."
1365        return f"@@{kind}{this}"
1366
1367    def placeholder_sql(self, expression: exp.Placeholder) -> str:
1368        return f":{expression.name}" if expression.name else "?"
1369
1370    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
1371        alias = self.sql(expression, "alias")
1372        alias = f"{sep}{alias}" if alias else ""
1373
1374        sql = self.query_modifiers(
1375            expression,
1376            self.wrap(expression),
1377            alias,
1378            self.expressions(expression, key="pivots", sep=" "),
1379        )
1380
1381        return self.prepend_ctes(expression, sql)
1382
1383    def qualify_sql(self, expression: exp.Qualify) -> str:
1384        this = self.indent(self.sql(expression, "this"))
1385        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
1386
1387    def union_sql(self, expression: exp.Union) -> str:
1388        return self.prepend_ctes(
1389            expression,
1390            self.set_operation(expression, self.union_op(expression)),
1391        )
1392
1393    def union_op(self, expression: exp.Union) -> str:
1394        kind = " DISTINCT" if self.EXPLICIT_UNION else ""
1395        kind = kind if expression.args.get("distinct") else " ALL"
1396        return f"UNION{kind}"
1397
1398    def unnest_sql(self, expression: exp.Unnest) -> str:
1399        args = self.expressions(expression, flat=True)
1400        alias = expression.args.get("alias")
1401        if alias and self.unnest_column_only:
1402            columns = alias.columns
1403            alias = self.sql(columns[0]) if columns else ""
1404        else:
1405            alias = self.sql(expression, "alias")
1406        alias = f" AS {alias}" if alias else alias
1407        ordinality = " WITH ORDINALITY" if expression.args.get("ordinality") else ""
1408        offset = expression.args.get("offset")
1409        offset = f" WITH OFFSET AS {self.sql(offset)}" if offset else ""
1410        return f"UNNEST({args}){ordinality}{alias}{offset}"
1411
1412    def where_sql(self, expression: exp.Where) -> str:
1413        this = self.indent(self.sql(expression, "this"))
1414        return f"{self.seg('WHERE')}{self.sep()}{this}"
1415
1416    def window_sql(self, expression: exp.Window) -> str:
1417        this = self.sql(expression, "this")
1418
1419        partition = self.partition_by_sql(expression)
1420
1421        order = expression.args.get("order")
1422        order_sql = self.order_sql(order, flat=True) if order else ""
1423
1424        partition_sql = partition + " " if partition and order else partition
1425
1426        spec = expression.args.get("spec")
1427        spec_sql = " " + self.window_spec_sql(spec) if spec else ""
1428
1429        alias = self.sql(expression, "alias")
1430        this = f"{this} {'AS' if expression.arg_key == 'windows' else 'OVER'}"
1431
1432        if not partition and not order and not spec and alias:
1433            return f"{this} {alias}"
1434
1435        window_args = alias + partition_sql + order_sql + spec_sql
1436
1437        return f"{this} ({window_args.strip()})"
1438
1439    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
1440        partition = self.expressions(expression, key="partition_by", flat=True)
1441        return f"PARTITION BY {partition}" if partition else ""
1442
1443    def window_spec_sql(self, expression: exp.WindowSpec) -> str:
1444        kind = self.sql(expression, "kind")
1445        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
1446        end = (
1447            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
1448            or "CURRENT ROW"
1449        )
1450        return f"{kind} BETWEEN {start} AND {end}"
1451
1452    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
1453        this = self.sql(expression, "this")
1454        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
1455        return f"{this} WITHIN GROUP ({expression_sql})"
1456
1457    def between_sql(self, expression: exp.Between) -> str:
1458        this = self.sql(expression, "this")
1459        low = self.sql(expression, "low")
1460        high = self.sql(expression, "high")
1461        return f"{this} BETWEEN {low} AND {high}"
1462
1463    def bracket_sql(self, expression: exp.Bracket) -> str:
1464        expressions = apply_index_offset(expression.expressions, self.index_offset)
1465        expressions_sql = ", ".join(self.sql(e) for e in expressions)
1466
1467        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
1468
1469    def all_sql(self, expression: exp.All) -> str:
1470        return f"ALL {self.wrap(expression)}"
1471
1472    def any_sql(self, expression: exp.Any) -> str:
1473        this = self.sql(expression, "this")
1474        if isinstance(expression.this, exp.Subqueryable):
1475            this = self.wrap(this)
1476        return f"ANY {this}"
1477
1478    def exists_sql(self, expression: exp.Exists) -> str:
1479        return f"EXISTS{self.wrap(expression)}"
1480
1481    def case_sql(self, expression: exp.Case) -> str:
1482        this = self.sql(expression, "this")
1483        statements = [f"CASE {this}" if this else "CASE"]
1484
1485        for e in expression.args["ifs"]:
1486            statements.append(f"WHEN {self.sql(e, 'this')}")
1487            statements.append(f"THEN {self.sql(e, 'true')}")
1488
1489        default = self.sql(expression, "default")
1490
1491        if default:
1492            statements.append(f"ELSE {default}")
1493
1494        statements.append("END")
1495
1496        if self.pretty and self.text_width(statements) > self._max_text_width:
1497            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
1498
1499        return " ".join(statements)
1500
1501    def constraint_sql(self, expression: exp.Constraint) -> str:
1502        this = self.sql(expression, "this")
1503        expressions = self.expressions(expression, flat=True)
1504        return f"CONSTRAINT {this} {expressions}"
1505
1506    def extract_sql(self, expression: exp.Extract) -> str:
1507        this = self.sql(expression, "this")
1508        expression_sql = self.sql(expression, "expression")
1509        return f"EXTRACT({this} FROM {expression_sql})"
1510
1511    def trim_sql(self, expression: exp.Trim) -> str:
1512        trim_type = self.sql(expression, "position")
1513
1514        if trim_type == "LEADING":
1515            return self.func("LTRIM", expression.this)
1516        elif trim_type == "TRAILING":
1517            return self.func("RTRIM", expression.this)
1518        else:
1519            return self.func("TRIM", expression.this, expression.expression)
1520
1521    def concat_sql(self, expression: exp.Concat) -> str:
1522        if len(expression.expressions) == 1:
1523            return self.sql(expression.expressions[0])
1524        return self.function_fallback_sql(expression)
1525
1526    def check_sql(self, expression: exp.Check) -> str:
1527        this = self.sql(expression, key="this")
1528        return f"CHECK ({this})"
1529
1530    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
1531        expressions = self.expressions(expression, flat=True)
1532        reference = self.sql(expression, "reference")
1533        reference = f" {reference}" if reference else ""
1534        delete = self.sql(expression, "delete")
1535        delete = f" ON DELETE {delete}" if delete else ""
1536        update = self.sql(expression, "update")
1537        update = f" ON UPDATE {update}" if update else ""
1538        return f"FOREIGN KEY ({expressions}){reference}{delete}{update}"
1539
1540    def primarykey_sql(self, expression: exp.ForeignKey) -> str:
1541        expressions = self.expressions(expression, flat=True)
1542        options = self.expressions(expression, "options", flat=True, sep=" ")
1543        options = f" {options}" if options else ""
1544        return f"PRIMARY KEY ({expressions}){options}"
1545
1546    def unique_sql(self, expression: exp.Unique) -> str:
1547        columns = self.expressions(expression, key="expressions")
1548        return f"UNIQUE ({columns})"
1549
1550    def if_sql(self, expression: exp.If) -> str:
1551        return self.case_sql(
1552            exp.Case(ifs=[expression.copy()], default=expression.args.get("false"))
1553        )
1554
1555    def in_sql(self, expression: exp.In) -> str:
1556        query = expression.args.get("query")
1557        unnest = expression.args.get("unnest")
1558        field = expression.args.get("field")
1559        is_global = " GLOBAL" if expression.args.get("is_global") else ""
1560
1561        if query:
1562            in_sql = self.wrap(query)
1563        elif unnest:
1564            in_sql = self.in_unnest_op(unnest)
1565        elif field:
1566            in_sql = self.sql(field)
1567        else:
1568            in_sql = f"({self.expressions(expression, flat=True)})"
1569
1570        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
1571
1572    def in_unnest_op(self, unnest: exp.Unnest) -> str:
1573        return f"(SELECT {self.sql(unnest)})"
1574
1575    def interval_sql(self, expression: exp.Interval) -> str:
1576        this = expression.args.get("this")
1577        if this:
1578            this = (
1579                f" {this}"
1580                if isinstance(this, exp.Literal) or isinstance(this, exp.Paren)
1581                else f" ({this})"
1582            )
1583        else:
1584            this = ""
1585        unit = self.sql(expression, "unit")
1586        unit = f" {unit}" if unit else ""
1587        return f"INTERVAL{this}{unit}"
1588
1589    def return_sql(self, expression: exp.Return) -> str:
1590        return f"RETURN {self.sql(expression, 'this')}"
1591
1592    def reference_sql(self, expression: exp.Reference) -> str:
1593        this = self.sql(expression, "this")
1594        expressions = self.expressions(expression, flat=True)
1595        expressions = f"({expressions})" if expressions else ""
1596        options = self.expressions(expression, "options", flat=True, sep=" ")
1597        options = f" {options}" if options else ""
1598        return f"REFERENCES {this}{expressions}{options}"
1599
1600    def anonymous_sql(self, expression: exp.Anonymous) -> str:
1601        return self.func(expression.name, *expression.expressions)
1602
1603    def paren_sql(self, expression: exp.Paren) -> str:
1604        if isinstance(expression.unnest(), exp.Select):
1605            sql = self.wrap(expression)
1606        else:
1607            sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
1608            sql = f"({sql}{self.seg(')', sep='')}"
1609
1610        return self.prepend_ctes(expression, sql)
1611
1612    def neg_sql(self, expression: exp.Neg) -> str:
1613        # This makes sure we don't convert "- - 5" to "--5", which is a comment
1614        this_sql = self.sql(expression, "this")
1615        sep = " " if this_sql[0] == "-" else ""
1616        return f"-{sep}{this_sql}"
1617
1618    def not_sql(self, expression: exp.Not) -> str:
1619        return f"NOT {self.sql(expression, 'this')}"
1620
1621    def alias_sql(self, expression: exp.Alias) -> str:
1622        to_sql = self.sql(expression, "alias")
1623        to_sql = f" AS {to_sql}" if to_sql else ""
1624        return f"{self.sql(expression, 'this')}{to_sql}"
1625
1626    def aliases_sql(self, expression: exp.Aliases) -> str:
1627        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
1628
1629    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
1630        this = self.sql(expression, "this")
1631        zone = self.sql(expression, "zone")
1632        return f"{this} AT TIME ZONE {zone}"
1633
1634    def add_sql(self, expression: exp.Add) -> str:
1635        return self.binary(expression, "+")
1636
1637    def and_sql(self, expression: exp.And) -> str:
1638        return self.connector_sql(expression, "AND")
1639
1640    def connector_sql(self, expression: exp.Connector, op: str) -> str:
1641        if not self.pretty:
1642            return self.binary(expression, op)
1643
1644        sqls = tuple(self.sql(e) for e in expression.flatten(unnest=False))
1645        sep = "\n" if self.text_width(sqls) > self._max_text_width else " "
1646        return f"{sep}{op} ".join(sqls)
1647
1648    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
1649        return self.binary(expression, "&")
1650
1651    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
1652        return self.binary(expression, "<<")
1653
1654    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
1655        return f"~{self.sql(expression, 'this')}"
1656
1657    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
1658        return self.binary(expression, "|")
1659
1660    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
1661        return self.binary(expression, ">>")
1662
1663    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
1664        return self.binary(expression, "^")
1665
1666    def cast_sql(self, expression: exp.Cast) -> str:
1667        return f"CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')})"
1668
1669    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
1670        zone = self.sql(expression, "this")
1671        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
1672
1673    def collate_sql(self, expression: exp.Collate) -> str:
1674        return self.binary(expression, "COLLATE")
1675
1676    def command_sql(self, expression: exp.Command) -> str:
1677        return f"{self.sql(expression, 'this').upper()} {expression.text('expression').strip()}"
1678
1679    def comment_sql(self, expression: exp.Comment) -> str:
1680        this = self.sql(expression, "this")
1681        kind = expression.args["kind"]
1682        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1683        expression_sql = self.sql(expression, "expression")
1684        return f"COMMENT{exists_sql}ON {kind} {this} IS {expression_sql}"
1685
1686    def transaction_sql(self, expression: exp.Transaction) -> str:
1687        return "BEGIN"
1688
1689    def commit_sql(self, expression: exp.Commit) -> str:
1690        chain = expression.args.get("chain")
1691        if chain is not None:
1692            chain = " AND CHAIN" if chain else " AND NO CHAIN"
1693
1694        return f"COMMIT{chain or ''}"
1695
1696    def rollback_sql(self, expression: exp.Rollback) -> str:
1697        savepoint = expression.args.get("savepoint")
1698        savepoint = f" TO {savepoint}" if savepoint else ""
1699        return f"ROLLBACK{savepoint}"
1700
1701    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
1702        this = self.sql(expression, "this")
1703
1704        dtype = self.sql(expression, "dtype")
1705        if dtype:
1706            collate = self.sql(expression, "collate")
1707            collate = f" COLLATE {collate}" if collate else ""
1708            using = self.sql(expression, "using")
1709            using = f" USING {using}" if using else ""
1710            return f"ALTER COLUMN {this} TYPE {dtype}{collate}{using}"
1711
1712        default = self.sql(expression, "default")
1713        if default:
1714            return f"ALTER COLUMN {this} SET DEFAULT {default}"
1715
1716        if not expression.args.get("drop"):
1717            self.unsupported("Unsupported ALTER COLUMN syntax")
1718
1719        return f"ALTER COLUMN {this} DROP DEFAULT"
1720
1721    def renametable_sql(self, expression: exp.RenameTable) -> str:
1722        this = self.sql(expression, "this")
1723        return f"RENAME TO {this}"
1724
1725    def altertable_sql(self, expression: exp.AlterTable) -> str:
1726        actions = expression.args["actions"]
1727
1728        if isinstance(actions[0], exp.ColumnDef):
1729            actions = self.expressions(expression, "actions", prefix="ADD COLUMN ")
1730        elif isinstance(actions[0], exp.Schema):
1731            actions = self.expressions(expression, "actions", prefix="ADD COLUMNS ")
1732        elif isinstance(actions[0], exp.Delete):
1733            actions = self.expressions(expression, "actions", flat=True)
1734        else:
1735            actions = self.expressions(expression, "actions")
1736
1737        exists = " IF EXISTS" if expression.args.get("exists") else ""
1738        return f"ALTER TABLE{exists} {self.sql(expression, 'this')} {actions}"
1739
1740    def droppartition_sql(self, expression: exp.DropPartition) -> str:
1741        expressions = self.expressions(expression)
1742        exists = " IF EXISTS " if expression.args.get("exists") else " "
1743        return f"DROP{exists}{expressions}"
1744
1745    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
1746        this = self.sql(expression, "this")
1747        expression_ = self.sql(expression, "expression")
1748        add_constraint = f"ADD CONSTRAINT {this}" if this else "ADD"
1749
1750        enforced = expression.args.get("enforced")
1751        if enforced is not None:
1752            return f"{add_constraint} CHECK ({expression_}){' ENFORCED' if enforced else ''}"
1753
1754        return f"{add_constraint} {expression_}"
1755
1756    def distinct_sql(self, expression: exp.Distinct) -> str:
1757        this = self.expressions(expression, flat=True)
1758        this = f" {this}" if this else ""
1759
1760        on = self.sql(expression, "on")
1761        on = f" ON {on}" if on else ""
1762        return f"DISTINCT{this}{on}"
1763
1764    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
1765        return f"{self.sql(expression, 'this')} IGNORE NULLS"
1766
1767    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
1768        return f"{self.sql(expression, 'this')} RESPECT NULLS"
1769
1770    def intdiv_sql(self, expression: exp.IntDiv) -> str:
1771        return self.sql(
1772            exp.Cast(
1773                this=exp.Div(this=expression.this, expression=expression.expression),
1774                to=exp.DataType(this=exp.DataType.Type.INT),
1775            )
1776        )
1777
1778    def dpipe_sql(self, expression: exp.DPipe) -> str:
1779        return self.binary(expression, "||")
1780
1781    def div_sql(self, expression: exp.Div) -> str:
1782        return self.binary(expression, "/")
1783
1784    def overlaps_sql(self, expression: exp.Overlaps) -> str:
1785        return self.binary(expression, "OVERLAPS")
1786
1787    def distance_sql(self, expression: exp.Distance) -> str:
1788        return self.binary(expression, "<->")
1789
1790    def dot_sql(self, expression: exp.Dot) -> str:
1791        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
1792
1793    def eq_sql(self, expression: exp.EQ) -> str:
1794        return self.binary(expression, "=")
1795
1796    def escape_sql(self, expression: exp.Escape) -> str:
1797        return self.binary(expression, "ESCAPE")
1798
1799    def glob_sql(self, expression: exp.Glob) -> str:
1800        return self.binary(expression, "GLOB")
1801
1802    def gt_sql(self, expression: exp.GT) -> str:
1803        return self.binary(expression, ">")
1804
1805    def gte_sql(self, expression: exp.GTE) -> str:
1806        return self.binary(expression, ">=")
1807
1808    def ilike_sql(self, expression: exp.ILike) -> str:
1809        return self.binary(expression, "ILIKE")
1810
1811    def is_sql(self, expression: exp.Is) -> str:
1812        return self.binary(expression, "IS")
1813
1814    def like_sql(self, expression: exp.Like) -> str:
1815        return self.binary(expression, "LIKE")
1816
1817    def similarto_sql(self, expression: exp.SimilarTo) -> str:
1818        return self.binary(expression, "SIMILAR TO")
1819
1820    def lt_sql(self, expression: exp.LT) -> str:
1821        return self.binary(expression, "<")
1822
1823    def lte_sql(self, expression: exp.LTE) -> str:
1824        return self.binary(expression, "<=")
1825
1826    def mod_sql(self, expression: exp.Mod) -> str:
1827        return self.binary(expression, "%")
1828
1829    def mul_sql(self, expression: exp.Mul) -> str:
1830        return self.binary(expression, "*")
1831
1832    def neq_sql(self, expression: exp.NEQ) -> str:
1833        return self.binary(expression, "<>")
1834
1835    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
1836        return self.binary(expression, "IS NOT DISTINCT FROM")
1837
1838    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
1839        return self.binary(expression, "IS DISTINCT FROM")
1840
1841    def or_sql(self, expression: exp.Or) -> str:
1842        return self.connector_sql(expression, "OR")
1843
1844    def slice_sql(self, expression: exp.Slice) -> str:
1845        return self.binary(expression, ":")
1846
1847    def sub_sql(self, expression: exp.Sub) -> str:
1848        return self.binary(expression, "-")
1849
1850    def trycast_sql(self, expression: exp.TryCast) -> str:
1851        return f"TRY_CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')})"
1852
1853    def use_sql(self, expression: exp.Use) -> str:
1854        kind = self.sql(expression, "kind")
1855        kind = f" {kind}" if kind else ""
1856        this = self.sql(expression, "this")
1857        this = f" {this}" if this else ""
1858        return f"USE{kind}{this}"
1859
1860    def binary(self, expression: exp.Binary, op: str) -> str:
1861        return f"{self.sql(expression, 'this')} {op} {self.sql(expression, 'expression')}"
1862
1863    def function_fallback_sql(self, expression: exp.Func) -> str:
1864        args = []
1865        for arg_value in expression.args.values():
1866            if isinstance(arg_value, list):
1867                for value in arg_value:
1868                    args.append(value)
1869            else:
1870                args.append(arg_value)
1871
1872        return self.func(expression.sql_name(), *args)
1873
1874    def func(self, name: str, *args: t.Optional[exp.Expression | str]) -> str:
1875        return f"{self.normalize_func(name)}({self.format_args(*args)})"
1876
1877    def format_args(self, *args: t.Optional[str | exp.Expression]) -> str:
1878        arg_sqls = tuple(self.sql(arg) for arg in args if arg is not None)
1879        if self.pretty and self.text_width(arg_sqls) > self._max_text_width:
1880            return self.indent("\n" + f",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True)
1881        return ", ".join(arg_sqls)
1882
1883    def text_width(self, args: t.Iterable) -> int:
1884        return sum(len(arg) for arg in args)
1885
1886    def format_time(self, expression: exp.Expression) -> t.Optional[str]:
1887        return format_time(self.sql(expression, "format"), self.time_mapping, self.time_trie)
1888
1889    def expressions(
1890        self,
1891        expression: exp.Expression,
1892        key: t.Optional[str] = None,
1893        flat: bool = False,
1894        indent: bool = True,
1895        sep: str = ", ",
1896        prefix: str = "",
1897    ) -> str:
1898        expressions = expression.args.get(key or "expressions")
1899
1900        if not expressions:
1901            return ""
1902
1903        if flat:
1904            return sep.join(self.sql(e) for e in expressions)
1905
1906        num_sqls = len(expressions)
1907
1908        # These are calculated once in case we have the leading_comma / pretty option set, correspondingly
1909        pad = " " * self.pad
1910        stripped_sep = sep.strip()
1911
1912        result_sqls = []
1913        for i, e in enumerate(expressions):
1914            sql = self.sql(e, comment=False)
1915            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
1916
1917            if self.pretty:
1918                if self._leading_comma:
1919                    result_sqls.append(f"{sep if i > 0 else pad}{prefix}{sql}{comments}")
1920                else:
1921                    result_sqls.append(
1922                        f"{prefix}{sql}{stripped_sep if i + 1 < num_sqls else ''}{comments}"
1923                    )
1924            else:
1925                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
1926
1927        result_sql = "\n".join(result_sqls) if self.pretty else "".join(result_sqls)
1928        return self.indent(result_sql, skip_first=False) if indent else result_sql
1929
1930    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
1931        flat = flat or isinstance(expression.parent, exp.Properties)
1932        expressions_sql = self.expressions(expression, flat=flat)
1933        if flat:
1934            return f"{op} {expressions_sql}"
1935        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
1936
1937    def naked_property(self, expression: exp.Property) -> str:
1938        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
1939        if not property_name:
1940            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
1941        return f"{property_name} {self.sql(expression, 'this')}"
1942
1943    def set_operation(self, expression: exp.Expression, op: str) -> str:
1944        this = self.sql(expression, "this")
1945        op = self.seg(op)
1946        return self.query_modifiers(
1947            expression, f"{this}{op}{self.sep()}{self.sql(expression, 'expression')}"
1948        )
1949
1950    def tag_sql(self, expression: exp.Tag) -> str:
1951        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
1952
1953    def token_sql(self, token_type: TokenType) -> str:
1954        return self.TOKEN_MAPPING.get(token_type, token_type.name)
1955
1956    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
1957        this = self.sql(expression, "this")
1958        expressions = self.no_identify(self.expressions, expression)
1959        expressions = (
1960            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
1961        )
1962        return f"{this}{expressions}"
1963
1964    def joinhint_sql(self, expression: exp.JoinHint) -> str:
1965        this = self.sql(expression, "this")
1966        expressions = self.expressions(expression, flat=True)
1967        return f"{this}({expressions})"
1968
1969    def kwarg_sql(self, expression: exp.Kwarg) -> str:
1970        return self.binary(expression, "=>")
1971
1972    def when_sql(self, expression: exp.When) -> str:
1973        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
1974        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
1975        condition = self.sql(expression, "condition")
1976        condition = f" AND {condition}" if condition else ""
1977
1978        then_expression = expression.args.get("then")
1979        if isinstance(then_expression, exp.Insert):
1980            then = f"INSERT {self.sql(then_expression, 'this')}"
1981            if "expression" in then_expression.args:
1982                then += f" VALUES {self.sql(then_expression, 'expression')}"
1983        elif isinstance(then_expression, exp.Update):
1984            if isinstance(then_expression.args.get("expressions"), exp.Star):
1985                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
1986            else:
1987                then = f"UPDATE SET {self.expressions(then_expression, flat=True)}"
1988        else:
1989            then = self.sql(then_expression)
1990        return f"WHEN {matched}{source}{condition} THEN {then}"
1991
1992    def merge_sql(self, expression: exp.Merge) -> str:
1993        this = self.sql(expression, "this")
1994        using = f"USING {self.sql(expression, 'using')}"
1995        on = f"ON {self.sql(expression, 'on')}"
1996        return f"MERGE INTO {this} {using} {on} {self.expressions(expression, sep=' ')}"
1997
1998    def tochar_sql(self, expression: exp.ToChar) -> str:
1999        if expression.args.get("format"):
2000            self.unsupported("Format argument unsupported for TO_CHAR/TO_VARCHAR function")
2001
2002        return self.sql(exp.cast(expression.this, "text"))
class Generator:
  16class Generator:
  17    """
  18    Generator interprets the given syntax tree and produces a SQL string as an output.
  19
  20    Args:
  21        time_mapping (dict): the dictionary of custom time mappings in which the key
  22            represents a python time format and the output the target time format
  23        time_trie (trie): a trie of the time_mapping keys
  24        pretty (bool): if set to True the returned string will be formatted. Default: False.
  25        quote_start (str): specifies which starting character to use to delimit quotes. Default: '.
  26        quote_end (str): specifies which ending character to use to delimit quotes. Default: '.
  27        identifier_start (str): specifies which starting character to use to delimit identifiers. Default: ".
  28        identifier_end (str): specifies which ending character to use to delimit identifiers. Default: ".
  29        identify (bool | str): 'always': always quote, 'safe': quote identifiers if they don't contain an upcase, True defaults to always.
  30        normalize (bool): if set to True all identifiers will lower cased
  31        string_escape (str): specifies a string escape character. Default: '.
  32        identifier_escape (str): specifies an identifier escape character. Default: ".
  33        pad (int): determines padding in a formatted string. Default: 2.
  34        indent (int): determines the size of indentation in a formatted string. Default: 4.
  35        unnest_column_only (bool): if true unnest table aliases are considered only as column aliases
  36        normalize_functions (str): normalize function names, "upper", "lower", or None
  37            Default: "upper"
  38        alias_post_tablesample (bool): if the table alias comes after tablesample
  39            Default: False
  40        unsupported_level (ErrorLevel): determines the generator's behavior when it encounters
  41            unsupported expressions. Default ErrorLevel.WARN.
  42        null_ordering (str): Indicates the default null ordering method to use if not explicitly set.
  43            Options are "nulls_are_small", "nulls_are_large", "nulls_are_last".
  44            Default: "nulls_are_small"
  45        max_unsupported (int): Maximum number of unsupported messages to include in a raised UnsupportedError.
  46            This is only relevant if unsupported_level is ErrorLevel.RAISE.
  47            Default: 3
  48        leading_comma (bool): if the the comma is leading or trailing in select statements
  49            Default: False
  50        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
  51            The default is on the smaller end because the length only represents a segment and not the true
  52            line length.
  53            Default: 80
  54        comments: Whether or not to preserve comments in the output SQL code.
  55            Default: True
  56    """
  57
  58    TRANSFORMS = {
  59        exp.DateAdd: lambda self, e: self.func(
  60            "DATE_ADD", e.this, e.expression, exp.Literal.string(e.text("unit"))
  61        ),
  62        exp.TsOrDsAdd: lambda self, e: self.func(
  63            "TS_OR_DS_ADD", e.this, e.expression, exp.Literal.string(e.text("unit"))
  64        ),
  65        exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
  66        exp.CharacterSetProperty: lambda self, e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}",
  67        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
  68        exp.ExternalProperty: lambda self, e: "EXTERNAL",
  69        exp.LanguageProperty: lambda self, e: self.naked_property(e),
  70        exp.LocationProperty: lambda self, e: self.naked_property(e),
  71        exp.LogProperty: lambda self, e: f"{'NO ' if e.args.get('no') else ''}LOG",
  72        exp.MaterializedProperty: lambda self, e: "MATERIALIZED",
  73        exp.NoPrimaryIndexProperty: lambda self, e: "NO PRIMARY INDEX",
  74        exp.OnCommitProperty: lambda self, e: "ON COMMIT PRESERVE ROWS",
  75        exp.ReturnsProperty: lambda self, e: self.naked_property(e),
  76        exp.SetProperty: lambda self, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
  77        exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}",
  78        exp.TemporaryProperty: lambda self, e: f"{'GLOBAL ' if e.args.get('global_') else ''}TEMPORARY",
  79        exp.TransientProperty: lambda self, e: "TRANSIENT",
  80        exp.VolatilityProperty: lambda self, e: e.name,
  81        exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
  82        exp.CaseSpecificColumnConstraint: lambda self, e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC",
  83        exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
  84        exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
  85        exp.UppercaseColumnConstraint: lambda self, e: f"UPPERCASE",
  86        exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
  87        exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
  88        exp.CheckColumnConstraint: lambda self, e: f"CHECK ({self.sql(e, 'this')})",
  89        exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
  90        exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
  91        exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
  92        exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
  93        exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
  94    }
  95
  96    # Whether or not null ordering is supported in order by
  97    NULL_ORDERING_SUPPORTED = True
  98
  99    # Whether or not locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
 100    LOCKING_READS_SUPPORTED = False
 101
 102    # Always do union distinct or union all
 103    EXPLICIT_UNION = False
 104
 105    # Wrap derived values in parens, usually standard but spark doesn't support it
 106    WRAP_DERIVED_VALUES = True
 107
 108    # Whether or not create function uses an AS before the RETURN
 109    CREATE_FUNCTION_RETURN_AS = True
 110
 111    # Whether or not MERGE ... WHEN MATCHED BY SOURCE is allowed
 112    MATCHED_BY_SOURCE = True
 113
 114    TYPE_MAPPING = {
 115        exp.DataType.Type.NCHAR: "CHAR",
 116        exp.DataType.Type.NVARCHAR: "VARCHAR",
 117        exp.DataType.Type.MEDIUMTEXT: "TEXT",
 118        exp.DataType.Type.LONGTEXT: "TEXT",
 119        exp.DataType.Type.MEDIUMBLOB: "BLOB",
 120        exp.DataType.Type.LONGBLOB: "BLOB",
 121        exp.DataType.Type.INET: "INET",
 122    }
 123
 124    STAR_MAPPING = {
 125        "except": "EXCEPT",
 126        "replace": "REPLACE",
 127    }
 128
 129    TOKEN_MAPPING: t.Dict[TokenType, str] = {}
 130
 131    STRUCT_DELIMITER = ("<", ">")
 132
 133    PARAMETER_TOKEN = "@"
 134
 135    PROPERTIES_LOCATION = {
 136        exp.AfterJournalProperty: exp.Properties.Location.POST_NAME,
 137        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
 138        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
 139        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
 140        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
 141        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
 142        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
 143        exp.Cluster: exp.Properties.Location.POST_SCHEMA,
 144        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
 145        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
 146        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
 147        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
 148        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
 149        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
 150        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
 151        exp.FallbackProperty: exp.Properties.Location.POST_NAME,
 152        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
 153        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
 154        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
 155        exp.JournalProperty: exp.Properties.Location.POST_NAME,
 156        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
 157        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
 158        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
 159        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
 160        exp.LogProperty: exp.Properties.Location.POST_NAME,
 161        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
 162        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
 163        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
 164        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
 165        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
 166        exp.Property: exp.Properties.Location.POST_WITH,
 167        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
 168        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
 169        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
 170        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
 171        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
 172        exp.SetProperty: exp.Properties.Location.POST_CREATE,
 173        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
 174        exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
 175        exp.TableFormatProperty: exp.Properties.Location.POST_WITH,
 176        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
 177        exp.TransientProperty: exp.Properties.Location.POST_CREATE,
 178        exp.VolatilityProperty: exp.Properties.Location.POST_SCHEMA,
 179        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
 180        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
 181    }
 182
 183    WITH_SEPARATED_COMMENTS = (exp.Select, exp.From, exp.Where, exp.Binary)
 184    SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
 185
 186    __slots__ = (
 187        "time_mapping",
 188        "time_trie",
 189        "pretty",
 190        "quote_start",
 191        "quote_end",
 192        "identifier_start",
 193        "identifier_end",
 194        "identify",
 195        "normalize",
 196        "string_escape",
 197        "identifier_escape",
 198        "pad",
 199        "index_offset",
 200        "unnest_column_only",
 201        "alias_post_tablesample",
 202        "normalize_functions",
 203        "unsupported_level",
 204        "unsupported_messages",
 205        "null_ordering",
 206        "max_unsupported",
 207        "_indent",
 208        "_escaped_quote_end",
 209        "_escaped_identifier_end",
 210        "_leading_comma",
 211        "_max_text_width",
 212        "_comments",
 213    )
 214
 215    def __init__(
 216        self,
 217        time_mapping=None,
 218        time_trie=None,
 219        pretty=None,
 220        quote_start=None,
 221        quote_end=None,
 222        identifier_start=None,
 223        identifier_end=None,
 224        identify=False,
 225        normalize=False,
 226        string_escape=None,
 227        identifier_escape=None,
 228        pad=2,
 229        indent=2,
 230        index_offset=0,
 231        unnest_column_only=False,
 232        alias_post_tablesample=False,
 233        normalize_functions="upper",
 234        unsupported_level=ErrorLevel.WARN,
 235        null_ordering=None,
 236        max_unsupported=3,
 237        leading_comma=False,
 238        max_text_width=80,
 239        comments=True,
 240    ):
 241        import sqlglot
 242
 243        self.time_mapping = time_mapping or {}
 244        self.time_trie = time_trie
 245        self.pretty = pretty if pretty is not None else sqlglot.pretty
 246        self.quote_start = quote_start or "'"
 247        self.quote_end = quote_end or "'"
 248        self.identifier_start = identifier_start or '"'
 249        self.identifier_end = identifier_end or '"'
 250        self.identify = identify
 251        self.normalize = normalize
 252        self.string_escape = string_escape or "'"
 253        self.identifier_escape = identifier_escape or '"'
 254        self.pad = pad
 255        self.index_offset = index_offset
 256        self.unnest_column_only = unnest_column_only
 257        self.alias_post_tablesample = alias_post_tablesample
 258        self.normalize_functions = normalize_functions
 259        self.unsupported_level = unsupported_level
 260        self.unsupported_messages = []
 261        self.max_unsupported = max_unsupported
 262        self.null_ordering = null_ordering
 263        self._indent = indent
 264        self._escaped_quote_end = self.string_escape + self.quote_end
 265        self._escaped_identifier_end = self.identifier_escape + self.identifier_end
 266        self._leading_comma = leading_comma
 267        self._max_text_width = max_text_width
 268        self._comments = comments
 269
 270    def generate(self, expression: t.Optional[exp.Expression]) -> str:
 271        """
 272        Generates a SQL string by interpreting the given syntax tree.
 273
 274        Args
 275            expression: the syntax tree.
 276
 277        Returns
 278            the SQL string.
 279        """
 280        self.unsupported_messages = []
 281        sql = self.sql(expression).strip()
 282
 283        if self.unsupported_level == ErrorLevel.IGNORE:
 284            return sql
 285
 286        if self.unsupported_level == ErrorLevel.WARN:
 287            for msg in self.unsupported_messages:
 288                logger.warning(msg)
 289        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
 290            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
 291
 292        if self.pretty:
 293            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
 294        return sql
 295
 296    def unsupported(self, message: str) -> None:
 297        if self.unsupported_level == ErrorLevel.IMMEDIATE:
 298            raise UnsupportedError(message)
 299        self.unsupported_messages.append(message)
 300
 301    def sep(self, sep: str = " ") -> str:
 302        return f"{sep.strip()}\n" if self.pretty else sep
 303
 304    def seg(self, sql: str, sep: str = " ") -> str:
 305        return f"{self.sep(sep)}{sql}"
 306
 307    def pad_comment(self, comment: str) -> str:
 308        comment = " " + comment if comment[0].strip() else comment
 309        comment = comment + " " if comment[-1].strip() else comment
 310        return comment
 311
 312    def maybe_comment(self, sql: str, expression: exp.Expression) -> str:
 313        comments = expression.comments if self._comments else None
 314
 315        if not comments:
 316            return sql
 317
 318        sep = "\n" if self.pretty else " "
 319        comments_sql = sep.join(
 320            f"/*{self.pad_comment(comment)}*/" for comment in comments if comment
 321        )
 322
 323        if not comments_sql:
 324            return sql
 325
 326        if isinstance(expression, self.WITH_SEPARATED_COMMENTS):
 327            return f"{comments_sql}{self.sep()}{sql}"
 328
 329        return f"{sql} {comments_sql}"
 330
 331    def wrap(self, expression: exp.Expression | str) -> str:
 332        this_sql = self.indent(
 333            self.sql(expression)
 334            if isinstance(expression, (exp.Select, exp.Union))
 335            else self.sql(expression, "this"),
 336            level=1,
 337            pad=0,
 338        )
 339        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
 340
 341    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
 342        original = self.identify
 343        self.identify = False
 344        result = func(*args, **kwargs)
 345        self.identify = original
 346        return result
 347
 348    def normalize_func(self, name: str) -> str:
 349        if self.normalize_functions == "upper":
 350            return name.upper()
 351        if self.normalize_functions == "lower":
 352            return name.lower()
 353        return name
 354
 355    def indent(
 356        self,
 357        sql: str,
 358        level: int = 0,
 359        pad: t.Optional[int] = None,
 360        skip_first: bool = False,
 361        skip_last: bool = False,
 362    ) -> str:
 363        if not self.pretty:
 364            return sql
 365
 366        pad = self.pad if pad is None else pad
 367        lines = sql.split("\n")
 368
 369        return "\n".join(
 370            line
 371            if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
 372            else f"{' ' * (level * self._indent + pad)}{line}"
 373            for i, line in enumerate(lines)
 374        )
 375
 376    def sql(
 377        self,
 378        expression: t.Optional[str | exp.Expression],
 379        key: t.Optional[str] = None,
 380        comment: bool = True,
 381    ) -> str:
 382        if not expression:
 383            return ""
 384
 385        if isinstance(expression, str):
 386            return expression
 387
 388        if key:
 389            return self.sql(expression.args.get(key))
 390
 391        transform = self.TRANSFORMS.get(expression.__class__)
 392
 393        if callable(transform):
 394            sql = transform(self, expression)
 395        elif transform:
 396            sql = transform
 397        elif isinstance(expression, exp.Expression):
 398            exp_handler_name = f"{expression.key}_sql"
 399
 400            if hasattr(self, exp_handler_name):
 401                sql = getattr(self, exp_handler_name)(expression)
 402            elif isinstance(expression, exp.Func):
 403                sql = self.function_fallback_sql(expression)
 404            elif isinstance(expression, exp.Property):
 405                sql = self.property_sql(expression)
 406            else:
 407                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
 408        else:
 409            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
 410
 411        return self.maybe_comment(sql, expression) if self._comments and comment else sql
 412
 413    def uncache_sql(self, expression: exp.Uncache) -> str:
 414        table = self.sql(expression, "this")
 415        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
 416        return f"UNCACHE TABLE{exists_sql} {table}"
 417
 418    def cache_sql(self, expression: exp.Cache) -> str:
 419        lazy = " LAZY" if expression.args.get("lazy") else ""
 420        table = self.sql(expression, "this")
 421        options = expression.args.get("options")
 422        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
 423        sql = self.sql(expression, "expression")
 424        sql = f" AS{self.sep()}{sql}" if sql else ""
 425        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
 426        return self.prepend_ctes(expression, sql)
 427
 428    def characterset_sql(self, expression: exp.CharacterSet) -> str:
 429        if isinstance(expression.parent, exp.Cast):
 430            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
 431        default = "DEFAULT " if expression.args.get("default") else ""
 432        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
 433
 434    def column_sql(self, expression: exp.Column) -> str:
 435        return ".".join(
 436            self.sql(part)
 437            for part in (
 438                expression.args.get("catalog"),
 439                expression.args.get("db"),
 440                expression.args.get("table"),
 441                expression.args.get("this"),
 442            )
 443            if part
 444        )
 445
 446    def columndef_sql(self, expression: exp.ColumnDef) -> str:
 447        column = self.sql(expression, "this")
 448        kind = self.sql(expression, "kind")
 449        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
 450        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
 451        kind = f" {kind}" if kind else ""
 452        constraints = f" {constraints}" if constraints else ""
 453
 454        return f"{exists}{column}{kind}{constraints}"
 455
 456    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
 457        this = self.sql(expression, "this")
 458        kind_sql = self.sql(expression, "kind")
 459        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
 460
 461    def autoincrementcolumnconstraint_sql(self, _) -> str:
 462        return self.token_sql(TokenType.AUTO_INCREMENT)
 463
 464    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
 465        if isinstance(expression.this, list):
 466            this = self.wrap(self.expressions(expression, key="this", flat=True))
 467        else:
 468            this = self.sql(expression, "this")
 469
 470        return f"COMPRESS {this}"
 471
 472    def generatedasidentitycolumnconstraint_sql(
 473        self, expression: exp.GeneratedAsIdentityColumnConstraint
 474    ) -> str:
 475        this = ""
 476        if expression.this is not None:
 477            this = " ALWAYS " if expression.this else " BY DEFAULT "
 478        start = expression.args.get("start")
 479        start = f"START WITH {start}" if start else ""
 480        increment = expression.args.get("increment")
 481        increment = f" INCREMENT BY {increment}" if increment else ""
 482        minvalue = expression.args.get("minvalue")
 483        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
 484        maxvalue = expression.args.get("maxvalue")
 485        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
 486        cycle = expression.args.get("cycle")
 487        cycle_sql = ""
 488        if cycle is not None:
 489            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
 490            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
 491        sequence_opts = ""
 492        if start or increment or cycle_sql:
 493            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
 494            sequence_opts = f" ({sequence_opts.strip()})"
 495        return f"GENERATED{this}AS IDENTITY{sequence_opts}"
 496
 497    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
 498        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
 499
 500    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
 501        desc = expression.args.get("desc")
 502        if desc is not None:
 503            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
 504        return f"PRIMARY KEY"
 505
 506    def uniquecolumnconstraint_sql(self, _) -> str:
 507        return "UNIQUE"
 508
 509    def create_sql(self, expression: exp.Create) -> str:
 510        kind = self.sql(expression, "kind").upper()
 511        properties = expression.args.get("properties")
 512        properties_exp = expression.copy()
 513        properties_locs = self.locate_properties(properties) if properties else {}
 514        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
 515            exp.Properties.Location.POST_WITH
 516        ):
 517            properties_exp.set(
 518                "properties",
 519                exp.Properties(
 520                    expressions=[
 521                        *properties_locs[exp.Properties.Location.POST_SCHEMA],
 522                        *properties_locs[exp.Properties.Location.POST_WITH],
 523                    ]
 524                ),
 525            )
 526        if kind == "TABLE" and properties_locs.get(exp.Properties.Location.POST_NAME):
 527            this_name = self.sql(expression.this, "this")
 528            this_properties = self.properties(
 529                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_NAME]),
 530                wrapped=False,
 531            )
 532            this_schema = f"({self.expressions(expression.this)})"
 533            this = f"{this_name}, {this_properties} {this_schema}"
 534            properties_sql = ""
 535        else:
 536            this = self.sql(expression, "this")
 537            properties_sql = self.sql(properties_exp, "properties")
 538        begin = " BEGIN" if expression.args.get("begin") else ""
 539        expression_sql = self.sql(expression, "expression")
 540        if expression_sql:
 541            expression_sql = f"{begin}{self.sep()}{expression_sql}"
 542
 543            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
 544                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
 545                    postalias_props_sql = self.properties(
 546                        exp.Properties(
 547                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
 548                        ),
 549                        wrapped=False,
 550                    )
 551                    expression_sql = f" AS {postalias_props_sql}{expression_sql}"
 552                else:
 553                    expression_sql = f" AS{expression_sql}"
 554
 555        postindex_props_sql = ""
 556        if properties_locs.get(exp.Properties.Location.POST_INDEX):
 557            postindex_props_sql = self.properties(
 558                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
 559                wrapped=False,
 560                prefix=" ",
 561            )
 562
 563        indexes = expression.args.get("indexes")
 564        if indexes:
 565            indexes_sql: t.List[str] = []
 566            for index in indexes:
 567                ind_unique = " UNIQUE" if index.args.get("unique") else ""
 568                ind_primary = " PRIMARY" if index.args.get("primary") else ""
 569                ind_amp = " AMP" if index.args.get("amp") else ""
 570                ind_name = f" {index.name}" if index.name else ""
 571                ind_columns = (
 572                    f' ({self.expressions(index, key="columns", flat=True)})'
 573                    if index.args.get("columns")
 574                    else ""
 575                )
 576                ind_sql = f"{ind_unique}{ind_primary}{ind_amp} INDEX{ind_name}{ind_columns}"
 577
 578                if indexes_sql:
 579                    indexes_sql.append(ind_sql)
 580                else:
 581                    indexes_sql.append(
 582                        f"{ind_sql}{postindex_props_sql}"
 583                        if index.args.get("primary")
 584                        else f"{postindex_props_sql}{ind_sql}"
 585                    )
 586
 587            index_sql = "".join(indexes_sql)
 588        else:
 589            index_sql = postindex_props_sql
 590
 591        replace = " OR REPLACE" if expression.args.get("replace") else ""
 592        unique = " UNIQUE" if expression.args.get("unique") else ""
 593        volatile = " VOLATILE" if expression.args.get("volatile") else ""
 594
 595        postcreate_props_sql = ""
 596        if properties_locs.get(exp.Properties.Location.POST_CREATE):
 597            postcreate_props_sql = self.properties(
 598                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
 599                sep=" ",
 600                prefix=" ",
 601                wrapped=False,
 602            )
 603
 604        modifiers = "".join((replace, unique, volatile, postcreate_props_sql))
 605
 606        postexpression_props_sql = ""
 607        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
 608            postexpression_props_sql = self.properties(
 609                exp.Properties(
 610                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
 611                ),
 612                sep=" ",
 613                prefix=" ",
 614                wrapped=False,
 615            )
 616
 617        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
 618        no_schema_binding = (
 619            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
 620        )
 621
 622        expression_sql = f"CREATE{modifiers} {kind}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}"
 623        return self.prepend_ctes(expression, expression_sql)
 624
 625    def describe_sql(self, expression: exp.Describe) -> str:
 626        return f"DESCRIBE {self.sql(expression, 'this')}"
 627
 628    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
 629        with_ = self.sql(expression, "with")
 630        if with_:
 631            sql = f"{with_}{self.sep()}{sql}"
 632        return sql
 633
 634    def with_sql(self, expression: exp.With) -> str:
 635        sql = self.expressions(expression, flat=True)
 636        recursive = "RECURSIVE " if expression.args.get("recursive") else ""
 637
 638        return f"WITH {recursive}{sql}"
 639
 640    def cte_sql(self, expression: exp.CTE) -> str:
 641        alias = self.sql(expression, "alias")
 642        return f"{alias} AS {self.wrap(expression)}"
 643
 644    def tablealias_sql(self, expression: exp.TableAlias) -> str:
 645        alias = self.sql(expression, "this")
 646        columns = self.expressions(expression, key="columns", flat=True)
 647        columns = f"({columns})" if columns else ""
 648        return f"{alias}{columns}"
 649
 650    def bitstring_sql(self, expression: exp.BitString) -> str:
 651        return self.sql(expression, "this")
 652
 653    def hexstring_sql(self, expression: exp.HexString) -> str:
 654        return self.sql(expression, "this")
 655
 656    def datatype_sql(self, expression: exp.DataType) -> str:
 657        type_value = expression.this
 658        type_sql = self.TYPE_MAPPING.get(type_value, type_value.value)
 659        nested = ""
 660        interior = self.expressions(expression, flat=True)
 661        values = ""
 662        if interior:
 663            if expression.args.get("nested"):
 664                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
 665                if expression.args.get("values") is not None:
 666                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
 667                    values = (
 668                        f"{delimiters[0]}{self.expressions(expression, 'values')}{delimiters[1]}"
 669                    )
 670            else:
 671                nested = f"({interior})"
 672
 673        return f"{type_sql}{nested}{values}"
 674
 675    def directory_sql(self, expression: exp.Directory) -> str:
 676        local = "LOCAL " if expression.args.get("local") else ""
 677        row_format = self.sql(expression, "row_format")
 678        row_format = f" {row_format}" if row_format else ""
 679        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
 680
 681    def delete_sql(self, expression: exp.Delete) -> str:
 682        this = self.sql(expression, "this")
 683        this = f" FROM {this}" if this else ""
 684        using_sql = (
 685            f" USING {self.expressions(expression, 'using', sep=', USING ')}"
 686            if expression.args.get("using")
 687            else ""
 688        )
 689        where_sql = self.sql(expression, "where")
 690        returning = self.sql(expression, "returning")
 691        sql = f"DELETE{this}{using_sql}{where_sql}{returning}"
 692        return self.prepend_ctes(expression, sql)
 693
 694    def drop_sql(self, expression: exp.Drop) -> str:
 695        this = self.sql(expression, "this")
 696        kind = expression.args["kind"]
 697        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
 698        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
 699        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
 700        cascade = " CASCADE" if expression.args.get("cascade") else ""
 701        return f"DROP{temporary}{materialized} {kind}{exists_sql}{this}{cascade}"
 702
 703    def except_sql(self, expression: exp.Except) -> str:
 704        return self.prepend_ctes(
 705            expression,
 706            self.set_operation(expression, self.except_op(expression)),
 707        )
 708
 709    def except_op(self, expression: exp.Except) -> str:
 710        return f"EXCEPT{'' if expression.args.get('distinct') else ' ALL'}"
 711
 712    def fetch_sql(self, expression: exp.Fetch) -> str:
 713        direction = expression.args.get("direction")
 714        direction = f" {direction.upper()}" if direction else ""
 715        count = expression.args.get("count")
 716        count = f" {count}" if count else ""
 717        return f"{self.seg('FETCH')}{direction}{count} ROWS ONLY"
 718
 719    def filter_sql(self, expression: exp.Filter) -> str:
 720        this = self.sql(expression, "this")
 721        where = self.sql(expression, "expression")[1:]  # where has a leading space
 722        return f"{this} FILTER({where})"
 723
 724    def hint_sql(self, expression: exp.Hint) -> str:
 725        if self.sql(expression, "this"):
 726            self.unsupported("Hints are not supported")
 727        return ""
 728
 729    def index_sql(self, expression: exp.Index) -> str:
 730        this = self.sql(expression, "this")
 731        table = self.sql(expression, "table")
 732        columns = self.sql(expression, "columns")
 733        return f"{this} ON {table} {columns}"
 734
 735    def identifier_sql(self, expression: exp.Identifier) -> str:
 736        text = expression.name
 737        text = text.lower() if self.normalize else text
 738        text = text.replace(self.identifier_end, self._escaped_identifier_end)
 739        if expression.args.get("quoted") or should_identify(text, self.identify):
 740            text = f"{self.identifier_start}{text}{self.identifier_end}"
 741        return text
 742
 743    def national_sql(self, expression: exp.National) -> str:
 744        return f"N{self.sql(expression, 'this')}"
 745
 746    def partition_sql(self, expression: exp.Partition) -> str:
 747        return f"PARTITION({self.expressions(expression)})"
 748
 749    def properties_sql(self, expression: exp.Properties) -> str:
 750        root_properties = []
 751        with_properties = []
 752
 753        for p in expression.expressions:
 754            p_loc = self.PROPERTIES_LOCATION[p.__class__]
 755            if p_loc == exp.Properties.Location.POST_WITH:
 756                with_properties.append(p)
 757            elif p_loc == exp.Properties.Location.POST_SCHEMA:
 758                root_properties.append(p)
 759
 760        return self.root_properties(
 761            exp.Properties(expressions=root_properties)
 762        ) + self.with_properties(exp.Properties(expressions=with_properties))
 763
 764    def root_properties(self, properties: exp.Properties) -> str:
 765        if properties.expressions:
 766            return self.sep() + self.expressions(properties, indent=False, sep=" ")
 767        return ""
 768
 769    def properties(
 770        self,
 771        properties: exp.Properties,
 772        prefix: str = "",
 773        sep: str = ", ",
 774        suffix: str = "",
 775        wrapped: bool = True,
 776    ) -> str:
 777        if properties.expressions:
 778            expressions = self.expressions(properties, sep=sep, indent=False)
 779            expressions = self.wrap(expressions) if wrapped else expressions
 780            return f"{prefix}{' ' if prefix and prefix != ' ' else ''}{expressions}{suffix}"
 781        return ""
 782
 783    def with_properties(self, properties: exp.Properties) -> str:
 784        return self.properties(properties, prefix=self.seg("WITH"))
 785
 786    def locate_properties(
 787        self, properties: exp.Properties
 788    ) -> t.Dict[exp.Properties.Location, list[exp.Property]]:
 789        properties_locs: t.Dict[exp.Properties.Location, list[exp.Property]] = {
 790            key: [] for key in exp.Properties.Location
 791        }
 792
 793        for p in properties.expressions:
 794            p_loc = self.PROPERTIES_LOCATION[p.__class__]
 795            if p_loc == exp.Properties.Location.POST_NAME:
 796                properties_locs[exp.Properties.Location.POST_NAME].append(p)
 797            elif p_loc == exp.Properties.Location.POST_INDEX:
 798                properties_locs[exp.Properties.Location.POST_INDEX].append(p)
 799            elif p_loc == exp.Properties.Location.POST_SCHEMA:
 800                properties_locs[exp.Properties.Location.POST_SCHEMA].append(p)
 801            elif p_loc == exp.Properties.Location.POST_WITH:
 802                properties_locs[exp.Properties.Location.POST_WITH].append(p)
 803            elif p_loc == exp.Properties.Location.POST_CREATE:
 804                properties_locs[exp.Properties.Location.POST_CREATE].append(p)
 805            elif p_loc == exp.Properties.Location.POST_ALIAS:
 806                properties_locs[exp.Properties.Location.POST_ALIAS].append(p)
 807            elif p_loc == exp.Properties.Location.POST_EXPRESSION:
 808                properties_locs[exp.Properties.Location.POST_EXPRESSION].append(p)
 809            elif p_loc == exp.Properties.Location.UNSUPPORTED:
 810                self.unsupported(f"Unsupported property {p.key}")
 811
 812        return properties_locs
 813
 814    def property_sql(self, expression: exp.Property) -> str:
 815        property_cls = expression.__class__
 816        if property_cls == exp.Property:
 817            return f"{expression.name}={self.sql(expression, 'value')}"
 818
 819        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
 820        if not property_name:
 821            self.unsupported(f"Unsupported property {expression.key}")
 822
 823        return f"{property_name}={self.sql(expression, 'this')}"
 824
 825    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
 826        options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
 827        options = f" {options}" if options else ""
 828        return f"LIKE {self.sql(expression, 'this')}{options}"
 829
 830    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
 831        no = "NO " if expression.args.get("no") else ""
 832        protection = " PROTECTION" if expression.args.get("protection") else ""
 833        return f"{no}FALLBACK{protection}"
 834
 835    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
 836        no = "NO " if expression.args.get("no") else ""
 837        dual = "DUAL " if expression.args.get("dual") else ""
 838        before = "BEFORE " if expression.args.get("before") else ""
 839        return f"{no}{dual}{before}JOURNAL"
 840
 841    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
 842        freespace = self.sql(expression, "this")
 843        percent = " PERCENT" if expression.args.get("percent") else ""
 844        return f"FREESPACE={freespace}{percent}"
 845
 846    def afterjournalproperty_sql(self, expression: exp.AfterJournalProperty) -> str:
 847        no = "NO " if expression.args.get("no") else ""
 848        dual = "DUAL " if expression.args.get("dual") else ""
 849        local = ""
 850        if expression.args.get("local") is not None:
 851            local = "LOCAL " if expression.args.get("local") else "NOT LOCAL "
 852        return f"{no}{dual}{local}AFTER JOURNAL"
 853
 854    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
 855        if expression.args.get("default"):
 856            property = "DEFAULT"
 857        elif expression.args.get("on"):
 858            property = "ON"
 859        else:
 860            property = "OFF"
 861        return f"CHECKSUM={property}"
 862
 863    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
 864        if expression.args.get("no"):
 865            return "NO MERGEBLOCKRATIO"
 866        if expression.args.get("default"):
 867            return "DEFAULT MERGEBLOCKRATIO"
 868
 869        percent = " PERCENT" if expression.args.get("percent") else ""
 870        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
 871
 872    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
 873        default = expression.args.get("default")
 874        min = expression.args.get("min")
 875        if default is not None or min is not None:
 876            if default:
 877                property = "DEFAULT"
 878            elif min:
 879                property = "MINIMUM"
 880            else:
 881                property = "MAXIMUM"
 882            return f"{property} DATABLOCKSIZE"
 883        else:
 884            units = expression.args.get("units")
 885            units = f" {units}" if units else ""
 886            return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
 887
 888    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
 889        autotemp = expression.args.get("autotemp")
 890        always = expression.args.get("always")
 891        default = expression.args.get("default")
 892        manual = expression.args.get("manual")
 893        never = expression.args.get("never")
 894
 895        if autotemp is not None:
 896            property = f"AUTOTEMP({self.expressions(autotemp)})"
 897        elif always:
 898            property = "ALWAYS"
 899        elif default:
 900            property = "DEFAULT"
 901        elif manual:
 902            property = "MANUAL"
 903        elif never:
 904            property = "NEVER"
 905        return f"BLOCKCOMPRESSION={property}"
 906
 907    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
 908        no = expression.args.get("no")
 909        no = " NO" if no else ""
 910        concurrent = expression.args.get("concurrent")
 911        concurrent = " CONCURRENT" if concurrent else ""
 912
 913        for_ = ""
 914        if expression.args.get("for_all"):
 915            for_ = " FOR ALL"
 916        elif expression.args.get("for_insert"):
 917            for_ = " FOR INSERT"
 918        elif expression.args.get("for_none"):
 919            for_ = " FOR NONE"
 920        return f"WITH{no}{concurrent} ISOLATED LOADING{for_}"
 921
 922    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
 923        kind = expression.args.get("kind")
 924        this: str = f" {this}" if expression.this else ""
 925        for_or_in = expression.args.get("for_or_in")
 926        lock_type = expression.args.get("lock_type")
 927        override = " OVERRIDE" if expression.args.get("override") else ""
 928        return f"LOCKING {kind}{this} {for_or_in} {lock_type}{override}"
 929
 930    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
 931        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
 932        statistics = expression.args.get("statistics")
 933        statistics_sql = ""
 934        if statistics is not None:
 935            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
 936        return f"{data_sql}{statistics_sql}"
 937
 938    def insert_sql(self, expression: exp.Insert) -> str:
 939        overwrite = expression.args.get("overwrite")
 940
 941        if isinstance(expression.this, exp.Directory):
 942            this = "OVERWRITE " if overwrite else "INTO "
 943        else:
 944            this = "OVERWRITE TABLE " if overwrite else "INTO "
 945
 946        alternative = expression.args.get("alternative")
 947        alternative = f" OR {alternative} " if alternative else " "
 948        this = f"{this}{self.sql(expression, 'this')}"
 949
 950        exists = " IF EXISTS " if expression.args.get("exists") else " "
 951        partition_sql = (
 952            self.sql(expression, "partition") if expression.args.get("partition") else ""
 953        )
 954        expression_sql = self.sql(expression, "expression")
 955        returning = self.sql(expression, "returning")
 956        sep = self.sep() if partition_sql else ""
 957        sql = f"INSERT{alternative}{this}{exists}{partition_sql}{sep}{expression_sql}{returning}"
 958        return self.prepend_ctes(expression, sql)
 959
 960    def intersect_sql(self, expression: exp.Intersect) -> str:
 961        return self.prepend_ctes(
 962            expression,
 963            self.set_operation(expression, self.intersect_op(expression)),
 964        )
 965
 966    def intersect_op(self, expression: exp.Intersect) -> str:
 967        return f"INTERSECT{'' if expression.args.get('distinct') else ' ALL'}"
 968
 969    def introducer_sql(self, expression: exp.Introducer) -> str:
 970        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
 971
 972    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
 973        return expression.name.upper()
 974
 975    def returning_sql(self, expression: exp.Returning) -> str:
 976        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
 977
 978    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
 979        fields = expression.args.get("fields")
 980        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
 981        escaped = expression.args.get("escaped")
 982        escaped = f" ESCAPED BY {escaped}" if escaped else ""
 983        items = expression.args.get("collection_items")
 984        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
 985        keys = expression.args.get("map_keys")
 986        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
 987        lines = expression.args.get("lines")
 988        lines = f" LINES TERMINATED BY {lines}" if lines else ""
 989        null = expression.args.get("null")
 990        null = f" NULL DEFINED AS {null}" if null else ""
 991        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
 992
 993    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
 994        table = ".".join(
 995            part
 996            for part in [
 997                self.sql(expression, "catalog"),
 998                self.sql(expression, "db"),
 999                self.sql(expression, "this"),
1000            ]
1001            if part
1002        )
1003
1004        alias = self.sql(expression, "alias")
1005        alias = f"{sep}{alias}" if alias else ""
1006        hints = self.expressions(expression, key="hints", sep=", ", flat=True)
1007        hints = f" WITH ({hints})" if hints else ""
1008        laterals = self.expressions(expression, key="laterals", sep="")
1009        joins = self.expressions(expression, key="joins", sep="")
1010        pivots = self.expressions(expression, key="pivots", sep="")
1011        system_time = expression.args.get("system_time")
1012        system_time = f" {self.sql(expression, 'system_time')}" if system_time else ""
1013
1014        return f"{table}{system_time}{alias}{hints}{laterals}{joins}{pivots}"
1015
1016    def tablesample_sql(self, expression: exp.TableSample, seed_prefix: str = "SEED") -> str:
1017        if self.alias_post_tablesample and expression.this.alias:
1018            this = self.sql(expression.this, "this")
1019            alias = f" AS {self.sql(expression.this, 'alias')}"
1020        else:
1021            this = self.sql(expression, "this")
1022            alias = ""
1023        method = self.sql(expression, "method")
1024        method = f"{method.upper()} " if method else ""
1025        numerator = self.sql(expression, "bucket_numerator")
1026        denominator = self.sql(expression, "bucket_denominator")
1027        field = self.sql(expression, "bucket_field")
1028        field = f" ON {field}" if field else ""
1029        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
1030        percent = self.sql(expression, "percent")
1031        percent = f"{percent} PERCENT" if percent else ""
1032        rows = self.sql(expression, "rows")
1033        rows = f"{rows} ROWS" if rows else ""
1034        size = self.sql(expression, "size")
1035        seed = self.sql(expression, "seed")
1036        seed = f" {seed_prefix} ({seed})" if seed else ""
1037        kind = expression.args.get("kind", "TABLESAMPLE")
1038        return f"{this} {kind} {method}({bucket}{percent}{rows}{size}){seed}{alias}"
1039
1040    def pivot_sql(self, expression: exp.Pivot) -> str:
1041        this = self.sql(expression, "this")
1042        alias = self.sql(expression, "alias")
1043        alias = f" AS {alias}" if alias else ""
1044        unpivot = expression.args.get("unpivot")
1045        direction = "UNPIVOT" if unpivot else "PIVOT"
1046        expressions = self.expressions(expression, key="expressions")
1047        field = self.sql(expression, "field")
1048        return f"{this} {direction}({expressions} FOR {field}){alias}"
1049
1050    def tuple_sql(self, expression: exp.Tuple) -> str:
1051        return f"({self.expressions(expression, flat=True)})"
1052
1053    def update_sql(self, expression: exp.Update) -> str:
1054        this = self.sql(expression, "this")
1055        set_sql = self.expressions(expression, flat=True)
1056        from_sql = self.sql(expression, "from")
1057        where_sql = self.sql(expression, "where")
1058        returning = self.sql(expression, "returning")
1059        sql = f"UPDATE {this} SET {set_sql}{from_sql}{where_sql}{returning}"
1060        return self.prepend_ctes(expression, sql)
1061
1062    def values_sql(self, expression: exp.Values) -> str:
1063        args = self.expressions(expression)
1064        alias = self.sql(expression, "alias")
1065        values = f"VALUES{self.seg('')}{args}"
1066        values = (
1067            f"({values})"
1068            if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From))
1069            else values
1070        )
1071        return f"{values} AS {alias}" if alias else values
1072
1073    def var_sql(self, expression: exp.Var) -> str:
1074        return self.sql(expression, "this")
1075
1076    def into_sql(self, expression: exp.Into) -> str:
1077        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1078        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
1079        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
1080
1081    def from_sql(self, expression: exp.From) -> str:
1082        expressions = self.expressions(expression, flat=True)
1083        return f"{self.seg('FROM')} {expressions}"
1084
1085    def group_sql(self, expression: exp.Group) -> str:
1086        group_by = self.op_expressions("GROUP BY", expression)
1087        grouping_sets = self.expressions(expression, key="grouping_sets", indent=False)
1088        grouping_sets = (
1089            f"{self.seg('GROUPING SETS')} {self.wrap(grouping_sets)}" if grouping_sets else ""
1090        )
1091
1092        cube = expression.args.get("cube", [])
1093        if seq_get(cube, 0) is True:
1094            return f"{group_by}{self.seg('WITH CUBE')}"
1095        else:
1096            cube_sql = self.expressions(expression, key="cube", indent=False)
1097            cube_sql = f"{self.seg('CUBE')} {self.wrap(cube_sql)}" if cube_sql else ""
1098
1099        rollup = expression.args.get("rollup", [])
1100        if seq_get(rollup, 0) is True:
1101            return f"{group_by}{self.seg('WITH ROLLUP')}"
1102        else:
1103            rollup_sql = self.expressions(expression, key="rollup", indent=False)
1104            rollup_sql = f"{self.seg('ROLLUP')} {self.wrap(rollup_sql)}" if rollup_sql else ""
1105
1106        groupings = csv(grouping_sets, cube_sql, rollup_sql, sep=",")
1107
1108        if expression.args.get("expressions") and groupings:
1109            group_by = f"{group_by},"
1110
1111        return f"{group_by}{groupings}"
1112
1113    def having_sql(self, expression: exp.Having) -> str:
1114        this = self.indent(self.sql(expression, "this"))
1115        return f"{self.seg('HAVING')}{self.sep()}{this}"
1116
1117    def join_sql(self, expression: exp.Join) -> str:
1118        op_sql = self.seg(
1119            " ".join(
1120                op
1121                for op in (
1122                    "NATURAL" if expression.args.get("natural") else None,
1123                    expression.side,
1124                    expression.kind,
1125                    "JOIN",
1126                )
1127                if op
1128            )
1129        )
1130        on_sql = self.sql(expression, "on")
1131        using = expression.args.get("using")
1132
1133        if not on_sql and using:
1134            on_sql = csv(*(self.sql(column) for column in using))
1135
1136        if on_sql:
1137            on_sql = self.indent(on_sql, skip_first=True)
1138            space = self.seg(" " * self.pad) if self.pretty else " "
1139            if using:
1140                on_sql = f"{space}USING ({on_sql})"
1141            else:
1142                on_sql = f"{space}ON {on_sql}"
1143
1144        expression_sql = self.sql(expression, "expression")
1145        this_sql = self.sql(expression, "this")
1146        return f"{expression_sql}{op_sql} {this_sql}{on_sql}"
1147
1148    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str:
1149        args = self.expressions(expression, flat=True)
1150        args = f"({args})" if len(args.split(",")) > 1 else args
1151        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
1152
1153    def lateral_sql(self, expression: exp.Lateral) -> str:
1154        this = self.sql(expression, "this")
1155
1156        if isinstance(expression.this, exp.Subquery):
1157            return f"LATERAL {this}"
1158
1159        if expression.args.get("view"):
1160            alias = expression.args["alias"]
1161            columns = self.expressions(alias, key="columns", flat=True)
1162            table = f" {alias.name}" if alias.name else ""
1163            columns = f" AS {columns}" if columns else ""
1164            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
1165            return f"{op_sql}{self.sep()}{this}{table}{columns}"
1166
1167        alias = self.sql(expression, "alias")
1168        alias = f" AS {alias}" if alias else ""
1169        return f"LATERAL {this}{alias}"
1170
1171    def limit_sql(self, expression: exp.Limit) -> str:
1172        this = self.sql(expression, "this")
1173        return f"{this}{self.seg('LIMIT')} {self.sql(expression, 'expression')}"
1174
1175    def offset_sql(self, expression: exp.Offset) -> str:
1176        this = self.sql(expression, "this")
1177        return f"{this}{self.seg('OFFSET')} {self.sql(expression, 'expression')}"
1178
1179    def setitem_sql(self, expression: exp.SetItem) -> str:
1180        kind = self.sql(expression, "kind")
1181        kind = f"{kind} " if kind else ""
1182        this = self.sql(expression, "this")
1183        expressions = self.expressions(expression)
1184        collate = self.sql(expression, "collate")
1185        collate = f" COLLATE {collate}" if collate else ""
1186        global_ = "GLOBAL " if expression.args.get("global") else ""
1187        return f"{global_}{kind}{this}{expressions}{collate}"
1188
1189    def set_sql(self, expression: exp.Set) -> str:
1190        expressions = (
1191            f" {self.expressions(expression, flat=True)}" if expression.expressions else ""
1192        )
1193        return f"SET{expressions}"
1194
1195    def lock_sql(self, expression: exp.Lock) -> str:
1196        if self.LOCKING_READS_SUPPORTED:
1197            lock_type = "UPDATE" if expression.args["update"] else "SHARE"
1198            return self.seg(f"FOR {lock_type}")
1199
1200        self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
1201        return ""
1202
1203    def literal_sql(self, expression: exp.Literal) -> str:
1204        text = expression.this or ""
1205        if expression.is_string:
1206            text = text.replace(self.quote_end, self._escaped_quote_end)
1207            if self.pretty:
1208                text = text.replace("\n", self.SENTINEL_LINE_BREAK)
1209            text = f"{self.quote_start}{text}{self.quote_end}"
1210        return text
1211
1212    def loaddata_sql(self, expression: exp.LoadData) -> str:
1213        local = " LOCAL" if expression.args.get("local") else ""
1214        inpath = f" INPATH {self.sql(expression, 'inpath')}"
1215        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
1216        this = f" INTO TABLE {self.sql(expression, 'this')}"
1217        partition = self.sql(expression, "partition")
1218        partition = f" {partition}" if partition else ""
1219        input_format = self.sql(expression, "input_format")
1220        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
1221        serde = self.sql(expression, "serde")
1222        serde = f" SERDE {serde}" if serde else ""
1223        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
1224
1225    def null_sql(self, *_) -> str:
1226        return "NULL"
1227
1228    def boolean_sql(self, expression: exp.Boolean) -> str:
1229        return "TRUE" if expression.this else "FALSE"
1230
1231    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
1232        this = self.sql(expression, "this")
1233        this = f"{this} " if this else this
1234        return self.op_expressions(f"{this}ORDER BY", expression, flat=this or flat)  # type: ignore
1235
1236    def cluster_sql(self, expression: exp.Cluster) -> str:
1237        return self.op_expressions("CLUSTER BY", expression)
1238
1239    def distribute_sql(self, expression: exp.Distribute) -> str:
1240        return self.op_expressions("DISTRIBUTE BY", expression)
1241
1242    def sort_sql(self, expression: exp.Sort) -> str:
1243        return self.op_expressions("SORT BY", expression)
1244
1245    def ordered_sql(self, expression: exp.Ordered) -> str:
1246        desc = expression.args.get("desc")
1247        asc = not desc
1248
1249        nulls_first = expression.args.get("nulls_first")
1250        nulls_last = not nulls_first
1251        nulls_are_large = self.null_ordering == "nulls_are_large"
1252        nulls_are_small = self.null_ordering == "nulls_are_small"
1253        nulls_are_last = self.null_ordering == "nulls_are_last"
1254
1255        sort_order = " DESC" if desc else ""
1256        nulls_sort_change = ""
1257        if nulls_first and (
1258            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
1259        ):
1260            nulls_sort_change = " NULLS FIRST"
1261        elif (
1262            nulls_last
1263            and ((asc and nulls_are_small) or (desc and nulls_are_large))
1264            and not nulls_are_last
1265        ):
1266            nulls_sort_change = " NULLS LAST"
1267
1268        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
1269            self.unsupported(
1270                "Sorting in an ORDER BY on NULLS FIRST/NULLS LAST is not supported by this dialect"
1271            )
1272            nulls_sort_change = ""
1273
1274        return f"{self.sql(expression, 'this')}{sort_order}{nulls_sort_change}"
1275
1276    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
1277        partition = self.partition_by_sql(expression)
1278        order = self.sql(expression, "order")
1279        measures = self.sql(expression, "measures")
1280        measures = self.seg(f"MEASURES {measures}") if measures else ""
1281        rows = self.sql(expression, "rows")
1282        rows = self.seg(rows) if rows else ""
1283        after = self.sql(expression, "after")
1284        after = self.seg(after) if after else ""
1285        pattern = self.sql(expression, "pattern")
1286        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
1287        define = self.sql(expression, "define")
1288        define = self.seg(f"DEFINE {define}") if define else ""
1289        body = "".join(
1290            (
1291                partition,
1292                order,
1293                measures,
1294                rows,
1295                after,
1296                pattern,
1297                define,
1298            )
1299        )
1300        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}"
1301
1302    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
1303        return csv(
1304            *sqls,
1305            *[self.sql(sql) for sql in expression.args.get("joins") or []],
1306            self.sql(expression, "match"),
1307            *[self.sql(sql) for sql in expression.args.get("laterals") or []],
1308            self.sql(expression, "where"),
1309            self.sql(expression, "group"),
1310            self.sql(expression, "having"),
1311            self.sql(expression, "qualify"),
1312            self.seg("WINDOW ") + self.expressions(expression, "windows", flat=True)
1313            if expression.args.get("windows")
1314            else "",
1315            self.sql(expression, "distribute"),
1316            self.sql(expression, "sort"),
1317            self.sql(expression, "cluster"),
1318            self.sql(expression, "order"),
1319            self.sql(expression, "limit"),
1320            self.sql(expression, "offset"),
1321            self.sql(expression, "lock"),
1322            self.sql(expression, "sample"),
1323            sep="",
1324        )
1325
1326    def select_sql(self, expression: exp.Select) -> str:
1327        hint = self.sql(expression, "hint")
1328        distinct = self.sql(expression, "distinct")
1329        distinct = f" {distinct}" if distinct else ""
1330        expressions = self.expressions(expression)
1331        expressions = f"{self.sep()}{expressions}" if expressions else expressions
1332        sql = self.query_modifiers(
1333            expression,
1334            f"SELECT{hint}{distinct}{expressions}",
1335            self.sql(expression, "into", comment=False),
1336            self.sql(expression, "from", comment=False),
1337        )
1338        return self.prepend_ctes(expression, sql)
1339
1340    def schema_sql(self, expression: exp.Schema) -> str:
1341        this = self.sql(expression, "this")
1342        this = f"{this} " if this else ""
1343        sql = f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
1344        return f"{this}{sql}"
1345
1346    def star_sql(self, expression: exp.Star) -> str:
1347        except_ = self.expressions(expression, key="except", flat=True)
1348        except_ = f"{self.seg(self.STAR_MAPPING['except'])} ({except_})" if except_ else ""
1349        replace = self.expressions(expression, key="replace", flat=True)
1350        replace = f"{self.seg(self.STAR_MAPPING['replace'])} ({replace})" if replace else ""
1351        return f"*{except_}{replace}"
1352
1353    def structkwarg_sql(self, expression: exp.StructKwarg) -> str:
1354        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
1355
1356    def parameter_sql(self, expression: exp.Parameter) -> str:
1357        this = self.sql(expression, "this")
1358        this = f"{{{this}}}" if expression.args.get("wrapped") else f"{this}"
1359        return f"{self.PARAMETER_TOKEN}{this}"
1360
1361    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
1362        this = self.sql(expression, "this")
1363        kind = expression.text("kind")
1364        if kind:
1365            kind = f"{kind}."
1366        return f"@@{kind}{this}"
1367
1368    def placeholder_sql(self, expression: exp.Placeholder) -> str:
1369        return f":{expression.name}" if expression.name else "?"
1370
1371    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
1372        alias = self.sql(expression, "alias")
1373        alias = f"{sep}{alias}" if alias else ""
1374
1375        sql = self.query_modifiers(
1376            expression,
1377            self.wrap(expression),
1378            alias,
1379            self.expressions(expression, key="pivots", sep=" "),
1380        )
1381
1382        return self.prepend_ctes(expression, sql)
1383
1384    def qualify_sql(self, expression: exp.Qualify) -> str:
1385        this = self.indent(self.sql(expression, "this"))
1386        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
1387
1388    def union_sql(self, expression: exp.Union) -> str:
1389        return self.prepend_ctes(
1390            expression,
1391            self.set_operation(expression, self.union_op(expression)),
1392        )
1393
1394    def union_op(self, expression: exp.Union) -> str:
1395        kind = " DISTINCT" if self.EXPLICIT_UNION else ""
1396        kind = kind if expression.args.get("distinct") else " ALL"
1397        return f"UNION{kind}"
1398
1399    def unnest_sql(self, expression: exp.Unnest) -> str:
1400        args = self.expressions(expression, flat=True)
1401        alias = expression.args.get("alias")
1402        if alias and self.unnest_column_only:
1403            columns = alias.columns
1404            alias = self.sql(columns[0]) if columns else ""
1405        else:
1406            alias = self.sql(expression, "alias")
1407        alias = f" AS {alias}" if alias else alias
1408        ordinality = " WITH ORDINALITY" if expression.args.get("ordinality") else ""
1409        offset = expression.args.get("offset")
1410        offset = f" WITH OFFSET AS {self.sql(offset)}" if offset else ""
1411        return f"UNNEST({args}){ordinality}{alias}{offset}"
1412
1413    def where_sql(self, expression: exp.Where) -> str:
1414        this = self.indent(self.sql(expression, "this"))
1415        return f"{self.seg('WHERE')}{self.sep()}{this}"
1416
1417    def window_sql(self, expression: exp.Window) -> str:
1418        this = self.sql(expression, "this")
1419
1420        partition = self.partition_by_sql(expression)
1421
1422        order = expression.args.get("order")
1423        order_sql = self.order_sql(order, flat=True) if order else ""
1424
1425        partition_sql = partition + " " if partition and order else partition
1426
1427        spec = expression.args.get("spec")
1428        spec_sql = " " + self.window_spec_sql(spec) if spec else ""
1429
1430        alias = self.sql(expression, "alias")
1431        this = f"{this} {'AS' if expression.arg_key == 'windows' else 'OVER'}"
1432
1433        if not partition and not order and not spec and alias:
1434            return f"{this} {alias}"
1435
1436        window_args = alias + partition_sql + order_sql + spec_sql
1437
1438        return f"{this} ({window_args.strip()})"
1439
1440    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
1441        partition = self.expressions(expression, key="partition_by", flat=True)
1442        return f"PARTITION BY {partition}" if partition else ""
1443
1444    def window_spec_sql(self, expression: exp.WindowSpec) -> str:
1445        kind = self.sql(expression, "kind")
1446        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
1447        end = (
1448            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
1449            or "CURRENT ROW"
1450        )
1451        return f"{kind} BETWEEN {start} AND {end}"
1452
1453    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
1454        this = self.sql(expression, "this")
1455        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
1456        return f"{this} WITHIN GROUP ({expression_sql})"
1457
1458    def between_sql(self, expression: exp.Between) -> str:
1459        this = self.sql(expression, "this")
1460        low = self.sql(expression, "low")
1461        high = self.sql(expression, "high")
1462        return f"{this} BETWEEN {low} AND {high}"
1463
1464    def bracket_sql(self, expression: exp.Bracket) -> str:
1465        expressions = apply_index_offset(expression.expressions, self.index_offset)
1466        expressions_sql = ", ".join(self.sql(e) for e in expressions)
1467
1468        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
1469
1470    def all_sql(self, expression: exp.All) -> str:
1471        return f"ALL {self.wrap(expression)}"
1472
1473    def any_sql(self, expression: exp.Any) -> str:
1474        this = self.sql(expression, "this")
1475        if isinstance(expression.this, exp.Subqueryable):
1476            this = self.wrap(this)
1477        return f"ANY {this}"
1478
1479    def exists_sql(self, expression: exp.Exists) -> str:
1480        return f"EXISTS{self.wrap(expression)}"
1481
1482    def case_sql(self, expression: exp.Case) -> str:
1483        this = self.sql(expression, "this")
1484        statements = [f"CASE {this}" if this else "CASE"]
1485
1486        for e in expression.args["ifs"]:
1487            statements.append(f"WHEN {self.sql(e, 'this')}")
1488            statements.append(f"THEN {self.sql(e, 'true')}")
1489
1490        default = self.sql(expression, "default")
1491
1492        if default:
1493            statements.append(f"ELSE {default}")
1494
1495        statements.append("END")
1496
1497        if self.pretty and self.text_width(statements) > self._max_text_width:
1498            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
1499
1500        return " ".join(statements)
1501
1502    def constraint_sql(self, expression: exp.Constraint) -> str:
1503        this = self.sql(expression, "this")
1504        expressions = self.expressions(expression, flat=True)
1505        return f"CONSTRAINT {this} {expressions}"
1506
1507    def extract_sql(self, expression: exp.Extract) -> str:
1508        this = self.sql(expression, "this")
1509        expression_sql = self.sql(expression, "expression")
1510        return f"EXTRACT({this} FROM {expression_sql})"
1511
1512    def trim_sql(self, expression: exp.Trim) -> str:
1513        trim_type = self.sql(expression, "position")
1514
1515        if trim_type == "LEADING":
1516            return self.func("LTRIM", expression.this)
1517        elif trim_type == "TRAILING":
1518            return self.func("RTRIM", expression.this)
1519        else:
1520            return self.func("TRIM", expression.this, expression.expression)
1521
1522    def concat_sql(self, expression: exp.Concat) -> str:
1523        if len(expression.expressions) == 1:
1524            return self.sql(expression.expressions[0])
1525        return self.function_fallback_sql(expression)
1526
1527    def check_sql(self, expression: exp.Check) -> str:
1528        this = self.sql(expression, key="this")
1529        return f"CHECK ({this})"
1530
1531    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
1532        expressions = self.expressions(expression, flat=True)
1533        reference = self.sql(expression, "reference")
1534        reference = f" {reference}" if reference else ""
1535        delete = self.sql(expression, "delete")
1536        delete = f" ON DELETE {delete}" if delete else ""
1537        update = self.sql(expression, "update")
1538        update = f" ON UPDATE {update}" if update else ""
1539        return f"FOREIGN KEY ({expressions}){reference}{delete}{update}"
1540
1541    def primarykey_sql(self, expression: exp.ForeignKey) -> str:
1542        expressions = self.expressions(expression, flat=True)
1543        options = self.expressions(expression, "options", flat=True, sep=" ")
1544        options = f" {options}" if options else ""
1545        return f"PRIMARY KEY ({expressions}){options}"
1546
1547    def unique_sql(self, expression: exp.Unique) -> str:
1548        columns = self.expressions(expression, key="expressions")
1549        return f"UNIQUE ({columns})"
1550
1551    def if_sql(self, expression: exp.If) -> str:
1552        return self.case_sql(
1553            exp.Case(ifs=[expression.copy()], default=expression.args.get("false"))
1554        )
1555
1556    def in_sql(self, expression: exp.In) -> str:
1557        query = expression.args.get("query")
1558        unnest = expression.args.get("unnest")
1559        field = expression.args.get("field")
1560        is_global = " GLOBAL" if expression.args.get("is_global") else ""
1561
1562        if query:
1563            in_sql = self.wrap(query)
1564        elif unnest:
1565            in_sql = self.in_unnest_op(unnest)
1566        elif field:
1567            in_sql = self.sql(field)
1568        else:
1569            in_sql = f"({self.expressions(expression, flat=True)})"
1570
1571        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
1572
1573    def in_unnest_op(self, unnest: exp.Unnest) -> str:
1574        return f"(SELECT {self.sql(unnest)})"
1575
1576    def interval_sql(self, expression: exp.Interval) -> str:
1577        this = expression.args.get("this")
1578        if this:
1579            this = (
1580                f" {this}"
1581                if isinstance(this, exp.Literal) or isinstance(this, exp.Paren)
1582                else f" ({this})"
1583            )
1584        else:
1585            this = ""
1586        unit = self.sql(expression, "unit")
1587        unit = f" {unit}" if unit else ""
1588        return f"INTERVAL{this}{unit}"
1589
1590    def return_sql(self, expression: exp.Return) -> str:
1591        return f"RETURN {self.sql(expression, 'this')}"
1592
1593    def reference_sql(self, expression: exp.Reference) -> str:
1594        this = self.sql(expression, "this")
1595        expressions = self.expressions(expression, flat=True)
1596        expressions = f"({expressions})" if expressions else ""
1597        options = self.expressions(expression, "options", flat=True, sep=" ")
1598        options = f" {options}" if options else ""
1599        return f"REFERENCES {this}{expressions}{options}"
1600
1601    def anonymous_sql(self, expression: exp.Anonymous) -> str:
1602        return self.func(expression.name, *expression.expressions)
1603
1604    def paren_sql(self, expression: exp.Paren) -> str:
1605        if isinstance(expression.unnest(), exp.Select):
1606            sql = self.wrap(expression)
1607        else:
1608            sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
1609            sql = f"({sql}{self.seg(')', sep='')}"
1610
1611        return self.prepend_ctes(expression, sql)
1612
1613    def neg_sql(self, expression: exp.Neg) -> str:
1614        # This makes sure we don't convert "- - 5" to "--5", which is a comment
1615        this_sql = self.sql(expression, "this")
1616        sep = " " if this_sql[0] == "-" else ""
1617        return f"-{sep}{this_sql}"
1618
1619    def not_sql(self, expression: exp.Not) -> str:
1620        return f"NOT {self.sql(expression, 'this')}"
1621
1622    def alias_sql(self, expression: exp.Alias) -> str:
1623        to_sql = self.sql(expression, "alias")
1624        to_sql = f" AS {to_sql}" if to_sql else ""
1625        return f"{self.sql(expression, 'this')}{to_sql}"
1626
1627    def aliases_sql(self, expression: exp.Aliases) -> str:
1628        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
1629
1630    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
1631        this = self.sql(expression, "this")
1632        zone = self.sql(expression, "zone")
1633        return f"{this} AT TIME ZONE {zone}"
1634
1635    def add_sql(self, expression: exp.Add) -> str:
1636        return self.binary(expression, "+")
1637
1638    def and_sql(self, expression: exp.And) -> str:
1639        return self.connector_sql(expression, "AND")
1640
1641    def connector_sql(self, expression: exp.Connector, op: str) -> str:
1642        if not self.pretty:
1643            return self.binary(expression, op)
1644
1645        sqls = tuple(self.sql(e) for e in expression.flatten(unnest=False))
1646        sep = "\n" if self.text_width(sqls) > self._max_text_width else " "
1647        return f"{sep}{op} ".join(sqls)
1648
1649    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
1650        return self.binary(expression, "&")
1651
1652    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
1653        return self.binary(expression, "<<")
1654
1655    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
1656        return f"~{self.sql(expression, 'this')}"
1657
1658    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
1659        return self.binary(expression, "|")
1660
1661    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
1662        return self.binary(expression, ">>")
1663
1664    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
1665        return self.binary(expression, "^")
1666
1667    def cast_sql(self, expression: exp.Cast) -> str:
1668        return f"CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')})"
1669
1670    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
1671        zone = self.sql(expression, "this")
1672        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
1673
1674    def collate_sql(self, expression: exp.Collate) -> str:
1675        return self.binary(expression, "COLLATE")
1676
1677    def command_sql(self, expression: exp.Command) -> str:
1678        return f"{self.sql(expression, 'this').upper()} {expression.text('expression').strip()}"
1679
1680    def comment_sql(self, expression: exp.Comment) -> str:
1681        this = self.sql(expression, "this")
1682        kind = expression.args["kind"]
1683        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1684        expression_sql = self.sql(expression, "expression")
1685        return f"COMMENT{exists_sql}ON {kind} {this} IS {expression_sql}"
1686
1687    def transaction_sql(self, expression: exp.Transaction) -> str:
1688        return "BEGIN"
1689
1690    def commit_sql(self, expression: exp.Commit) -> str:
1691        chain = expression.args.get("chain")
1692        if chain is not None:
1693            chain = " AND CHAIN" if chain else " AND NO CHAIN"
1694
1695        return f"COMMIT{chain or ''}"
1696
1697    def rollback_sql(self, expression: exp.Rollback) -> str:
1698        savepoint = expression.args.get("savepoint")
1699        savepoint = f" TO {savepoint}" if savepoint else ""
1700        return f"ROLLBACK{savepoint}"
1701
1702    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
1703        this = self.sql(expression, "this")
1704
1705        dtype = self.sql(expression, "dtype")
1706        if dtype:
1707            collate = self.sql(expression, "collate")
1708            collate = f" COLLATE {collate}" if collate else ""
1709            using = self.sql(expression, "using")
1710            using = f" USING {using}" if using else ""
1711            return f"ALTER COLUMN {this} TYPE {dtype}{collate}{using}"
1712
1713        default = self.sql(expression, "default")
1714        if default:
1715            return f"ALTER COLUMN {this} SET DEFAULT {default}"
1716
1717        if not expression.args.get("drop"):
1718            self.unsupported("Unsupported ALTER COLUMN syntax")
1719
1720        return f"ALTER COLUMN {this} DROP DEFAULT"
1721
1722    def renametable_sql(self, expression: exp.RenameTable) -> str:
1723        this = self.sql(expression, "this")
1724        return f"RENAME TO {this}"
1725
1726    def altertable_sql(self, expression: exp.AlterTable) -> str:
1727        actions = expression.args["actions"]
1728
1729        if isinstance(actions[0], exp.ColumnDef):
1730            actions = self.expressions(expression, "actions", prefix="ADD COLUMN ")
1731        elif isinstance(actions[0], exp.Schema):
1732            actions = self.expressions(expression, "actions", prefix="ADD COLUMNS ")
1733        elif isinstance(actions[0], exp.Delete):
1734            actions = self.expressions(expression, "actions", flat=True)
1735        else:
1736            actions = self.expressions(expression, "actions")
1737
1738        exists = " IF EXISTS" if expression.args.get("exists") else ""
1739        return f"ALTER TABLE{exists} {self.sql(expression, 'this')} {actions}"
1740
1741    def droppartition_sql(self, expression: exp.DropPartition) -> str:
1742        expressions = self.expressions(expression)
1743        exists = " IF EXISTS " if expression.args.get("exists") else " "
1744        return f"DROP{exists}{expressions}"
1745
1746    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
1747        this = self.sql(expression, "this")
1748        expression_ = self.sql(expression, "expression")
1749        add_constraint = f"ADD CONSTRAINT {this}" if this else "ADD"
1750
1751        enforced = expression.args.get("enforced")
1752        if enforced is not None:
1753            return f"{add_constraint} CHECK ({expression_}){' ENFORCED' if enforced else ''}"
1754
1755        return f"{add_constraint} {expression_}"
1756
1757    def distinct_sql(self, expression: exp.Distinct) -> str:
1758        this = self.expressions(expression, flat=True)
1759        this = f" {this}" if this else ""
1760
1761        on = self.sql(expression, "on")
1762        on = f" ON {on}" if on else ""
1763        return f"DISTINCT{this}{on}"
1764
1765    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
1766        return f"{self.sql(expression, 'this')} IGNORE NULLS"
1767
1768    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
1769        return f"{self.sql(expression, 'this')} RESPECT NULLS"
1770
1771    def intdiv_sql(self, expression: exp.IntDiv) -> str:
1772        return self.sql(
1773            exp.Cast(
1774                this=exp.Div(this=expression.this, expression=expression.expression),
1775                to=exp.DataType(this=exp.DataType.Type.INT),
1776            )
1777        )
1778
1779    def dpipe_sql(self, expression: exp.DPipe) -> str:
1780        return self.binary(expression, "||")
1781
1782    def div_sql(self, expression: exp.Div) -> str:
1783        return self.binary(expression, "/")
1784
1785    def overlaps_sql(self, expression: exp.Overlaps) -> str:
1786        return self.binary(expression, "OVERLAPS")
1787
1788    def distance_sql(self, expression: exp.Distance) -> str:
1789        return self.binary(expression, "<->")
1790
1791    def dot_sql(self, expression: exp.Dot) -> str:
1792        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
1793
1794    def eq_sql(self, expression: exp.EQ) -> str:
1795        return self.binary(expression, "=")
1796
1797    def escape_sql(self, expression: exp.Escape) -> str:
1798        return self.binary(expression, "ESCAPE")
1799
1800    def glob_sql(self, expression: exp.Glob) -> str:
1801        return self.binary(expression, "GLOB")
1802
1803    def gt_sql(self, expression: exp.GT) -> str:
1804        return self.binary(expression, ">")
1805
1806    def gte_sql(self, expression: exp.GTE) -> str:
1807        return self.binary(expression, ">=")
1808
1809    def ilike_sql(self, expression: exp.ILike) -> str:
1810        return self.binary(expression, "ILIKE")
1811
1812    def is_sql(self, expression: exp.Is) -> str:
1813        return self.binary(expression, "IS")
1814
1815    def like_sql(self, expression: exp.Like) -> str:
1816        return self.binary(expression, "LIKE")
1817
1818    def similarto_sql(self, expression: exp.SimilarTo) -> str:
1819        return self.binary(expression, "SIMILAR TO")
1820
1821    def lt_sql(self, expression: exp.LT) -> str:
1822        return self.binary(expression, "<")
1823
1824    def lte_sql(self, expression: exp.LTE) -> str:
1825        return self.binary(expression, "<=")
1826
1827    def mod_sql(self, expression: exp.Mod) -> str:
1828        return self.binary(expression, "%")
1829
1830    def mul_sql(self, expression: exp.Mul) -> str:
1831        return self.binary(expression, "*")
1832
1833    def neq_sql(self, expression: exp.NEQ) -> str:
1834        return self.binary(expression, "<>")
1835
1836    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
1837        return self.binary(expression, "IS NOT DISTINCT FROM")
1838
1839    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
1840        return self.binary(expression, "IS DISTINCT FROM")
1841
1842    def or_sql(self, expression: exp.Or) -> str:
1843        return self.connector_sql(expression, "OR")
1844
1845    def slice_sql(self, expression: exp.Slice) -> str:
1846        return self.binary(expression, ":")
1847
1848    def sub_sql(self, expression: exp.Sub) -> str:
1849        return self.binary(expression, "-")
1850
1851    def trycast_sql(self, expression: exp.TryCast) -> str:
1852        return f"TRY_CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')})"
1853
1854    def use_sql(self, expression: exp.Use) -> str:
1855        kind = self.sql(expression, "kind")
1856        kind = f" {kind}" if kind else ""
1857        this = self.sql(expression, "this")
1858        this = f" {this}" if this else ""
1859        return f"USE{kind}{this}"
1860
1861    def binary(self, expression: exp.Binary, op: str) -> str:
1862        return f"{self.sql(expression, 'this')} {op} {self.sql(expression, 'expression')}"
1863
1864    def function_fallback_sql(self, expression: exp.Func) -> str:
1865        args = []
1866        for arg_value in expression.args.values():
1867            if isinstance(arg_value, list):
1868                for value in arg_value:
1869                    args.append(value)
1870            else:
1871                args.append(arg_value)
1872
1873        return self.func(expression.sql_name(), *args)
1874
1875    def func(self, name: str, *args: t.Optional[exp.Expression | str]) -> str:
1876        return f"{self.normalize_func(name)}({self.format_args(*args)})"
1877
1878    def format_args(self, *args: t.Optional[str | exp.Expression]) -> str:
1879        arg_sqls = tuple(self.sql(arg) for arg in args if arg is not None)
1880        if self.pretty and self.text_width(arg_sqls) > self._max_text_width:
1881            return self.indent("\n" + f",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True)
1882        return ", ".join(arg_sqls)
1883
1884    def text_width(self, args: t.Iterable) -> int:
1885        return sum(len(arg) for arg in args)
1886
1887    def format_time(self, expression: exp.Expression) -> t.Optional[str]:
1888        return format_time(self.sql(expression, "format"), self.time_mapping, self.time_trie)
1889
1890    def expressions(
1891        self,
1892        expression: exp.Expression,
1893        key: t.Optional[str] = None,
1894        flat: bool = False,
1895        indent: bool = True,
1896        sep: str = ", ",
1897        prefix: str = "",
1898    ) -> str:
1899        expressions = expression.args.get(key or "expressions")
1900
1901        if not expressions:
1902            return ""
1903
1904        if flat:
1905            return sep.join(self.sql(e) for e in expressions)
1906
1907        num_sqls = len(expressions)
1908
1909        # These are calculated once in case we have the leading_comma / pretty option set, correspondingly
1910        pad = " " * self.pad
1911        stripped_sep = sep.strip()
1912
1913        result_sqls = []
1914        for i, e in enumerate(expressions):
1915            sql = self.sql(e, comment=False)
1916            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
1917
1918            if self.pretty:
1919                if self._leading_comma:
1920                    result_sqls.append(f"{sep if i > 0 else pad}{prefix}{sql}{comments}")
1921                else:
1922                    result_sqls.append(
1923                        f"{prefix}{sql}{stripped_sep if i + 1 < num_sqls else ''}{comments}"
1924                    )
1925            else:
1926                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
1927
1928        result_sql = "\n".join(result_sqls) if self.pretty else "".join(result_sqls)
1929        return self.indent(result_sql, skip_first=False) if indent else result_sql
1930
1931    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
1932        flat = flat or isinstance(expression.parent, exp.Properties)
1933        expressions_sql = self.expressions(expression, flat=flat)
1934        if flat:
1935            return f"{op} {expressions_sql}"
1936        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
1937
1938    def naked_property(self, expression: exp.Property) -> str:
1939        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
1940        if not property_name:
1941            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
1942        return f"{property_name} {self.sql(expression, 'this')}"
1943
1944    def set_operation(self, expression: exp.Expression, op: str) -> str:
1945        this = self.sql(expression, "this")
1946        op = self.seg(op)
1947        return self.query_modifiers(
1948            expression, f"{this}{op}{self.sep()}{self.sql(expression, 'expression')}"
1949        )
1950
1951    def tag_sql(self, expression: exp.Tag) -> str:
1952        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
1953
1954    def token_sql(self, token_type: TokenType) -> str:
1955        return self.TOKEN_MAPPING.get(token_type, token_type.name)
1956
1957    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
1958        this = self.sql(expression, "this")
1959        expressions = self.no_identify(self.expressions, expression)
1960        expressions = (
1961            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
1962        )
1963        return f"{this}{expressions}"
1964
1965    def joinhint_sql(self, expression: exp.JoinHint) -> str:
1966        this = self.sql(expression, "this")
1967        expressions = self.expressions(expression, flat=True)
1968        return f"{this}({expressions})"
1969
1970    def kwarg_sql(self, expression: exp.Kwarg) -> str:
1971        return self.binary(expression, "=>")
1972
1973    def when_sql(self, expression: exp.When) -> str:
1974        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
1975        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
1976        condition = self.sql(expression, "condition")
1977        condition = f" AND {condition}" if condition else ""
1978
1979        then_expression = expression.args.get("then")
1980        if isinstance(then_expression, exp.Insert):
1981            then = f"INSERT {self.sql(then_expression, 'this')}"
1982            if "expression" in then_expression.args:
1983                then += f" VALUES {self.sql(then_expression, 'expression')}"
1984        elif isinstance(then_expression, exp.Update):
1985            if isinstance(then_expression.args.get("expressions"), exp.Star):
1986                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
1987            else:
1988                then = f"UPDATE SET {self.expressions(then_expression, flat=True)}"
1989        else:
1990            then = self.sql(then_expression)
1991        return f"WHEN {matched}{source}{condition} THEN {then}"
1992
1993    def merge_sql(self, expression: exp.Merge) -> str:
1994        this = self.sql(expression, "this")
1995        using = f"USING {self.sql(expression, 'using')}"
1996        on = f"ON {self.sql(expression, 'on')}"
1997        return f"MERGE INTO {this} {using} {on} {self.expressions(expression, sep=' ')}"
1998
1999    def tochar_sql(self, expression: exp.ToChar) -> str:
2000        if expression.args.get("format"):
2001            self.unsupported("Format argument unsupported for TO_CHAR/TO_VARCHAR function")
2002
2003        return self.sql(exp.cast(expression.this, "text"))

Generator interprets the given syntax tree and produces a SQL string as an output.

Arguments:
  • time_mapping (dict): the dictionary of custom time mappings in which the key represents a python time format and the output the target time format
  • time_trie (trie): a trie of the time_mapping keys
  • pretty (bool): if set to True the returned string will be formatted. Default: False.
  • quote_start (str): specifies which starting character to use to delimit quotes. Default: '.
  • quote_end (str): specifies which ending character to use to delimit quotes. Default: '.
  • identifier_start (str): specifies which starting character to use to delimit identifiers. Default: ".
  • identifier_end (str): specifies which ending character to use to delimit identifiers. Default: ".
  • identify (bool | str): 'always': always quote, 'safe': quote identifiers if they don't contain an upcase, True defaults to always.
  • normalize (bool): if set to True all identifiers will lower cased
  • string_escape (str): specifies a string escape character. Default: '.
  • identifier_escape (str): specifies an identifier escape character. Default: ".
  • pad (int): determines padding in a formatted string. Default: 2.
  • indent (int): determines the size of indentation in a formatted string. Default: 4.
  • unnest_column_only (bool): if true unnest table aliases are considered only as column aliases
  • normalize_functions (str): normalize function names, "upper", "lower", or None Default: "upper"
  • alias_post_tablesample (bool): if the table alias comes after tablesample Default: False
  • unsupported_level (ErrorLevel): determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
  • null_ordering (str): Indicates the default null ordering method to use if not explicitly set. Options are "nulls_are_small", "nulls_are_large", "nulls_are_last". Default: "nulls_are_small"
  • max_unsupported (int): 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 (bool): if the the comma is leading or trailing in select statements 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 or not to preserve comments in the output SQL code. Default: True
Generator( time_mapping=None, time_trie=None, pretty=None, quote_start=None, quote_end=None, identifier_start=None, identifier_end=None, identify=False, normalize=False, string_escape=None, identifier_escape=None, pad=2, indent=2, index_offset=0, unnest_column_only=False, alias_post_tablesample=False, normalize_functions='upper', unsupported_level=<ErrorLevel.WARN: 'WARN'>, null_ordering=None, max_unsupported=3, leading_comma=False, max_text_width=80, comments=True)
215    def __init__(
216        self,
217        time_mapping=None,
218        time_trie=None,
219        pretty=None,
220        quote_start=None,
221        quote_end=None,
222        identifier_start=None,
223        identifier_end=None,
224        identify=False,
225        normalize=False,
226        string_escape=None,
227        identifier_escape=None,
228        pad=2,
229        indent=2,
230        index_offset=0,
231        unnest_column_only=False,
232        alias_post_tablesample=False,
233        normalize_functions="upper",
234        unsupported_level=ErrorLevel.WARN,
235        null_ordering=None,
236        max_unsupported=3,
237        leading_comma=False,
238        max_text_width=80,
239        comments=True,
240    ):
241        import sqlglot
242
243        self.time_mapping = time_mapping or {}
244        self.time_trie = time_trie
245        self.pretty = pretty if pretty is not None else sqlglot.pretty
246        self.quote_start = quote_start or "'"
247        self.quote_end = quote_end or "'"
248        self.identifier_start = identifier_start or '"'
249        self.identifier_end = identifier_end or '"'
250        self.identify = identify
251        self.normalize = normalize
252        self.string_escape = string_escape or "'"
253        self.identifier_escape = identifier_escape or '"'
254        self.pad = pad
255        self.index_offset = index_offset
256        self.unnest_column_only = unnest_column_only
257        self.alias_post_tablesample = alias_post_tablesample
258        self.normalize_functions = normalize_functions
259        self.unsupported_level = unsupported_level
260        self.unsupported_messages = []
261        self.max_unsupported = max_unsupported
262        self.null_ordering = null_ordering
263        self._indent = indent
264        self._escaped_quote_end = self.string_escape + self.quote_end
265        self._escaped_identifier_end = self.identifier_escape + self.identifier_end
266        self._leading_comma = leading_comma
267        self._max_text_width = max_text_width
268        self._comments = comments
def generate(self, expression: Optional[sqlglot.expressions.Expression]) -> str:
270    def generate(self, expression: t.Optional[exp.Expression]) -> str:
271        """
272        Generates a SQL string by interpreting the given syntax tree.
273
274        Args
275            expression: the syntax tree.
276
277        Returns
278            the SQL string.
279        """
280        self.unsupported_messages = []
281        sql = self.sql(expression).strip()
282
283        if self.unsupported_level == ErrorLevel.IGNORE:
284            return sql
285
286        if self.unsupported_level == ErrorLevel.WARN:
287            for msg in self.unsupported_messages:
288                logger.warning(msg)
289        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
290            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
291
292        if self.pretty:
293            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
294        return sql

Generates a SQL string by interpreting the given syntax tree.

Args expression: the syntax tree.

Returns the SQL string.

def unsupported(self, message: str) -> None:
296    def unsupported(self, message: str) -> None:
297        if self.unsupported_level == ErrorLevel.IMMEDIATE:
298            raise UnsupportedError(message)
299        self.unsupported_messages.append(message)
def sep(self, sep: str = ' ') -> str:
301    def sep(self, sep: str = " ") -> str:
302        return f"{sep.strip()}\n" if self.pretty else sep
def seg(self, sql: str, sep: str = ' ') -> str:
304    def seg(self, sql: str, sep: str = " ") -> str:
305        return f"{self.sep(sep)}{sql}"
def pad_comment(self, comment: str) -> str:
307    def pad_comment(self, comment: str) -> str:
308        comment = " " + comment if comment[0].strip() else comment
309        comment = comment + " " if comment[-1].strip() else comment
310        return comment
def maybe_comment(self, sql: str, expression: sqlglot.expressions.Expression) -> str:
312    def maybe_comment(self, sql: str, expression: exp.Expression) -> str:
313        comments = expression.comments if self._comments else None
314
315        if not comments:
316            return sql
317
318        sep = "\n" if self.pretty else " "
319        comments_sql = sep.join(
320            f"/*{self.pad_comment(comment)}*/" for comment in comments if comment
321        )
322
323        if not comments_sql:
324            return sql
325
326        if isinstance(expression, self.WITH_SEPARATED_COMMENTS):
327            return f"{comments_sql}{self.sep()}{sql}"
328
329        return f"{sql} {comments_sql}"
def wrap(self, expression: sqlglot.expressions.Expression | str) -> str:
331    def wrap(self, expression: exp.Expression | str) -> str:
332        this_sql = self.indent(
333            self.sql(expression)
334            if isinstance(expression, (exp.Select, exp.Union))
335            else self.sql(expression, "this"),
336            level=1,
337            pad=0,
338        )
339        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def no_identify(self, func: Callable[..., str], *args, **kwargs) -> str:
341    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
342        original = self.identify
343        self.identify = False
344        result = func(*args, **kwargs)
345        self.identify = original
346        return result
def normalize_func(self, name: str) -> str:
348    def normalize_func(self, name: str) -> str:
349        if self.normalize_functions == "upper":
350            return name.upper()
351        if self.normalize_functions == "lower":
352            return name.lower()
353        return name
def indent( self, sql: str, level: int = 0, pad: Optional[int] = None, skip_first: bool = False, skip_last: bool = False) -> str:
355    def indent(
356        self,
357        sql: str,
358        level: int = 0,
359        pad: t.Optional[int] = None,
360        skip_first: bool = False,
361        skip_last: bool = False,
362    ) -> str:
363        if not self.pretty:
364            return sql
365
366        pad = self.pad if pad is None else pad
367        lines = sql.split("\n")
368
369        return "\n".join(
370            line
371            if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
372            else f"{' ' * (level * self._indent + pad)}{line}"
373            for i, line in enumerate(lines)
374        )
def sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
376    def sql(
377        self,
378        expression: t.Optional[str | exp.Expression],
379        key: t.Optional[str] = None,
380        comment: bool = True,
381    ) -> str:
382        if not expression:
383            return ""
384
385        if isinstance(expression, str):
386            return expression
387
388        if key:
389            return self.sql(expression.args.get(key))
390
391        transform = self.TRANSFORMS.get(expression.__class__)
392
393        if callable(transform):
394            sql = transform(self, expression)
395        elif transform:
396            sql = transform
397        elif isinstance(expression, exp.Expression):
398            exp_handler_name = f"{expression.key}_sql"
399
400            if hasattr(self, exp_handler_name):
401                sql = getattr(self, exp_handler_name)(expression)
402            elif isinstance(expression, exp.Func):
403                sql = self.function_fallback_sql(expression)
404            elif isinstance(expression, exp.Property):
405                sql = self.property_sql(expression)
406            else:
407                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
408        else:
409            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
410
411        return self.maybe_comment(sql, expression) if self._comments and comment else sql
def uncache_sql(self, expression: sqlglot.expressions.Uncache) -> str:
413    def uncache_sql(self, expression: exp.Uncache) -> str:
414        table = self.sql(expression, "this")
415        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
416        return f"UNCACHE TABLE{exists_sql} {table}"
def cache_sql(self, expression: sqlglot.expressions.Cache) -> str:
418    def cache_sql(self, expression: exp.Cache) -> str:
419        lazy = " LAZY" if expression.args.get("lazy") else ""
420        table = self.sql(expression, "this")
421        options = expression.args.get("options")
422        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
423        sql = self.sql(expression, "expression")
424        sql = f" AS{self.sep()}{sql}" if sql else ""
425        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
426        return self.prepend_ctes(expression, sql)
def characterset_sql(self, expression: sqlglot.expressions.CharacterSet) -> str:
428    def characterset_sql(self, expression: exp.CharacterSet) -> str:
429        if isinstance(expression.parent, exp.Cast):
430            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
431        default = "DEFAULT " if expression.args.get("default") else ""
432        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
def column_sql(self, expression: sqlglot.expressions.Column) -> str:
434    def column_sql(self, expression: exp.Column) -> str:
435        return ".".join(
436            self.sql(part)
437            for part in (
438                expression.args.get("catalog"),
439                expression.args.get("db"),
440                expression.args.get("table"),
441                expression.args.get("this"),
442            )
443            if part
444        )
def columndef_sql(self, expression: sqlglot.expressions.ColumnDef) -> str:
446    def columndef_sql(self, expression: exp.ColumnDef) -> str:
447        column = self.sql(expression, "this")
448        kind = self.sql(expression, "kind")
449        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
450        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
451        kind = f" {kind}" if kind else ""
452        constraints = f" {constraints}" if constraints else ""
453
454        return f"{exists}{column}{kind}{constraints}"
def columnconstraint_sql(self, expression: sqlglot.expressions.ColumnConstraint) -> str:
456    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
457        this = self.sql(expression, "this")
458        kind_sql = self.sql(expression, "kind")
459        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
def autoincrementcolumnconstraint_sql(self, _) -> str:
461    def autoincrementcolumnconstraint_sql(self, _) -> str:
462        return self.token_sql(TokenType.AUTO_INCREMENT)
def compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
464    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
465        if isinstance(expression.this, list):
466            this = self.wrap(self.expressions(expression, key="this", flat=True))
467        else:
468            this = self.sql(expression, "this")
469
470        return f"COMPRESS {this}"
def generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
472    def generatedasidentitycolumnconstraint_sql(
473        self, expression: exp.GeneratedAsIdentityColumnConstraint
474    ) -> str:
475        this = ""
476        if expression.this is not None:
477            this = " ALWAYS " if expression.this else " BY DEFAULT "
478        start = expression.args.get("start")
479        start = f"START WITH {start}" if start else ""
480        increment = expression.args.get("increment")
481        increment = f" INCREMENT BY {increment}" if increment else ""
482        minvalue = expression.args.get("minvalue")
483        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
484        maxvalue = expression.args.get("maxvalue")
485        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
486        cycle = expression.args.get("cycle")
487        cycle_sql = ""
488        if cycle is not None:
489            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
490            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
491        sequence_opts = ""
492        if start or increment or cycle_sql:
493            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
494            sequence_opts = f" ({sequence_opts.strip()})"
495        return f"GENERATED{this}AS IDENTITY{sequence_opts}"
def notnullcolumnconstraint_sql(self, expression: sqlglot.expressions.NotNullColumnConstraint) -> str:
497    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
498        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
def primarykeycolumnconstraint_sql(self, expression: sqlglot.expressions.PrimaryKeyColumnConstraint) -> str:
500    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
501        desc = expression.args.get("desc")
502        if desc is not None:
503            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
504        return f"PRIMARY KEY"
def uniquecolumnconstraint_sql(self, _) -> str:
506    def uniquecolumnconstraint_sql(self, _) -> str:
507        return "UNIQUE"
def create_sql(self, expression: sqlglot.expressions.Create) -> str:
509    def create_sql(self, expression: exp.Create) -> str:
510        kind = self.sql(expression, "kind").upper()
511        properties = expression.args.get("properties")
512        properties_exp = expression.copy()
513        properties_locs = self.locate_properties(properties) if properties else {}
514        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
515            exp.Properties.Location.POST_WITH
516        ):
517            properties_exp.set(
518                "properties",
519                exp.Properties(
520                    expressions=[
521                        *properties_locs[exp.Properties.Location.POST_SCHEMA],
522                        *properties_locs[exp.Properties.Location.POST_WITH],
523                    ]
524                ),
525            )
526        if kind == "TABLE" and properties_locs.get(exp.Properties.Location.POST_NAME):
527            this_name = self.sql(expression.this, "this")
528            this_properties = self.properties(
529                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_NAME]),
530                wrapped=False,
531            )
532            this_schema = f"({self.expressions(expression.this)})"
533            this = f"{this_name}, {this_properties} {this_schema}"
534            properties_sql = ""
535        else:
536            this = self.sql(expression, "this")
537            properties_sql = self.sql(properties_exp, "properties")
538        begin = " BEGIN" if expression.args.get("begin") else ""
539        expression_sql = self.sql(expression, "expression")
540        if expression_sql:
541            expression_sql = f"{begin}{self.sep()}{expression_sql}"
542
543            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
544                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
545                    postalias_props_sql = self.properties(
546                        exp.Properties(
547                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
548                        ),
549                        wrapped=False,
550                    )
551                    expression_sql = f" AS {postalias_props_sql}{expression_sql}"
552                else:
553                    expression_sql = f" AS{expression_sql}"
554
555        postindex_props_sql = ""
556        if properties_locs.get(exp.Properties.Location.POST_INDEX):
557            postindex_props_sql = self.properties(
558                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
559                wrapped=False,
560                prefix=" ",
561            )
562
563        indexes = expression.args.get("indexes")
564        if indexes:
565            indexes_sql: t.List[str] = []
566            for index in indexes:
567                ind_unique = " UNIQUE" if index.args.get("unique") else ""
568                ind_primary = " PRIMARY" if index.args.get("primary") else ""
569                ind_amp = " AMP" if index.args.get("amp") else ""
570                ind_name = f" {index.name}" if index.name else ""
571                ind_columns = (
572                    f' ({self.expressions(index, key="columns", flat=True)})'
573                    if index.args.get("columns")
574                    else ""
575                )
576                ind_sql = f"{ind_unique}{ind_primary}{ind_amp} INDEX{ind_name}{ind_columns}"
577
578                if indexes_sql:
579                    indexes_sql.append(ind_sql)
580                else:
581                    indexes_sql.append(
582                        f"{ind_sql}{postindex_props_sql}"
583                        if index.args.get("primary")
584                        else f"{postindex_props_sql}{ind_sql}"
585                    )
586
587            index_sql = "".join(indexes_sql)
588        else:
589            index_sql = postindex_props_sql
590
591        replace = " OR REPLACE" if expression.args.get("replace") else ""
592        unique = " UNIQUE" if expression.args.get("unique") else ""
593        volatile = " VOLATILE" if expression.args.get("volatile") else ""
594
595        postcreate_props_sql = ""
596        if properties_locs.get(exp.Properties.Location.POST_CREATE):
597            postcreate_props_sql = self.properties(
598                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
599                sep=" ",
600                prefix=" ",
601                wrapped=False,
602            )
603
604        modifiers = "".join((replace, unique, volatile, postcreate_props_sql))
605
606        postexpression_props_sql = ""
607        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
608            postexpression_props_sql = self.properties(
609                exp.Properties(
610                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
611                ),
612                sep=" ",
613                prefix=" ",
614                wrapped=False,
615            )
616
617        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
618        no_schema_binding = (
619            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
620        )
621
622        expression_sql = f"CREATE{modifiers} {kind}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}"
623        return self.prepend_ctes(expression, expression_sql)
def describe_sql(self, expression: sqlglot.expressions.Describe) -> str:
625    def describe_sql(self, expression: exp.Describe) -> str:
626        return f"DESCRIBE {self.sql(expression, 'this')}"
def prepend_ctes(self, expression: sqlglot.expressions.Expression, sql: str) -> str:
628    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
629        with_ = self.sql(expression, "with")
630        if with_:
631            sql = f"{with_}{self.sep()}{sql}"
632        return sql
def with_sql(self, expression: sqlglot.expressions.With) -> str:
634    def with_sql(self, expression: exp.With) -> str:
635        sql = self.expressions(expression, flat=True)
636        recursive = "RECURSIVE " if expression.args.get("recursive") else ""
637
638        return f"WITH {recursive}{sql}"
def cte_sql(self, expression: sqlglot.expressions.CTE) -> str:
640    def cte_sql(self, expression: exp.CTE) -> str:
641        alias = self.sql(expression, "alias")
642        return f"{alias} AS {self.wrap(expression)}"
def tablealias_sql(self, expression: sqlglot.expressions.TableAlias) -> str:
644    def tablealias_sql(self, expression: exp.TableAlias) -> str:
645        alias = self.sql(expression, "this")
646        columns = self.expressions(expression, key="columns", flat=True)
647        columns = f"({columns})" if columns else ""
648        return f"{alias}{columns}"
def bitstring_sql(self, expression: sqlglot.expressions.BitString) -> str:
650    def bitstring_sql(self, expression: exp.BitString) -> str:
651        return self.sql(expression, "this")
def hexstring_sql(self, expression: sqlglot.expressions.HexString) -> str:
653    def hexstring_sql(self, expression: exp.HexString) -> str:
654        return self.sql(expression, "this")
def datatype_sql(self, expression: sqlglot.expressions.DataType) -> str:
656    def datatype_sql(self, expression: exp.DataType) -> str:
657        type_value = expression.this
658        type_sql = self.TYPE_MAPPING.get(type_value, type_value.value)
659        nested = ""
660        interior = self.expressions(expression, flat=True)
661        values = ""
662        if interior:
663            if expression.args.get("nested"):
664                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
665                if expression.args.get("values") is not None:
666                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
667                    values = (
668                        f"{delimiters[0]}{self.expressions(expression, 'values')}{delimiters[1]}"
669                    )
670            else:
671                nested = f"({interior})"
672
673        return f"{type_sql}{nested}{values}"
def directory_sql(self, expression: sqlglot.expressions.Directory) -> str:
675    def directory_sql(self, expression: exp.Directory) -> str:
676        local = "LOCAL " if expression.args.get("local") else ""
677        row_format = self.sql(expression, "row_format")
678        row_format = f" {row_format}" if row_format else ""
679        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
def delete_sql(self, expression: sqlglot.expressions.Delete) -> str:
681    def delete_sql(self, expression: exp.Delete) -> str:
682        this = self.sql(expression, "this")
683        this = f" FROM {this}" if this else ""
684        using_sql = (
685            f" USING {self.expressions(expression, 'using', sep=', USING ')}"
686            if expression.args.get("using")
687            else ""
688        )
689        where_sql = self.sql(expression, "where")
690        returning = self.sql(expression, "returning")
691        sql = f"DELETE{this}{using_sql}{where_sql}{returning}"
692        return self.prepend_ctes(expression, sql)
def drop_sql(self, expression: sqlglot.expressions.Drop) -> str:
694    def drop_sql(self, expression: exp.Drop) -> str:
695        this = self.sql(expression, "this")
696        kind = expression.args["kind"]
697        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
698        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
699        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
700        cascade = " CASCADE" if expression.args.get("cascade") else ""
701        return f"DROP{temporary}{materialized} {kind}{exists_sql}{this}{cascade}"
def except_sql(self, expression: sqlglot.expressions.Except) -> str:
703    def except_sql(self, expression: exp.Except) -> str:
704        return self.prepend_ctes(
705            expression,
706            self.set_operation(expression, self.except_op(expression)),
707        )
def except_op(self, expression: sqlglot.expressions.Except) -> str:
709    def except_op(self, expression: exp.Except) -> str:
710        return f"EXCEPT{'' if expression.args.get('distinct') else ' ALL'}"
def fetch_sql(self, expression: sqlglot.expressions.Fetch) -> str:
712    def fetch_sql(self, expression: exp.Fetch) -> str:
713        direction = expression.args.get("direction")
714        direction = f" {direction.upper()}" if direction else ""
715        count = expression.args.get("count")
716        count = f" {count}" if count else ""
717        return f"{self.seg('FETCH')}{direction}{count} ROWS ONLY"
def filter_sql(self, expression: sqlglot.expressions.Filter) -> str:
719    def filter_sql(self, expression: exp.Filter) -> str:
720        this = self.sql(expression, "this")
721        where = self.sql(expression, "expression")[1:]  # where has a leading space
722        return f"{this} FILTER({where})"
def hint_sql(self, expression: sqlglot.expressions.Hint) -> str:
724    def hint_sql(self, expression: exp.Hint) -> str:
725        if self.sql(expression, "this"):
726            self.unsupported("Hints are not supported")
727        return ""
def index_sql(self, expression: sqlglot.expressions.Index) -> str:
729    def index_sql(self, expression: exp.Index) -> str:
730        this = self.sql(expression, "this")
731        table = self.sql(expression, "table")
732        columns = self.sql(expression, "columns")
733        return f"{this} ON {table} {columns}"
def identifier_sql(self, expression: sqlglot.expressions.Identifier) -> str:
735    def identifier_sql(self, expression: exp.Identifier) -> str:
736        text = expression.name
737        text = text.lower() if self.normalize else text
738        text = text.replace(self.identifier_end, self._escaped_identifier_end)
739        if expression.args.get("quoted") or should_identify(text, self.identify):
740            text = f"{self.identifier_start}{text}{self.identifier_end}"
741        return text
def national_sql(self, expression: sqlglot.expressions.National) -> str:
743    def national_sql(self, expression: exp.National) -> str:
744        return f"N{self.sql(expression, 'this')}"
def partition_sql(self, expression: sqlglot.expressions.Partition) -> str:
746    def partition_sql(self, expression: exp.Partition) -> str:
747        return f"PARTITION({self.expressions(expression)})"
def properties_sql(self, expression: sqlglot.expressions.Properties) -> str:
749    def properties_sql(self, expression: exp.Properties) -> str:
750        root_properties = []
751        with_properties = []
752
753        for p in expression.expressions:
754            p_loc = self.PROPERTIES_LOCATION[p.__class__]
755            if p_loc == exp.Properties.Location.POST_WITH:
756                with_properties.append(p)
757            elif p_loc == exp.Properties.Location.POST_SCHEMA:
758                root_properties.append(p)
759
760        return self.root_properties(
761            exp.Properties(expressions=root_properties)
762        ) + self.with_properties(exp.Properties(expressions=with_properties))
def root_properties(self, properties: sqlglot.expressions.Properties) -> str:
764    def root_properties(self, properties: exp.Properties) -> str:
765        if properties.expressions:
766            return self.sep() + self.expressions(properties, indent=False, sep=" ")
767        return ""
def properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
769    def properties(
770        self,
771        properties: exp.Properties,
772        prefix: str = "",
773        sep: str = ", ",
774        suffix: str = "",
775        wrapped: bool = True,
776    ) -> str:
777        if properties.expressions:
778            expressions = self.expressions(properties, sep=sep, indent=False)
779            expressions = self.wrap(expressions) if wrapped else expressions
780            return f"{prefix}{' ' if prefix and prefix != ' ' else ''}{expressions}{suffix}"
781        return ""
def with_properties(self, properties: sqlglot.expressions.Properties) -> str:
783    def with_properties(self, properties: exp.Properties) -> str:
784        return self.properties(properties, prefix=self.seg("WITH"))
def locate_properties( self, properties: sqlglot.expressions.Properties) -> Dict[sqlglot.expressions.Properties.Location, list[sqlglot.expressions.Property]]:
786    def locate_properties(
787        self, properties: exp.Properties
788    ) -> t.Dict[exp.Properties.Location, list[exp.Property]]:
789        properties_locs: t.Dict[exp.Properties.Location, list[exp.Property]] = {
790            key: [] for key in exp.Properties.Location
791        }
792
793        for p in properties.expressions:
794            p_loc = self.PROPERTIES_LOCATION[p.__class__]
795            if p_loc == exp.Properties.Location.POST_NAME:
796                properties_locs[exp.Properties.Location.POST_NAME].append(p)
797            elif p_loc == exp.Properties.Location.POST_INDEX:
798                properties_locs[exp.Properties.Location.POST_INDEX].append(p)
799            elif p_loc == exp.Properties.Location.POST_SCHEMA:
800                properties_locs[exp.Properties.Location.POST_SCHEMA].append(p)
801            elif p_loc == exp.Properties.Location.POST_WITH:
802                properties_locs[exp.Properties.Location.POST_WITH].append(p)
803            elif p_loc == exp.Properties.Location.POST_CREATE:
804                properties_locs[exp.Properties.Location.POST_CREATE].append(p)
805            elif p_loc == exp.Properties.Location.POST_ALIAS:
806                properties_locs[exp.Properties.Location.POST_ALIAS].append(p)
807            elif p_loc == exp.Properties.Location.POST_EXPRESSION:
808                properties_locs[exp.Properties.Location.POST_EXPRESSION].append(p)
809            elif p_loc == exp.Properties.Location.UNSUPPORTED:
810                self.unsupported(f"Unsupported property {p.key}")
811
812        return properties_locs
def property_sql(self, expression: sqlglot.expressions.Property) -> str:
814    def property_sql(self, expression: exp.Property) -> str:
815        property_cls = expression.__class__
816        if property_cls == exp.Property:
817            return f"{expression.name}={self.sql(expression, 'value')}"
818
819        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
820        if not property_name:
821            self.unsupported(f"Unsupported property {expression.key}")
822
823        return f"{property_name}={self.sql(expression, 'this')}"
def likeproperty_sql(self, expression: sqlglot.expressions.LikeProperty) -> str:
825    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
826        options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
827        options = f" {options}" if options else ""
828        return f"LIKE {self.sql(expression, 'this')}{options}"
def fallbackproperty_sql(self, expression: sqlglot.expressions.FallbackProperty) -> str:
830    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
831        no = "NO " if expression.args.get("no") else ""
832        protection = " PROTECTION" if expression.args.get("protection") else ""
833        return f"{no}FALLBACK{protection}"
def journalproperty_sql(self, expression: sqlglot.expressions.JournalProperty) -> str:
835    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
836        no = "NO " if expression.args.get("no") else ""
837        dual = "DUAL " if expression.args.get("dual") else ""
838        before = "BEFORE " if expression.args.get("before") else ""
839        return f"{no}{dual}{before}JOURNAL"
def freespaceproperty_sql(self, expression: sqlglot.expressions.FreespaceProperty) -> str:
841    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
842        freespace = self.sql(expression, "this")
843        percent = " PERCENT" if expression.args.get("percent") else ""
844        return f"FREESPACE={freespace}{percent}"
def afterjournalproperty_sql(self, expression: sqlglot.expressions.AfterJournalProperty) -> str:
846    def afterjournalproperty_sql(self, expression: exp.AfterJournalProperty) -> str:
847        no = "NO " if expression.args.get("no") else ""
848        dual = "DUAL " if expression.args.get("dual") else ""
849        local = ""
850        if expression.args.get("local") is not None:
851            local = "LOCAL " if expression.args.get("local") else "NOT LOCAL "
852        return f"{no}{dual}{local}AFTER JOURNAL"
def checksumproperty_sql(self, expression: sqlglot.expressions.ChecksumProperty) -> str:
854    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
855        if expression.args.get("default"):
856            property = "DEFAULT"
857        elif expression.args.get("on"):
858            property = "ON"
859        else:
860            property = "OFF"
861        return f"CHECKSUM={property}"
def mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
863    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
864        if expression.args.get("no"):
865            return "NO MERGEBLOCKRATIO"
866        if expression.args.get("default"):
867            return "DEFAULT MERGEBLOCKRATIO"
868
869        percent = " PERCENT" if expression.args.get("percent") else ""
870        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def datablocksizeproperty_sql(self, expression: sqlglot.expressions.DataBlocksizeProperty) -> str:
872    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
873        default = expression.args.get("default")
874        min = expression.args.get("min")
875        if default is not None or min is not None:
876            if default:
877                property = "DEFAULT"
878            elif min:
879                property = "MINIMUM"
880            else:
881                property = "MAXIMUM"
882            return f"{property} DATABLOCKSIZE"
883        else:
884            units = expression.args.get("units")
885            units = f" {units}" if units else ""
886            return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
888    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
889        autotemp = expression.args.get("autotemp")
890        always = expression.args.get("always")
891        default = expression.args.get("default")
892        manual = expression.args.get("manual")
893        never = expression.args.get("never")
894
895        if autotemp is not None:
896            property = f"AUTOTEMP({self.expressions(autotemp)})"
897        elif always:
898            property = "ALWAYS"
899        elif default:
900            property = "DEFAULT"
901        elif manual:
902            property = "MANUAL"
903        elif never:
904            property = "NEVER"
905        return f"BLOCKCOMPRESSION={property}"
def isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
907    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
908        no = expression.args.get("no")
909        no = " NO" if no else ""
910        concurrent = expression.args.get("concurrent")
911        concurrent = " CONCURRENT" if concurrent else ""
912
913        for_ = ""
914        if expression.args.get("for_all"):
915            for_ = " FOR ALL"
916        elif expression.args.get("for_insert"):
917            for_ = " FOR INSERT"
918        elif expression.args.get("for_none"):
919            for_ = " FOR NONE"
920        return f"WITH{no}{concurrent} ISOLATED LOADING{for_}"
def lockingproperty_sql(self, expression: sqlglot.expressions.LockingProperty) -> str:
922    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
923        kind = expression.args.get("kind")
924        this: str = f" {this}" if expression.this else ""
925        for_or_in = expression.args.get("for_or_in")
926        lock_type = expression.args.get("lock_type")
927        override = " OVERRIDE" if expression.args.get("override") else ""
928        return f"LOCKING {kind}{this} {for_or_in} {lock_type}{override}"
def withdataproperty_sql(self, expression: sqlglot.expressions.WithDataProperty) -> str:
930    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
931        data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
932        statistics = expression.args.get("statistics")
933        statistics_sql = ""
934        if statistics is not None:
935            statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
936        return f"{data_sql}{statistics_sql}"
def insert_sql(self, expression: sqlglot.expressions.Insert) -> str:
938    def insert_sql(self, expression: exp.Insert) -> str:
939        overwrite = expression.args.get("overwrite")
940
941        if isinstance(expression.this, exp.Directory):
942            this = "OVERWRITE " if overwrite else "INTO "
943        else:
944            this = "OVERWRITE TABLE " if overwrite else "INTO "
945
946        alternative = expression.args.get("alternative")
947        alternative = f" OR {alternative} " if alternative else " "
948        this = f"{this}{self.sql(expression, 'this')}"
949
950        exists = " IF EXISTS " if expression.args.get("exists") else " "
951        partition_sql = (
952            self.sql(expression, "partition") if expression.args.get("partition") else ""
953        )
954        expression_sql = self.sql(expression, "expression")
955        returning = self.sql(expression, "returning")
956        sep = self.sep() if partition_sql else ""
957        sql = f"INSERT{alternative}{this}{exists}{partition_sql}{sep}{expression_sql}{returning}"
958        return self.prepend_ctes(expression, sql)
def intersect_sql(self, expression: sqlglot.expressions.Intersect) -> str:
960    def intersect_sql(self, expression: exp.Intersect) -> str:
961        return self.prepend_ctes(
962            expression,
963            self.set_operation(expression, self.intersect_op(expression)),
964        )
def intersect_op(self, expression: sqlglot.expressions.Intersect) -> str:
966    def intersect_op(self, expression: exp.Intersect) -> str:
967        return f"INTERSECT{'' if expression.args.get('distinct') else ' ALL'}"
def introducer_sql(self, expression: sqlglot.expressions.Introducer) -> str:
969    def introducer_sql(self, expression: exp.Introducer) -> str:
970        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def pseudotype_sql(self, expression: sqlglot.expressions.PseudoType) -> str:
972    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
973        return expression.name.upper()
def returning_sql(self, expression: sqlglot.expressions.Returning) -> str:
975    def returning_sql(self, expression: exp.Returning) -> str:
976        return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
def rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
978    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
979        fields = expression.args.get("fields")
980        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
981        escaped = expression.args.get("escaped")
982        escaped = f" ESCAPED BY {escaped}" if escaped else ""
983        items = expression.args.get("collection_items")
984        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
985        keys = expression.args.get("map_keys")
986        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
987        lines = expression.args.get("lines")
988        lines = f" LINES TERMINATED BY {lines}" if lines else ""
989        null = expression.args.get("null")
990        null = f" NULL DEFINED AS {null}" if null else ""
991        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
def table_sql(self, expression: sqlglot.expressions.Table, sep: str = ' AS ') -> str:
 993    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
 994        table = ".".join(
 995            part
 996            for part in [
 997                self.sql(expression, "catalog"),
 998                self.sql(expression, "db"),
 999                self.sql(expression, "this"),
1000            ]
1001            if part
1002        )
1003
1004        alias = self.sql(expression, "alias")
1005        alias = f"{sep}{alias}" if alias else ""
1006        hints = self.expressions(expression, key="hints", sep=", ", flat=True)
1007        hints = f" WITH ({hints})" if hints else ""
1008        laterals = self.expressions(expression, key="laterals", sep="")
1009        joins = self.expressions(expression, key="joins", sep="")
1010        pivots = self.expressions(expression, key="pivots", sep="")
1011        system_time = expression.args.get("system_time")
1012        system_time = f" {self.sql(expression, 'system_time')}" if system_time else ""
1013
1014        return f"{table}{system_time}{alias}{hints}{laterals}{joins}{pivots}"
def tablesample_sql( self, expression: sqlglot.expressions.TableSample, seed_prefix: str = 'SEED') -> str:
1016    def tablesample_sql(self, expression: exp.TableSample, seed_prefix: str = "SEED") -> str:
1017        if self.alias_post_tablesample and expression.this.alias:
1018            this = self.sql(expression.this, "this")
1019            alias = f" AS {self.sql(expression.this, 'alias')}"
1020        else:
1021            this = self.sql(expression, "this")
1022            alias = ""
1023        method = self.sql(expression, "method")
1024        method = f"{method.upper()} " if method else ""
1025        numerator = self.sql(expression, "bucket_numerator")
1026        denominator = self.sql(expression, "bucket_denominator")
1027        field = self.sql(expression, "bucket_field")
1028        field = f" ON {field}" if field else ""
1029        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
1030        percent = self.sql(expression, "percent")
1031        percent = f"{percent} PERCENT" if percent else ""
1032        rows = self.sql(expression, "rows")
1033        rows = f"{rows} ROWS" if rows else ""
1034        size = self.sql(expression, "size")
1035        seed = self.sql(expression, "seed")
1036        seed = f" {seed_prefix} ({seed})" if seed else ""
1037        kind = expression.args.get("kind", "TABLESAMPLE")
1038        return f"{this} {kind} {method}({bucket}{percent}{rows}{size}){seed}{alias}"
def pivot_sql(self, expression: sqlglot.expressions.Pivot) -> str:
1040    def pivot_sql(self, expression: exp.Pivot) -> str:
1041        this = self.sql(expression, "this")
1042        alias = self.sql(expression, "alias")
1043        alias = f" AS {alias}" if alias else ""
1044        unpivot = expression.args.get("unpivot")
1045        direction = "UNPIVOT" if unpivot else "PIVOT"
1046        expressions = self.expressions(expression, key="expressions")
1047        field = self.sql(expression, "field")
1048        return f"{this} {direction}({expressions} FOR {field}){alias}"
def tuple_sql(self, expression: sqlglot.expressions.Tuple) -> str:
1050    def tuple_sql(self, expression: exp.Tuple) -> str:
1051        return f"({self.expressions(expression, flat=True)})"
def update_sql(self, expression: sqlglot.expressions.Update) -> str:
1053    def update_sql(self, expression: exp.Update) -> str:
1054        this = self.sql(expression, "this")
1055        set_sql = self.expressions(expression, flat=True)
1056        from_sql = self.sql(expression, "from")
1057        where_sql = self.sql(expression, "where")
1058        returning = self.sql(expression, "returning")
1059        sql = f"UPDATE {this} SET {set_sql}{from_sql}{where_sql}{returning}"
1060        return self.prepend_ctes(expression, sql)
def values_sql(self, expression: sqlglot.expressions.Values) -> str:
1062    def values_sql(self, expression: exp.Values) -> str:
1063        args = self.expressions(expression)
1064        alias = self.sql(expression, "alias")
1065        values = f"VALUES{self.seg('')}{args}"
1066        values = (
1067            f"({values})"
1068            if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From))
1069            else values
1070        )
1071        return f"{values} AS {alias}" if alias else values
def var_sql(self, expression: sqlglot.expressions.Var) -> str:
1073    def var_sql(self, expression: exp.Var) -> str:
1074        return self.sql(expression, "this")
def into_sql(self, expression: sqlglot.expressions.Into) -> str:
1076    def into_sql(self, expression: exp.Into) -> str:
1077        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1078        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
1079        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
def from_sql(self, expression: sqlglot.expressions.From) -> str:
1081    def from_sql(self, expression: exp.From) -> str:
1082        expressions = self.expressions(expression, flat=True)
1083        return f"{self.seg('FROM')} {expressions}"
def group_sql(self, expression: sqlglot.expressions.Group) -> str:
1085    def group_sql(self, expression: exp.Group) -> str:
1086        group_by = self.op_expressions("GROUP BY", expression)
1087        grouping_sets = self.expressions(expression, key="grouping_sets", indent=False)
1088        grouping_sets = (
1089            f"{self.seg('GROUPING SETS')} {self.wrap(grouping_sets)}" if grouping_sets else ""
1090        )
1091
1092        cube = expression.args.get("cube", [])
1093        if seq_get(cube, 0) is True:
1094            return f"{group_by}{self.seg('WITH CUBE')}"
1095        else:
1096            cube_sql = self.expressions(expression, key="cube", indent=False)
1097            cube_sql = f"{self.seg('CUBE')} {self.wrap(cube_sql)}" if cube_sql else ""
1098
1099        rollup = expression.args.get("rollup", [])
1100        if seq_get(rollup, 0) is True:
1101            return f"{group_by}{self.seg('WITH ROLLUP')}"
1102        else:
1103            rollup_sql = self.expressions(expression, key="rollup", indent=False)
1104            rollup_sql = f"{self.seg('ROLLUP')} {self.wrap(rollup_sql)}" if rollup_sql else ""
1105
1106        groupings = csv(grouping_sets, cube_sql, rollup_sql, sep=",")
1107
1108        if expression.args.get("expressions") and groupings:
1109            group_by = f"{group_by},"
1110
1111        return f"{group_by}{groupings}"
def having_sql(self, expression: sqlglot.expressions.Having) -> str:
1113    def having_sql(self, expression: exp.Having) -> str:
1114        this = self.indent(self.sql(expression, "this"))
1115        return f"{self.seg('HAVING')}{self.sep()}{this}"
def join_sql(self, expression: sqlglot.expressions.Join) -> str:
1117    def join_sql(self, expression: exp.Join) -> str:
1118        op_sql = self.seg(
1119            " ".join(
1120                op
1121                for op in (
1122                    "NATURAL" if expression.args.get("natural") else None,
1123                    expression.side,
1124                    expression.kind,
1125                    "JOIN",
1126                )
1127                if op
1128            )
1129        )
1130        on_sql = self.sql(expression, "on")
1131        using = expression.args.get("using")
1132
1133        if not on_sql and using:
1134            on_sql = csv(*(self.sql(column) for column in using))
1135
1136        if on_sql:
1137            on_sql = self.indent(on_sql, skip_first=True)
1138            space = self.seg(" " * self.pad) if self.pretty else " "
1139            if using:
1140                on_sql = f"{space}USING ({on_sql})"
1141            else:
1142                on_sql = f"{space}ON {on_sql}"
1143
1144        expression_sql = self.sql(expression, "expression")
1145        this_sql = self.sql(expression, "this")
1146        return f"{expression_sql}{op_sql} {this_sql}{on_sql}"
def lambda_sql( self, expression: sqlglot.expressions.Lambda, arrow_sep: str = '->') -> str:
1148    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str:
1149        args = self.expressions(expression, flat=True)
1150        args = f"({args})" if len(args.split(",")) > 1 else args
1151        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
def lateral_sql(self, expression: sqlglot.expressions.Lateral) -> str:
1153    def lateral_sql(self, expression: exp.Lateral) -> str:
1154        this = self.sql(expression, "this")
1155
1156        if isinstance(expression.this, exp.Subquery):
1157            return f"LATERAL {this}"
1158
1159        if expression.args.get("view"):
1160            alias = expression.args["alias"]
1161            columns = self.expressions(alias, key="columns", flat=True)
1162            table = f" {alias.name}" if alias.name else ""
1163            columns = f" AS {columns}" if columns else ""
1164            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
1165            return f"{op_sql}{self.sep()}{this}{table}{columns}"
1166
1167        alias = self.sql(expression, "alias")
1168        alias = f" AS {alias}" if alias else ""
1169        return f"LATERAL {this}{alias}"
def limit_sql(self, expression: sqlglot.expressions.Limit) -> str:
1171    def limit_sql(self, expression: exp.Limit) -> str:
1172        this = self.sql(expression, "this")
1173        return f"{this}{self.seg('LIMIT')} {self.sql(expression, 'expression')}"
def offset_sql(self, expression: sqlglot.expressions.Offset) -> str:
1175    def offset_sql(self, expression: exp.Offset) -> str:
1176        this = self.sql(expression, "this")
1177        return f"{this}{self.seg('OFFSET')} {self.sql(expression, 'expression')}"
def setitem_sql(self, expression: sqlglot.expressions.SetItem) -> str:
1179    def setitem_sql(self, expression: exp.SetItem) -> str:
1180        kind = self.sql(expression, "kind")
1181        kind = f"{kind} " if kind else ""
1182        this = self.sql(expression, "this")
1183        expressions = self.expressions(expression)
1184        collate = self.sql(expression, "collate")
1185        collate = f" COLLATE {collate}" if collate else ""
1186        global_ = "GLOBAL " if expression.args.get("global") else ""
1187        return f"{global_}{kind}{this}{expressions}{collate}"
def set_sql(self, expression: sqlglot.expressions.Set) -> str:
1189    def set_sql(self, expression: exp.Set) -> str:
1190        expressions = (
1191            f" {self.expressions(expression, flat=True)}" if expression.expressions else ""
1192        )
1193        return f"SET{expressions}"
def lock_sql(self, expression: sqlglot.expressions.Lock) -> str:
1195    def lock_sql(self, expression: exp.Lock) -> str:
1196        if self.LOCKING_READS_SUPPORTED:
1197            lock_type = "UPDATE" if expression.args["update"] else "SHARE"
1198            return self.seg(f"FOR {lock_type}")
1199
1200        self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
1201        return ""
def literal_sql(self, expression: sqlglot.expressions.Literal) -> str:
1203    def literal_sql(self, expression: exp.Literal) -> str:
1204        text = expression.this or ""
1205        if expression.is_string:
1206            text = text.replace(self.quote_end, self._escaped_quote_end)
1207            if self.pretty:
1208                text = text.replace("\n", self.SENTINEL_LINE_BREAK)
1209            text = f"{self.quote_start}{text}{self.quote_end}"
1210        return text
def loaddata_sql(self, expression: sqlglot.expressions.LoadData) -> str:
1212    def loaddata_sql(self, expression: exp.LoadData) -> str:
1213        local = " LOCAL" if expression.args.get("local") else ""
1214        inpath = f" INPATH {self.sql(expression, 'inpath')}"
1215        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
1216        this = f" INTO TABLE {self.sql(expression, 'this')}"
1217        partition = self.sql(expression, "partition")
1218        partition = f" {partition}" if partition else ""
1219        input_format = self.sql(expression, "input_format")
1220        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
1221        serde = self.sql(expression, "serde")
1222        serde = f" SERDE {serde}" if serde else ""
1223        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
def null_sql(self, *_) -> str:
1225    def null_sql(self, *_) -> str:
1226        return "NULL"
def boolean_sql(self, expression: sqlglot.expressions.Boolean) -> str:
1228    def boolean_sql(self, expression: exp.Boolean) -> str:
1229        return "TRUE" if expression.this else "FALSE"
def order_sql(self, expression: sqlglot.expressions.Order, flat: bool = False) -> str:
1231    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
1232        this = self.sql(expression, "this")
1233        this = f"{this} " if this else this
1234        return self.op_expressions(f"{this}ORDER BY", expression, flat=this or flat)  # type: ignore
def cluster_sql(self, expression: sqlglot.expressions.Cluster) -> str:
1236    def cluster_sql(self, expression: exp.Cluster) -> str:
1237        return self.op_expressions("CLUSTER BY", expression)
def distribute_sql(self, expression: sqlglot.expressions.Distribute) -> str:
1239    def distribute_sql(self, expression: exp.Distribute) -> str:
1240        return self.op_expressions("DISTRIBUTE BY", expression)
def sort_sql(self, expression: sqlglot.expressions.Sort) -> str:
1242    def sort_sql(self, expression: exp.Sort) -> str:
1243        return self.op_expressions("SORT BY", expression)
def ordered_sql(self, expression: sqlglot.expressions.Ordered) -> str:
1245    def ordered_sql(self, expression: exp.Ordered) -> str:
1246        desc = expression.args.get("desc")
1247        asc = not desc
1248
1249        nulls_first = expression.args.get("nulls_first")
1250        nulls_last = not nulls_first
1251        nulls_are_large = self.null_ordering == "nulls_are_large"
1252        nulls_are_small = self.null_ordering == "nulls_are_small"
1253        nulls_are_last = self.null_ordering == "nulls_are_last"
1254
1255        sort_order = " DESC" if desc else ""
1256        nulls_sort_change = ""
1257        if nulls_first and (
1258            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
1259        ):
1260            nulls_sort_change = " NULLS FIRST"
1261        elif (
1262            nulls_last
1263            and ((asc and nulls_are_small) or (desc and nulls_are_large))
1264            and not nulls_are_last
1265        ):
1266            nulls_sort_change = " NULLS LAST"
1267
1268        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
1269            self.unsupported(
1270                "Sorting in an ORDER BY on NULLS FIRST/NULLS LAST is not supported by this dialect"
1271            )
1272            nulls_sort_change = ""
1273
1274        return f"{self.sql(expression, 'this')}{sort_order}{nulls_sort_change}"
def matchrecognize_sql(self, expression: sqlglot.expressions.MatchRecognize) -> str:
1276    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
1277        partition = self.partition_by_sql(expression)
1278        order = self.sql(expression, "order")
1279        measures = self.sql(expression, "measures")
1280        measures = self.seg(f"MEASURES {measures}") if measures else ""
1281        rows = self.sql(expression, "rows")
1282        rows = self.seg(rows) if rows else ""
1283        after = self.sql(expression, "after")
1284        after = self.seg(after) if after else ""
1285        pattern = self.sql(expression, "pattern")
1286        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
1287        define = self.sql(expression, "define")
1288        define = self.seg(f"DEFINE {define}") if define else ""
1289        body = "".join(
1290            (
1291                partition,
1292                order,
1293                measures,
1294                rows,
1295                after,
1296                pattern,
1297                define,
1298            )
1299        )
1300        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}"
def query_modifiers(self, expression: sqlglot.expressions.Expression, *sqls: str) -> str:
1302    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
1303        return csv(
1304            *sqls,
1305            *[self.sql(sql) for sql in expression.args.get("joins") or []],
1306            self.sql(expression, "match"),
1307            *[self.sql(sql) for sql in expression.args.get("laterals") or []],
1308            self.sql(expression, "where"),
1309            self.sql(expression, "group"),
1310            self.sql(expression, "having"),
1311            self.sql(expression, "qualify"),
1312            self.seg("WINDOW ") + self.expressions(expression, "windows", flat=True)
1313            if expression.args.get("windows")
1314            else "",
1315            self.sql(expression, "distribute"),
1316            self.sql(expression, "sort"),
1317            self.sql(expression, "cluster"),
1318            self.sql(expression, "order"),
1319            self.sql(expression, "limit"),
1320            self.sql(expression, "offset"),
1321            self.sql(expression, "lock"),
1322            self.sql(expression, "sample"),
1323            sep="",
1324        )
def select_sql(self, expression: sqlglot.expressions.Select) -> str:
1326    def select_sql(self, expression: exp.Select) -> str:
1327        hint = self.sql(expression, "hint")
1328        distinct = self.sql(expression, "distinct")
1329        distinct = f" {distinct}" if distinct else ""
1330        expressions = self.expressions(expression)
1331        expressions = f"{self.sep()}{expressions}" if expressions else expressions
1332        sql = self.query_modifiers(
1333            expression,
1334            f"SELECT{hint}{distinct}{expressions}",
1335            self.sql(expression, "into", comment=False),
1336            self.sql(expression, "from", comment=False),
1337        )
1338        return self.prepend_ctes(expression, sql)
def schema_sql(self, expression: sqlglot.expressions.Schema) -> str:
1340    def schema_sql(self, expression: exp.Schema) -> str:
1341        this = self.sql(expression, "this")
1342        this = f"{this} " if this else ""
1343        sql = f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
1344        return f"{this}{sql}"
def star_sql(self, expression: sqlglot.expressions.Star) -> str:
1346    def star_sql(self, expression: exp.Star) -> str:
1347        except_ = self.expressions(expression, key="except", flat=True)
1348        except_ = f"{self.seg(self.STAR_MAPPING['except'])} ({except_})" if except_ else ""
1349        replace = self.expressions(expression, key="replace", flat=True)
1350        replace = f"{self.seg(self.STAR_MAPPING['replace'])} ({replace})" if replace else ""
1351        return f"*{except_}{replace}"
def structkwarg_sql(self, expression: sqlglot.expressions.StructKwarg) -> str:
1353    def structkwarg_sql(self, expression: exp.StructKwarg) -> str:
1354        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
def parameter_sql(self, expression: sqlglot.expressions.Parameter) -> str:
1356    def parameter_sql(self, expression: exp.Parameter) -> str:
1357        this = self.sql(expression, "this")
1358        this = f"{{{this}}}" if expression.args.get("wrapped") else f"{this}"
1359        return f"{self.PARAMETER_TOKEN}{this}"
def sessionparameter_sql(self, expression: sqlglot.expressions.SessionParameter) -> str:
1361    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
1362        this = self.sql(expression, "this")
1363        kind = expression.text("kind")
1364        if kind:
1365            kind = f"{kind}."
1366        return f"@@{kind}{this}"
def placeholder_sql(self, expression: sqlglot.expressions.Placeholder) -> str:
1368    def placeholder_sql(self, expression: exp.Placeholder) -> str:
1369        return f":{expression.name}" if expression.name else "?"
def subquery_sql(self, expression: sqlglot.expressions.Subquery, sep: str = ' AS ') -> str:
1371    def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
1372        alias = self.sql(expression, "alias")
1373        alias = f"{sep}{alias}" if alias else ""
1374
1375        sql = self.query_modifiers(
1376            expression,
1377            self.wrap(expression),
1378            alias,
1379            self.expressions(expression, key="pivots", sep=" "),
1380        )
1381
1382        return self.prepend_ctes(expression, sql)
def qualify_sql(self, expression: sqlglot.expressions.Qualify) -> str:
1384    def qualify_sql(self, expression: exp.Qualify) -> str:
1385        this = self.indent(self.sql(expression, "this"))
1386        return f"{self.seg('QUALIFY')}{self.sep()}{this}"
def union_sql(self, expression: sqlglot.expressions.Union) -> str:
1388    def union_sql(self, expression: exp.Union) -> str:
1389        return self.prepend_ctes(
1390            expression,
1391            self.set_operation(expression, self.union_op(expression)),
1392        )
def union_op(self, expression: sqlglot.expressions.Union) -> str:
1394    def union_op(self, expression: exp.Union) -> str:
1395        kind = " DISTINCT" if self.EXPLICIT_UNION else ""
1396        kind = kind if expression.args.get("distinct") else " ALL"
1397        return f"UNION{kind}"
def unnest_sql(self, expression: sqlglot.expressions.Unnest) -> str:
1399    def unnest_sql(self, expression: exp.Unnest) -> str:
1400        args = self.expressions(expression, flat=True)
1401        alias = expression.args.get("alias")
1402        if alias and self.unnest_column_only:
1403            columns = alias.columns
1404            alias = self.sql(columns[0]) if columns else ""
1405        else:
1406            alias = self.sql(expression, "alias")
1407        alias = f" AS {alias}" if alias else alias
1408        ordinality = " WITH ORDINALITY" if expression.args.get("ordinality") else ""
1409        offset = expression.args.get("offset")
1410        offset = f" WITH OFFSET AS {self.sql(offset)}" if offset else ""
1411        return f"UNNEST({args}){ordinality}{alias}{offset}"
def where_sql(self, expression: sqlglot.expressions.Where) -> str:
1413    def where_sql(self, expression: exp.Where) -> str:
1414        this = self.indent(self.sql(expression, "this"))
1415        return f"{self.seg('WHERE')}{self.sep()}{this}"
def window_sql(self, expression: sqlglot.expressions.Window) -> str:
1417    def window_sql(self, expression: exp.Window) -> str:
1418        this = self.sql(expression, "this")
1419
1420        partition = self.partition_by_sql(expression)
1421
1422        order = expression.args.get("order")
1423        order_sql = self.order_sql(order, flat=True) if order else ""
1424
1425        partition_sql = partition + " " if partition and order else partition
1426
1427        spec = expression.args.get("spec")
1428        spec_sql = " " + self.window_spec_sql(spec) if spec else ""
1429
1430        alias = self.sql(expression, "alias")
1431        this = f"{this} {'AS' if expression.arg_key == 'windows' else 'OVER'}"
1432
1433        if not partition and not order and not spec and alias:
1434            return f"{this} {alias}"
1435
1436        window_args = alias + partition_sql + order_sql + spec_sql
1437
1438        return f"{this} ({window_args.strip()})"
def partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
1440    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
1441        partition = self.expressions(expression, key="partition_by", flat=True)
1442        return f"PARTITION BY {partition}" if partition else ""
def window_spec_sql(self, expression: sqlglot.expressions.WindowSpec) -> str:
1444    def window_spec_sql(self, expression: exp.WindowSpec) -> str:
1445        kind = self.sql(expression, "kind")
1446        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
1447        end = (
1448            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
1449            or "CURRENT ROW"
1450        )
1451        return f"{kind} BETWEEN {start} AND {end}"
def withingroup_sql(self, expression: sqlglot.expressions.WithinGroup) -> str:
1453    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
1454        this = self.sql(expression, "this")
1455        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
1456        return f"{this} WITHIN GROUP ({expression_sql})"
def between_sql(self, expression: sqlglot.expressions.Between) -> str:
1458    def between_sql(self, expression: exp.Between) -> str:
1459        this = self.sql(expression, "this")
1460        low = self.sql(expression, "low")
1461        high = self.sql(expression, "high")
1462        return f"{this} BETWEEN {low} AND {high}"
def bracket_sql(self, expression: sqlglot.expressions.Bracket) -> str:
1464    def bracket_sql(self, expression: exp.Bracket) -> str:
1465        expressions = apply_index_offset(expression.expressions, self.index_offset)
1466        expressions_sql = ", ".join(self.sql(e) for e in expressions)
1467
1468        return f"{self.sql(expression, 'this')}[{expressions_sql}]"
def all_sql(self, expression: sqlglot.expressions.All) -> str:
1470    def all_sql(self, expression: exp.All) -> str:
1471        return f"ALL {self.wrap(expression)}"
def any_sql(self, expression: sqlglot.expressions.Any) -> str:
1473    def any_sql(self, expression: exp.Any) -> str:
1474        this = self.sql(expression, "this")
1475        if isinstance(expression.this, exp.Subqueryable):
1476            this = self.wrap(this)
1477        return f"ANY {this}"
def exists_sql(self, expression: sqlglot.expressions.Exists) -> str:
1479    def exists_sql(self, expression: exp.Exists) -> str:
1480        return f"EXISTS{self.wrap(expression)}"
def case_sql(self, expression: sqlglot.expressions.Case) -> str:
1482    def case_sql(self, expression: exp.Case) -> str:
1483        this = self.sql(expression, "this")
1484        statements = [f"CASE {this}" if this else "CASE"]
1485
1486        for e in expression.args["ifs"]:
1487            statements.append(f"WHEN {self.sql(e, 'this')}")
1488            statements.append(f"THEN {self.sql(e, 'true')}")
1489
1490        default = self.sql(expression, "default")
1491
1492        if default:
1493            statements.append(f"ELSE {default}")
1494
1495        statements.append("END")
1496
1497        if self.pretty and self.text_width(statements) > self._max_text_width:
1498            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
1499
1500        return " ".join(statements)
def constraint_sql(self, expression: sqlglot.expressions.Constraint) -> str:
1502    def constraint_sql(self, expression: exp.Constraint) -> str:
1503        this = self.sql(expression, "this")
1504        expressions = self.expressions(expression, flat=True)
1505        return f"CONSTRAINT {this} {expressions}"
def extract_sql(self, expression: sqlglot.expressions.Extract) -> str:
1507    def extract_sql(self, expression: exp.Extract) -> str:
1508        this = self.sql(expression, "this")
1509        expression_sql = self.sql(expression, "expression")
1510        return f"EXTRACT({this} FROM {expression_sql})"
def trim_sql(self, expression: sqlglot.expressions.Trim) -> str:
1512    def trim_sql(self, expression: exp.Trim) -> str:
1513        trim_type = self.sql(expression, "position")
1514
1515        if trim_type == "LEADING":
1516            return self.func("LTRIM", expression.this)
1517        elif trim_type == "TRAILING":
1518            return self.func("RTRIM", expression.this)
1519        else:
1520            return self.func("TRIM", expression.this, expression.expression)
def concat_sql(self, expression: sqlglot.expressions.Concat) -> str:
1522    def concat_sql(self, expression: exp.Concat) -> str:
1523        if len(expression.expressions) == 1:
1524            return self.sql(expression.expressions[0])
1525        return self.function_fallback_sql(expression)
def check_sql(self, expression: sqlglot.expressions.Check) -> str:
1527    def check_sql(self, expression: exp.Check) -> str:
1528        this = self.sql(expression, key="this")
1529        return f"CHECK ({this})"
def foreignkey_sql(self, expression: sqlglot.expressions.ForeignKey) -> str:
1531    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
1532        expressions = self.expressions(expression, flat=True)
1533        reference = self.sql(expression, "reference")
1534        reference = f" {reference}" if reference else ""
1535        delete = self.sql(expression, "delete")
1536        delete = f" ON DELETE {delete}" if delete else ""
1537        update = self.sql(expression, "update")
1538        update = f" ON UPDATE {update}" if update else ""
1539        return f"FOREIGN KEY ({expressions}){reference}{delete}{update}"
def primarykey_sql(self, expression: sqlglot.expressions.ForeignKey) -> str:
1541    def primarykey_sql(self, expression: exp.ForeignKey) -> str:
1542        expressions = self.expressions(expression, flat=True)
1543        options = self.expressions(expression, "options", flat=True, sep=" ")
1544        options = f" {options}" if options else ""
1545        return f"PRIMARY KEY ({expressions}){options}"
def unique_sql(self, expression: sqlglot.expressions.Unique) -> str:
1547    def unique_sql(self, expression: exp.Unique) -> str:
1548        columns = self.expressions(expression, key="expressions")
1549        return f"UNIQUE ({columns})"
def if_sql(self, expression: sqlglot.expressions.If) -> str:
1551    def if_sql(self, expression: exp.If) -> str:
1552        return self.case_sql(
1553            exp.Case(ifs=[expression.copy()], default=expression.args.get("false"))
1554        )
def in_sql(self, expression: sqlglot.expressions.In) -> str:
1556    def in_sql(self, expression: exp.In) -> str:
1557        query = expression.args.get("query")
1558        unnest = expression.args.get("unnest")
1559        field = expression.args.get("field")
1560        is_global = " GLOBAL" if expression.args.get("is_global") else ""
1561
1562        if query:
1563            in_sql = self.wrap(query)
1564        elif unnest:
1565            in_sql = self.in_unnest_op(unnest)
1566        elif field:
1567            in_sql = self.sql(field)
1568        else:
1569            in_sql = f"({self.expressions(expression, flat=True)})"
1570
1571        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
def in_unnest_op(self, unnest: sqlglot.expressions.Unnest) -> str:
1573    def in_unnest_op(self, unnest: exp.Unnest) -> str:
1574        return f"(SELECT {self.sql(unnest)})"
def interval_sql(self, expression: sqlglot.expressions.Interval) -> str:
1576    def interval_sql(self, expression: exp.Interval) -> str:
1577        this = expression.args.get("this")
1578        if this:
1579            this = (
1580                f" {this}"
1581                if isinstance(this, exp.Literal) or isinstance(this, exp.Paren)
1582                else f" ({this})"
1583            )
1584        else:
1585            this = ""
1586        unit = self.sql(expression, "unit")
1587        unit = f" {unit}" if unit else ""
1588        return f"INTERVAL{this}{unit}"
def return_sql(self, expression: sqlglot.expressions.Return) -> str:
1590    def return_sql(self, expression: exp.Return) -> str:
1591        return f"RETURN {self.sql(expression, 'this')}"
def reference_sql(self, expression: sqlglot.expressions.Reference) -> str:
1593    def reference_sql(self, expression: exp.Reference) -> str:
1594        this = self.sql(expression, "this")
1595        expressions = self.expressions(expression, flat=True)
1596        expressions = f"({expressions})" if expressions else ""
1597        options = self.expressions(expression, "options", flat=True, sep=" ")
1598        options = f" {options}" if options else ""
1599        return f"REFERENCES {this}{expressions}{options}"
def anonymous_sql(self, expression: sqlglot.expressions.Anonymous) -> str:
1601    def anonymous_sql(self, expression: exp.Anonymous) -> str:
1602        return self.func(expression.name, *expression.expressions)
def paren_sql(self, expression: sqlglot.expressions.Paren) -> str:
1604    def paren_sql(self, expression: exp.Paren) -> str:
1605        if isinstance(expression.unnest(), exp.Select):
1606            sql = self.wrap(expression)
1607        else:
1608            sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
1609            sql = f"({sql}{self.seg(')', sep='')}"
1610
1611        return self.prepend_ctes(expression, sql)
def neg_sql(self, expression: sqlglot.expressions.Neg) -> str:
1613    def neg_sql(self, expression: exp.Neg) -> str:
1614        # This makes sure we don't convert "- - 5" to "--5", which is a comment
1615        this_sql = self.sql(expression, "this")
1616        sep = " " if this_sql[0] == "-" else ""
1617        return f"-{sep}{this_sql}"
def not_sql(self, expression: sqlglot.expressions.Not) -> str:
1619    def not_sql(self, expression: exp.Not) -> str:
1620        return f"NOT {self.sql(expression, 'this')}"
def alias_sql(self, expression: sqlglot.expressions.Alias) -> str:
1622    def alias_sql(self, expression: exp.Alias) -> str:
1623        to_sql = self.sql(expression, "alias")
1624        to_sql = f" AS {to_sql}" if to_sql else ""
1625        return f"{self.sql(expression, 'this')}{to_sql}"
def aliases_sql(self, expression: sqlglot.expressions.Aliases) -> str:
1627    def aliases_sql(self, expression: exp.Aliases) -> str:
1628        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
def attimezone_sql(self, expression: sqlglot.expressions.AtTimeZone) -> str:
1630    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
1631        this = self.sql(expression, "this")
1632        zone = self.sql(expression, "zone")
1633        return f"{this} AT TIME ZONE {zone}"
def add_sql(self, expression: sqlglot.expressions.Add) -> str:
1635    def add_sql(self, expression: exp.Add) -> str:
1636        return self.binary(expression, "+")
def and_sql(self, expression: sqlglot.expressions.And) -> str:
1638    def and_sql(self, expression: exp.And) -> str:
1639        return self.connector_sql(expression, "AND")
def connector_sql(self, expression: sqlglot.expressions.Connector, op: str) -> str:
1641    def connector_sql(self, expression: exp.Connector, op: str) -> str:
1642        if not self.pretty:
1643            return self.binary(expression, op)
1644
1645        sqls = tuple(self.sql(e) for e in expression.flatten(unnest=False))
1646        sep = "\n" if self.text_width(sqls) > self._max_text_width else " "
1647        return f"{sep}{op} ".join(sqls)
def bitwiseand_sql(self, expression: sqlglot.expressions.BitwiseAnd) -> str:
1649    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
1650        return self.binary(expression, "&")
def bitwiseleftshift_sql(self, expression: sqlglot.expressions.BitwiseLeftShift) -> str:
1652    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
1653        return self.binary(expression, "<<")
def bitwisenot_sql(self, expression: sqlglot.expressions.BitwiseNot) -> str:
1655    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
1656        return f"~{self.sql(expression, 'this')}"
def bitwiseor_sql(self, expression: sqlglot.expressions.BitwiseOr) -> str:
1658    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
1659        return self.binary(expression, "|")
def bitwiserightshift_sql(self, expression: sqlglot.expressions.BitwiseRightShift) -> str:
1661    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
1662        return self.binary(expression, ">>")
def bitwisexor_sql(self, expression: sqlglot.expressions.BitwiseXor) -> str:
1664    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
1665        return self.binary(expression, "^")
def cast_sql(self, expression: sqlglot.expressions.Cast) -> str:
1667    def cast_sql(self, expression: exp.Cast) -> str:
1668        return f"CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')})"
def currentdate_sql(self, expression: sqlglot.expressions.CurrentDate) -> str:
1670    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
1671        zone = self.sql(expression, "this")
1672        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
def collate_sql(self, expression: sqlglot.expressions.Collate) -> str:
1674    def collate_sql(self, expression: exp.Collate) -> str:
1675        return self.binary(expression, "COLLATE")
def command_sql(self, expression: sqlglot.expressions.Command) -> str:
1677    def command_sql(self, expression: exp.Command) -> str:
1678        return f"{self.sql(expression, 'this').upper()} {expression.text('expression').strip()}"
def comment_sql(self, expression: sqlglot.expressions.Comment) -> str:
1680    def comment_sql(self, expression: exp.Comment) -> str:
1681        this = self.sql(expression, "this")
1682        kind = expression.args["kind"]
1683        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1684        expression_sql = self.sql(expression, "expression")
1685        return f"COMMENT{exists_sql}ON {kind} {this} IS {expression_sql}"
def transaction_sql(self, expression: sqlglot.expressions.Transaction) -> str:
1687    def transaction_sql(self, expression: exp.Transaction) -> str:
1688        return "BEGIN"
def commit_sql(self, expression: sqlglot.expressions.Commit) -> str:
1690    def commit_sql(self, expression: exp.Commit) -> str:
1691        chain = expression.args.get("chain")
1692        if chain is not None:
1693            chain = " AND CHAIN" if chain else " AND NO CHAIN"
1694
1695        return f"COMMIT{chain or ''}"
def rollback_sql(self, expression: sqlglot.expressions.Rollback) -> str:
1697    def rollback_sql(self, expression: exp.Rollback) -> str:
1698        savepoint = expression.args.get("savepoint")
1699        savepoint = f" TO {savepoint}" if savepoint else ""
1700        return f"ROLLBACK{savepoint}"
def altercolumn_sql(self, expression: sqlglot.expressions.AlterColumn) -> str:
1702    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
1703        this = self.sql(expression, "this")
1704
1705        dtype = self.sql(expression, "dtype")
1706        if dtype:
1707            collate = self.sql(expression, "collate")
1708            collate = f" COLLATE {collate}" if collate else ""
1709            using = self.sql(expression, "using")
1710            using = f" USING {using}" if using else ""
1711            return f"ALTER COLUMN {this} TYPE {dtype}{collate}{using}"
1712
1713        default = self.sql(expression, "default")
1714        if default:
1715            return f"ALTER COLUMN {this} SET DEFAULT {default}"
1716
1717        if not expression.args.get("drop"):
1718            self.unsupported("Unsupported ALTER COLUMN syntax")
1719
1720        return f"ALTER COLUMN {this} DROP DEFAULT"
def renametable_sql(self, expression: sqlglot.expressions.RenameTable) -> str:
1722    def renametable_sql(self, expression: exp.RenameTable) -> str:
1723        this = self.sql(expression, "this")
1724        return f"RENAME TO {this}"
def altertable_sql(self, expression: sqlglot.expressions.AlterTable) -> str:
1726    def altertable_sql(self, expression: exp.AlterTable) -> str:
1727        actions = expression.args["actions"]
1728
1729        if isinstance(actions[0], exp.ColumnDef):
1730            actions = self.expressions(expression, "actions", prefix="ADD COLUMN ")
1731        elif isinstance(actions[0], exp.Schema):
1732            actions = self.expressions(expression, "actions", prefix="ADD COLUMNS ")
1733        elif isinstance(actions[0], exp.Delete):
1734            actions = self.expressions(expression, "actions", flat=True)
1735        else:
1736            actions = self.expressions(expression, "actions")
1737
1738        exists = " IF EXISTS" if expression.args.get("exists") else ""
1739        return f"ALTER TABLE{exists} {self.sql(expression, 'this')} {actions}"
def droppartition_sql(self, expression: sqlglot.expressions.DropPartition) -> str:
1741    def droppartition_sql(self, expression: exp.DropPartition) -> str:
1742        expressions = self.expressions(expression)
1743        exists = " IF EXISTS " if expression.args.get("exists") else " "
1744        return f"DROP{exists}{expressions}"
def addconstraint_sql(self, expression: sqlglot.expressions.AddConstraint) -> str:
1746    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
1747        this = self.sql(expression, "this")
1748        expression_ = self.sql(expression, "expression")
1749        add_constraint = f"ADD CONSTRAINT {this}" if this else "ADD"
1750
1751        enforced = expression.args.get("enforced")
1752        if enforced is not None:
1753            return f"{add_constraint} CHECK ({expression_}){' ENFORCED' if enforced else ''}"
1754
1755        return f"{add_constraint} {expression_}"
def distinct_sql(self, expression: sqlglot.expressions.Distinct) -> str:
1757    def distinct_sql(self, expression: exp.Distinct) -> str:
1758        this = self.expressions(expression, flat=True)
1759        this = f" {this}" if this else ""
1760
1761        on = self.sql(expression, "on")
1762        on = f" ON {on}" if on else ""
1763        return f"DISTINCT{this}{on}"
def ignorenulls_sql(self, expression: sqlglot.expressions.IgnoreNulls) -> str:
1765    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
1766        return f"{self.sql(expression, 'this')} IGNORE NULLS"
def respectnulls_sql(self, expression: sqlglot.expressions.RespectNulls) -> str:
1768    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
1769        return f"{self.sql(expression, 'this')} RESPECT NULLS"
def intdiv_sql(self, expression: sqlglot.expressions.IntDiv) -> str:
1771    def intdiv_sql(self, expression: exp.IntDiv) -> str:
1772        return self.sql(
1773            exp.Cast(
1774                this=exp.Div(this=expression.this, expression=expression.expression),
1775                to=exp.DataType(this=exp.DataType.Type.INT),
1776            )
1777        )
def dpipe_sql(self, expression: sqlglot.expressions.DPipe) -> str:
1779    def dpipe_sql(self, expression: exp.DPipe) -> str:
1780        return self.binary(expression, "||")
def div_sql(self, expression: sqlglot.expressions.Div) -> str:
1782    def div_sql(self, expression: exp.Div) -> str:
1783        return self.binary(expression, "/")
def overlaps_sql(self, expression: sqlglot.expressions.Overlaps) -> str:
1785    def overlaps_sql(self, expression: exp.Overlaps) -> str:
1786        return self.binary(expression, "OVERLAPS")
def distance_sql(self, expression: sqlglot.expressions.Distance) -> str:
1788    def distance_sql(self, expression: exp.Distance) -> str:
1789        return self.binary(expression, "<->")
def dot_sql(self, expression: sqlglot.expressions.Dot) -> str:
1791    def dot_sql(self, expression: exp.Dot) -> str:
1792        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
def eq_sql(self, expression: sqlglot.expressions.EQ) -> str:
1794    def eq_sql(self, expression: exp.EQ) -> str:
1795        return self.binary(expression, "=")
def escape_sql(self, expression: sqlglot.expressions.Escape) -> str:
1797    def escape_sql(self, expression: exp.Escape) -> str:
1798        return self.binary(expression, "ESCAPE")
def glob_sql(self, expression: sqlglot.expressions.Glob) -> str:
1800    def glob_sql(self, expression: exp.Glob) -> str:
1801        return self.binary(expression, "GLOB")
def gt_sql(self, expression: sqlglot.expressions.GT) -> str:
1803    def gt_sql(self, expression: exp.GT) -> str:
1804        return self.binary(expression, ">")
def gte_sql(self, expression: sqlglot.expressions.GTE) -> str:
1806    def gte_sql(self, expression: exp.GTE) -> str:
1807        return self.binary(expression, ">=")
def ilike_sql(self, expression: sqlglot.expressions.ILike) -> str:
1809    def ilike_sql(self, expression: exp.ILike) -> str:
1810        return self.binary(expression, "ILIKE")
def is_sql(self, expression: sqlglot.expressions.Is) -> str:
1812    def is_sql(self, expression: exp.Is) -> str:
1813        return self.binary(expression, "IS")
def like_sql(self, expression: sqlglot.expressions.Like) -> str:
1815    def like_sql(self, expression: exp.Like) -> str:
1816        return self.binary(expression, "LIKE")
def similarto_sql(self, expression: sqlglot.expressions.SimilarTo) -> str:
1818    def similarto_sql(self, expression: exp.SimilarTo) -> str:
1819        return self.binary(expression, "SIMILAR TO")
def lt_sql(self, expression: sqlglot.expressions.LT) -> str:
1821    def lt_sql(self, expression: exp.LT) -> str:
1822        return self.binary(expression, "<")
def lte_sql(self, expression: sqlglot.expressions.LTE) -> str:
1824    def lte_sql(self, expression: exp.LTE) -> str:
1825        return self.binary(expression, "<=")
def mod_sql(self, expression: sqlglot.expressions.Mod) -> str:
1827    def mod_sql(self, expression: exp.Mod) -> str:
1828        return self.binary(expression, "%")
def mul_sql(self, expression: sqlglot.expressions.Mul) -> str:
1830    def mul_sql(self, expression: exp.Mul) -> str:
1831        return self.binary(expression, "*")
def neq_sql(self, expression: sqlglot.expressions.NEQ) -> str:
1833    def neq_sql(self, expression: exp.NEQ) -> str:
1834        return self.binary(expression, "<>")
def nullsafeeq_sql(self, expression: sqlglot.expressions.NullSafeEQ) -> str:
1836    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
1837        return self.binary(expression, "IS NOT DISTINCT FROM")
def nullsafeneq_sql(self, expression: sqlglot.expressions.NullSafeNEQ) -> str:
1839    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
1840        return self.binary(expression, "IS DISTINCT FROM")
def or_sql(self, expression: sqlglot.expressions.Or) -> str:
1842    def or_sql(self, expression: exp.Or) -> str:
1843        return self.connector_sql(expression, "OR")
def slice_sql(self, expression: sqlglot.expressions.Slice) -> str:
1845    def slice_sql(self, expression: exp.Slice) -> str:
1846        return self.binary(expression, ":")
def sub_sql(self, expression: sqlglot.expressions.Sub) -> str:
1848    def sub_sql(self, expression: exp.Sub) -> str:
1849        return self.binary(expression, "-")
def trycast_sql(self, expression: sqlglot.expressions.TryCast) -> str:
1851    def trycast_sql(self, expression: exp.TryCast) -> str:
1852        return f"TRY_CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')})"
def use_sql(self, expression: sqlglot.expressions.Use) -> str:
1854    def use_sql(self, expression: exp.Use) -> str:
1855        kind = self.sql(expression, "kind")
1856        kind = f" {kind}" if kind else ""
1857        this = self.sql(expression, "this")
1858        this = f" {this}" if this else ""
1859        return f"USE{kind}{this}"
def binary(self, expression: sqlglot.expressions.Binary, op: str) -> str:
1861    def binary(self, expression: exp.Binary, op: str) -> str:
1862        return f"{self.sql(expression, 'this')} {op} {self.sql(expression, 'expression')}"
def function_fallback_sql(self, expression: sqlglot.expressions.Func) -> str:
1864    def function_fallback_sql(self, expression: exp.Func) -> str:
1865        args = []
1866        for arg_value in expression.args.values():
1867            if isinstance(arg_value, list):
1868                for value in arg_value:
1869                    args.append(value)
1870            else:
1871                args.append(arg_value)
1872
1873        return self.func(expression.sql_name(), *args)
def func( self, name: str, *args: Union[str, sqlglot.expressions.Expression, NoneType]) -> str:
1875    def func(self, name: str, *args: t.Optional[exp.Expression | str]) -> str:
1876        return f"{self.normalize_func(name)}({self.format_args(*args)})"
def format_args(self, *args: Union[str, sqlglot.expressions.Expression, NoneType]) -> str:
1878    def format_args(self, *args: t.Optional[str | exp.Expression]) -> str:
1879        arg_sqls = tuple(self.sql(arg) for arg in args if arg is not None)
1880        if self.pretty and self.text_width(arg_sqls) > self._max_text_width:
1881            return self.indent("\n" + f",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True)
1882        return ", ".join(arg_sqls)
def text_width(self, args: Iterable) -> int:
1884    def text_width(self, args: t.Iterable) -> int:
1885        return sum(len(arg) for arg in args)
def format_time(self, expression: sqlglot.expressions.Expression) -> Optional[str]:
1887    def format_time(self, expression: exp.Expression) -> t.Optional[str]:
1888        return format_time(self.sql(expression, "format"), self.time_mapping, self.time_trie)
def expressions( self, expression: sqlglot.expressions.Expression, key: Optional[str] = None, flat: bool = False, indent: bool = True, sep: str = ', ', prefix: str = '') -> str:
1890    def expressions(
1891        self,
1892        expression: exp.Expression,
1893        key: t.Optional[str] = None,
1894        flat: bool = False,
1895        indent: bool = True,
1896        sep: str = ", ",
1897        prefix: str = "",
1898    ) -> str:
1899        expressions = expression.args.get(key or "expressions")
1900
1901        if not expressions:
1902            return ""
1903
1904        if flat:
1905            return sep.join(self.sql(e) for e in expressions)
1906
1907        num_sqls = len(expressions)
1908
1909        # These are calculated once in case we have the leading_comma / pretty option set, correspondingly
1910        pad = " " * self.pad
1911        stripped_sep = sep.strip()
1912
1913        result_sqls = []
1914        for i, e in enumerate(expressions):
1915            sql = self.sql(e, comment=False)
1916            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
1917
1918            if self.pretty:
1919                if self._leading_comma:
1920                    result_sqls.append(f"{sep if i > 0 else pad}{prefix}{sql}{comments}")
1921                else:
1922                    result_sqls.append(
1923                        f"{prefix}{sql}{stripped_sep if i + 1 < num_sqls else ''}{comments}"
1924                    )
1925            else:
1926                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
1927
1928        result_sql = "\n".join(result_sqls) if self.pretty else "".join(result_sqls)
1929        return self.indent(result_sql, skip_first=False) if indent else result_sql
def op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
1931    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
1932        flat = flat or isinstance(expression.parent, exp.Properties)
1933        expressions_sql = self.expressions(expression, flat=flat)
1934        if flat:
1935            return f"{op} {expressions_sql}"
1936        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
def naked_property(self, expression: sqlglot.expressions.Property) -> str:
1938    def naked_property(self, expression: exp.Property) -> str:
1939        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
1940        if not property_name:
1941            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
1942        return f"{property_name} {self.sql(expression, 'this')}"
def set_operation(self, expression: sqlglot.expressions.Expression, op: str) -> str:
1944    def set_operation(self, expression: exp.Expression, op: str) -> str:
1945        this = self.sql(expression, "this")
1946        op = self.seg(op)
1947        return self.query_modifiers(
1948            expression, f"{this}{op}{self.sep()}{self.sql(expression, 'expression')}"
1949        )
def tag_sql(self, expression: sqlglot.expressions.Tag) -> str:
1951    def tag_sql(self, expression: exp.Tag) -> str:
1952        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
def token_sql(self, token_type: sqlglot.tokens.TokenType) -> str:
1954    def token_sql(self, token_type: TokenType) -> str:
1955        return self.TOKEN_MAPPING.get(token_type, token_type.name)
def userdefinedfunction_sql(self, expression: sqlglot.expressions.UserDefinedFunction) -> str:
1957    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
1958        this = self.sql(expression, "this")
1959        expressions = self.no_identify(self.expressions, expression)
1960        expressions = (
1961            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
1962        )
1963        return f"{this}{expressions}"
def joinhint_sql(self, expression: sqlglot.expressions.JoinHint) -> str:
1965    def joinhint_sql(self, expression: exp.JoinHint) -> str:
1966        this = self.sql(expression, "this")
1967        expressions = self.expressions(expression, flat=True)
1968        return f"{this}({expressions})"
def kwarg_sql(self, expression: sqlglot.expressions.Kwarg) -> str:
1970    def kwarg_sql(self, expression: exp.Kwarg) -> str:
1971        return self.binary(expression, "=>")
def when_sql(self, expression: sqlglot.expressions.When) -> str:
1973    def when_sql(self, expression: exp.When) -> str:
1974        matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
1975        source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
1976        condition = self.sql(expression, "condition")
1977        condition = f" AND {condition}" if condition else ""
1978
1979        then_expression = expression.args.get("then")
1980        if isinstance(then_expression, exp.Insert):
1981            then = f"INSERT {self.sql(then_expression, 'this')}"
1982            if "expression" in then_expression.args:
1983                then += f" VALUES {self.sql(then_expression, 'expression')}"
1984        elif isinstance(then_expression, exp.Update):
1985            if isinstance(then_expression.args.get("expressions"), exp.Star):
1986                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
1987            else:
1988                then = f"UPDATE SET {self.expressions(then_expression, flat=True)}"
1989        else:
1990            then = self.sql(then_expression)
1991        return f"WHEN {matched}{source}{condition} THEN {then}"
def merge_sql(self, expression: sqlglot.expressions.Merge) -> str:
1993    def merge_sql(self, expression: exp.Merge) -> str:
1994        this = self.sql(expression, "this")
1995        using = f"USING {self.sql(expression, 'using')}"
1996        on = f"ON {self.sql(expression, 'on')}"
1997        return f"MERGE INTO {this} {using} {on} {self.expressions(expression, sep=' ')}"
def tochar_sql(self, expression: sqlglot.expressions.ToChar) -> str:
1999    def tochar_sql(self, expression: exp.ToChar) -> str:
2000        if expression.args.get("format"):
2001            self.unsupported("Format argument unsupported for TO_CHAR/TO_VARCHAR function")
2002
2003        return self.sql(exp.cast(expression.this, "text"))