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

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