Edit on GitHub

sqlglot.generator

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