sqlglot.generator API documentation

Edit on GitHub

# sqlglot.generator

View Source

       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: View Source

      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) View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    275    def seg(self, sql: str, sep: str = " ") -> str:
    276        return f"{self.sep(sep)}{sql}"

def pad_comment(self, comment: str) -> str: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    431    def autoincrementcolumnconstraint_sql(self, _) -> str:
    432        return self.token_sql(TokenType.AUTO_INCREMENT)

def checkcolumnconstraint_sql(self, expression: sqlglot.expressions.CheckColumnConstraint) -> str: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    479    def uniquecolumnconstraint_sql(self, _) -> str:
    480        return "UNIQUE"

def create_sql(self, expression: sqlglot.expressions.Create) -> str: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    636    def bitstring_sql(self, expression: exp.BitString) -> str:
    637        return self.sql(expression, "this")

def hexstring_sql(self, expression: sqlglot.expressions.HexString) -> str: View Source

    639    def hexstring_sql(self, expression: exp.HexString) -> str:
    640        return self.sql(expression, "this")

def datatype_sql(self, expression: sqlglot.expressions.DataType) -> str: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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]]: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    933    def pseudotype_sql(self, expression: exp.PseudoType) -> str:
    934        return expression.name.upper()

def rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    1031    def var_sql(self, expression: exp.Var) -> str:
    1032        return self.sql(expression, "this")

def into_sql(self, expression: sqlglot.expressions.Into) -> str: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    1164    def null_sql(self, *_) -> str:
    1165        return "NULL"

def boolean_sql(self, expression: sqlglot.expressions.Boolean) -> str: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    1568    def add_sql(self, expression: exp.Add) -> str:
    1569        return self.binary(expression, "+")

def and_sql(self, expression: sqlglot.expressions.And) -> str: View Source

    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: View Source

    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: View Source

    1582    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
    1583        return self.binary(expression, "&")

def bitwiseleftshift_sql(self, expression: sqlglot.expressions.BitwiseLeftShift) -> str: View Source

    1585    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
    1586        return self.binary(expression, "<<")

def bitwisenot_sql(self, expression: sqlglot.expressions.BitwiseNot) -> str: View Source

    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: View Source

    1591    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
    1592        return self.binary(expression, "|")

def bitwiserightshift_sql(self, expression: sqlglot.expressions.BitwiseRightShift) -> str: View Source

    1594    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
    1595        return self.binary(expression, ">>")

def bitwisexor_sql(self, expression: sqlglot.expressions.BitwiseXor) -> str: View Source

    1597    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
    1598        return self.binary(expression, "^")

def cast_sql(self, expression: sqlglot.expressions.Cast) -> str: View Source

    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: View Source

    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: View Source

    1607    def collate_sql(self, expression: exp.Collate) -> str:
    1608        return self.binary(expression, "COLLATE")

def command_sql(self, expression: sqlglot.expressions.Command) -> str: View Source

    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: View Source

    1613    def transaction_sql(self, *_) -> str:
    1614        return "BEGIN"

def commit_sql(self, expression: sqlglot.expressions.Commit) -> str: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    1705    def dpipe_sql(self, expression: exp.DPipe) -> str:
    1706        return self.binary(expression, "||")

def div_sql(self, expression: sqlglot.expressions.Div) -> str: View Source

    1708    def div_sql(self, expression: exp.Div) -> str:
    1709        return self.binary(expression, "/")

def distance_sql(self, expression: sqlglot.expressions.Distance) -> str: View Source

    1711    def distance_sql(self, expression: exp.Distance) -> str:
    1712        return self.binary(expression, "<->")

def dot_sql(self, expression: sqlglot.expressions.Dot) -> str: View Source

    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: View Source

    1717    def eq_sql(self, expression: exp.EQ) -> str:
    1718        return self.binary(expression, "=")

def escape_sql(self, expression: sqlglot.expressions.Escape) -> str: View Source

    1720    def escape_sql(self, expression: exp.Escape) -> str:
    1721        return self.binary(expression, "ESCAPE")

def glob_sql(self, expression: sqlglot.expressions.Glob) -> str: View Source

    1723    def glob_sql(self, expression: exp.Glob) -> str:
    1724        return self.binary(expression, "GLOB")

def gt_sql(self, expression: sqlglot.expressions.GT) -> str: View Source

    1726    def gt_sql(self, expression: exp.GT) -> str:
    1727        return self.binary(expression, ">")

def gte_sql(self, expression: sqlglot.expressions.GTE) -> str: View Source

    1729    def gte_sql(self, expression: exp.GTE) -> str:
    1730        return self.binary(expression, ">=")

def ilike_sql(self, expression: sqlglot.expressions.ILike) -> str: View Source

    1732    def ilike_sql(self, expression: exp.ILike) -> str:
    1733        return self.binary(expression, "ILIKE")

def is_sql(self, expression: sqlglot.expressions.Is) -> str: View Source

    1735    def is_sql(self, expression: exp.Is) -> str:
    1736        return self.binary(expression, "IS")

def like_sql(self, expression: sqlglot.expressions.Like) -> str: View Source

    1738    def like_sql(self, expression: exp.Like) -> str:
    1739        return self.binary(expression, "LIKE")

def similarto_sql(self, expression: sqlglot.expressions.SimilarTo) -> str: View Source

    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: View Source

    1744    def lt_sql(self, expression: exp.LT) -> str:
    1745        return self.binary(expression, "<")

def lte_sql(self, expression: sqlglot.expressions.LTE) -> str: View Source

    1747    def lte_sql(self, expression: exp.LTE) -> str:
    1748        return self.binary(expression, "<=")

def mod_sql(self, expression: sqlglot.expressions.Mod) -> str: View Source

    1750    def mod_sql(self, expression: exp.Mod) -> str:
    1751        return self.binary(expression, "%")

def mul_sql(self, expression: sqlglot.expressions.Mul) -> str: View Source

    1753    def mul_sql(self, expression: exp.Mul) -> str:
    1754        return self.binary(expression, "*")

def neq_sql(self, expression: sqlglot.expressions.NEQ) -> str: View Source

    1756    def neq_sql(self, expression: exp.NEQ) -> str:
    1757        return self.binary(expression, "<>")

def nullsafeeq_sql(self, expression: sqlglot.expressions.NullSafeEQ) -> str: View Source

    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: View Source

    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: View Source

    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: View Source

    1768    def slice_sql(self, expression: exp.Slice) -> str:
    1769        return self.binary(expression, ":")

def sub_sql(self, expression: sqlglot.expressions.Sub) -> str: View Source

    1771    def sub_sql(self, expression: exp.Sub) -> str:
    1772        return self.binary(expression, "-")

def trycast_sql(self, expression: sqlglot.expressions.TryCast) -> str: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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]: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    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: View Source

    1894    def kwarg_sql(self, expression: exp.Kwarg) -> str:
    1895        return self.binary(expression, "=>")

def when_sql(self, expression: sqlglot.expressions.When) -> str: View Source

    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: View Source

    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=' ')}"