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