sqlglot.generator API documentation

Edit on GitHub

# sqlglot.generator

View Source

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

class Generator: View Source

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

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

###### Arguments:

- time_mapping (dict): the dictionary of custom time mappings in which the key represents a python time format and the output the target time format
- time_trie (trie): a trie of the time_mapping keys
- pretty (bool): if set to True the returned string will be formatted. Default: False.
- quote_start (str): specifies which starting character to use to delimit quotes. Default: '.
- quote_end (str): specifies which ending character to use to delimit quotes. Default: '.
- identifier_start (str): specifies which starting character to use to delimit identifiers. Default: ".
- identifier_end (str): specifies which ending character to use to delimit identifiers. Default: ".
- identify (bool): if set to True all identifiers will be delimited by the corresponding character.
- normalize (bool): if set to True all identifiers will lower cased
- string_escape (str): specifies a string escape character. Default: '.
- identifier_escape (str): specifies an identifier escape character. Default: ".
- pad (int): determines padding in a formatted string. Default: 2.
- indent (int): determines the size of indentation in a formatted string. Default: 4.
- unnest_column_only (bool): if true unnest table aliases are considered only as column aliases
- normalize_functions (str): normalize function names, "upper", "lower", or None Default: "upper"
- alias_post_tablesample (bool): if the table alias comes after tablesample Default: False
- unsupported_level (ErrorLevel): determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- null_ordering (str): Indicates the default null ordering method to use if not explicitly set. Options are "nulls_are_small", "nulls_are_large", "nulls_are_last". Default: "nulls_are_small"
- max_unsupported (int): Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
- leading_comma (bool): if the the comma is leading or trailing in select statements Default: False
- max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
- comments: Whether or not to preserve comments in the output SQL code. Default: True

Generator( time_mapping=None, time_trie=None, pretty=None, quote_start=None, quote_end=None, identifier_start=None, identifier_end=None, identify=False, normalize=False, string_escape=None, identifier_escape=None, pad=2, indent=2, index_offset=0, unnest_column_only=False, alias_post_tablesample=False, normalize_functions='upper', unsupported_level=<ErrorLevel.WARN: 'WARN'>, null_ordering=None, max_unsupported=3, leading_comma=False, max_text_width=80, comments=True) View Source

    200    def __init__(
    201        self,
    202        time_mapping=None,
    203        time_trie=None,
    204        pretty=None,
    205        quote_start=None,
    206        quote_end=None,
    207        identifier_start=None,
    208        identifier_end=None,
    209        identify=False,
    210        normalize=False,
    211        string_escape=None,
    212        identifier_escape=None,
    213        pad=2,
    214        indent=2,
    215        index_offset=0,
    216        unnest_column_only=False,
    217        alias_post_tablesample=False,
    218        normalize_functions="upper",
    219        unsupported_level=ErrorLevel.WARN,
    220        null_ordering=None,
    221        max_unsupported=3,
    222        leading_comma=False,
    223        max_text_width=80,
    224        comments=True,
    225    ):
    226        import sqlglot
    227
    228        self.time_mapping = time_mapping or {}
    229        self.time_trie = time_trie
    230        self.pretty = pretty if pretty is not None else sqlglot.pretty
    231        self.quote_start = quote_start or "'"
    232        self.quote_end = quote_end or "'"
    233        self.identifier_start = identifier_start or '"'
    234        self.identifier_end = identifier_end or '"'
    235        self.identify = identify
    236        self.normalize = normalize
    237        self.string_escape = string_escape or "'"
    238        self.identifier_escape = identifier_escape or '"'
    239        self.pad = pad
    240        self.index_offset = index_offset
    241        self.unnest_column_only = unnest_column_only
    242        self.alias_post_tablesample = alias_post_tablesample
    243        self.normalize_functions = normalize_functions
    244        self.unsupported_level = unsupported_level
    245        self.unsupported_messages = []
    246        self.max_unsupported = max_unsupported
    247        self.null_ordering = null_ordering
    248        self._indent = indent
    249        self._escaped_quote_end = self.string_escape + self.quote_end
    250        self._escaped_identifier_end = self.identifier_escape + self.identifier_end
    251        self._leading_comma = leading_comma
    252        self._max_text_width = max_text_width
    253        self._comments = comments

def generate(self, expression: Optional[sqlglot.expressions.Expression]) -> str: View Source

    255    def generate(self, expression: t.Optional[exp.Expression]) -> str:
    256        """
    257        Generates a SQL string by interpreting the given syntax tree.
    258
    259        Args
    260            expression: the syntax tree.
    261
    262        Returns
    263            the SQL string.
    264        """
    265        self.unsupported_messages = []
    266        sql = self.sql(expression).strip()
    267
    268        if self.unsupported_level == ErrorLevel.IGNORE:
    269            return sql
    270
    271        if self.unsupported_level == ErrorLevel.WARN:
    272            for msg in self.unsupported_messages:
    273                logger.warning(msg)
    274        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
    275            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
    276
    277        if self.pretty:
    278            sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
    279        return sql

Generates a SQL string by interpreting the given syntax tree.

Args expression: the syntax tree.

Returns the SQL string.

def unsupported(self, message: str) -> None: View Source

    281    def unsupported(self, message: str) -> None:
    282        if self.unsupported_level == ErrorLevel.IMMEDIATE:
    283            raise UnsupportedError(message)
    284        self.unsupported_messages.append(message)

def sep(self, sep: str = ' ') -> str: View Source

    286    def sep(self, sep: str = " ") -> str:
    287        return f"{sep.strip()}\n" if self.pretty else sep

def seg(self, sql: str, sep: str = ' ') -> str: View Source

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

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

    292    def pad_comment(self, comment: str) -> str:
    293        comment = " " + comment if comment[0].strip() else comment
    294        comment = comment + " " if comment[-1].strip() else comment
    295        return comment

def maybe_comment(self, sql: str, expression: sqlglot.expressions.Expression) -> str: View Source

    297    def maybe_comment(self, sql: str, expression: exp.Expression) -> str:
    298        comments = expression.comments if self._comments else None
    299
    300        if not comments:
    301            return sql
    302
    303        sep = "\n" if self.pretty else " "
    304        comments_sql = sep.join(
    305            f"/*{self.pad_comment(comment)}*/" for comment in comments if comment
    306        )
    307
    308        if not comments_sql:
    309            return sql
    310
    311        if isinstance(expression, self.WITH_SEPARATED_COMMENTS):
    312            return f"{comments_sql}{self.sep()}{sql}"
    313
    314        return f"{sql} {comments_sql}"

def wrap(self, expression: sqlglot.expressions.Expression | str) -> str: View Source

    316    def wrap(self, expression: exp.Expression | str) -> str:
    317        this_sql = self.indent(
    318            self.sql(expression)
    319            if isinstance(expression, (exp.Select, exp.Union))
    320            else self.sql(expression, "this"),
    321            level=1,
    322            pad=0,
    323        )
    324        return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"

def no_identify(self, func: Callable[..., str], *args, **kwargs) -> str: View Source

    326    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
    327        original = self.identify
    328        self.identify = False
    329        result = func(*args, **kwargs)
    330        self.identify = original
    331        return result

def normalize_func(self, name: str) -> str: View Source

    333    def normalize_func(self, name: str) -> str:
    334        if self.normalize_functions == "upper":
    335            return name.upper()
    336        if self.normalize_functions == "lower":
    337            return name.lower()
    338        return name

def indent( self, sql: str, level: int = 0, pad: Optional[int] = None, skip_first: bool = False, skip_last: bool = False) -> str: View Source

    340    def indent(
    341        self,
    342        sql: str,
    343        level: int = 0,
    344        pad: t.Optional[int] = None,
    345        skip_first: bool = False,
    346        skip_last: bool = False,
    347    ) -> str:
    348        if not self.pretty:
    349            return sql
    350
    351        pad = self.pad if pad is None else pad
    352        lines = sql.split("\n")
    353
    354        return "\n".join(
    355            line
    356            if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
    357            else f"{' ' * (level * self._indent + pad)}{line}"
    358            for i, line in enumerate(lines)
    359        )

def sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str: View Source

    361    def sql(
    362        self,
    363        expression: t.Optional[str | exp.Expression],
    364        key: t.Optional[str] = None,
    365        comment: bool = True,
    366    ) -> str:
    367        if not expression:
    368            return ""
    369
    370        if isinstance(expression, str):
    371            return expression
    372
    373        if key:
    374            return self.sql(expression.args.get(key))
    375
    376        transform = self.TRANSFORMS.get(expression.__class__)
    377
    378        if callable(transform):
    379            sql = transform(self, expression)
    380        elif transform:
    381            sql = transform
    382        elif isinstance(expression, exp.Expression):
    383            exp_handler_name = f"{expression.key}_sql"
    384
    385            if hasattr(self, exp_handler_name):
    386                sql = getattr(self, exp_handler_name)(expression)
    387            elif isinstance(expression, exp.Func):
    388                sql = self.function_fallback_sql(expression)
    389            elif isinstance(expression, exp.Property):
    390                sql = self.property_sql(expression)
    391            else:
    392                raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
    393        else:
    394            raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
    395
    396        return self.maybe_comment(sql, expression) if self._comments and comment else sql

def uncache_sql(self, expression: sqlglot.expressions.Uncache) -> str: View Source

    398    def uncache_sql(self, expression: exp.Uncache) -> str:
    399        table = self.sql(expression, "this")
    400        exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
    401        return f"UNCACHE TABLE{exists_sql} {table}"

def cache_sql(self, expression: sqlglot.expressions.Cache) -> str: View Source

    403    def cache_sql(self, expression: exp.Cache) -> str:
    404        lazy = " LAZY" if expression.args.get("lazy") else ""
    405        table = self.sql(expression, "this")
    406        options = expression.args.get("options")
    407        options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
    408        sql = self.sql(expression, "expression")
    409        sql = f" AS{self.sep()}{sql}" if sql else ""
    410        sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
    411        return self.prepend_ctes(expression, sql)

def characterset_sql(self, expression: sqlglot.expressions.CharacterSet) -> str: View Source

    413    def characterset_sql(self, expression: exp.CharacterSet) -> str:
    414        if isinstance(expression.parent, exp.Cast):
    415            return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
    416        default = "DEFAULT " if expression.args.get("default") else ""
    417        return f"{default}CHARACTER SET={self.sql(expression, 'this')}"

def column_sql(self, expression: sqlglot.expressions.Column) -> str: View Source

    419    def column_sql(self, expression: exp.Column) -> str:
    420        return ".".join(
    421            self.sql(part)
    422            for part in (
    423                expression.args.get("catalog"),
    424                expression.args.get("db"),
    425                expression.args.get("table"),
    426                expression.args.get("this"),
    427            )
    428            if part
    429        )

def columndef_sql(self, expression: sqlglot.expressions.ColumnDef) -> str: View Source

    431    def columndef_sql(self, expression: exp.ColumnDef) -> str:
    432        column = self.sql(expression, "this")
    433        kind = self.sql(expression, "kind")
    434        constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
    435        exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
    436        kind = f" {kind}" if kind else ""
    437        constraints = f" {constraints}" if constraints else ""
    438
    439        return f"{exists}{column}{kind}{constraints}"

def columnconstraint_sql(self, expression: sqlglot.expressions.ColumnConstraint) -> str: View Source

    441    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
    442        this = self.sql(expression, "this")
    443        kind_sql = self.sql(expression, "kind")
    444        return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql

def autoincrementcolumnconstraint_sql(self, _) -> str: View Source

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

def generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str: View Source

    449    def generatedasidentitycolumnconstraint_sql(
    450        self, expression: exp.GeneratedAsIdentityColumnConstraint
    451    ) -> str:
    452        this = ""
    453        if expression.this is not None:
    454            this = " ALWAYS " if expression.this else " BY DEFAULT "
    455        start = expression.args.get("start")
    456        start = f"START WITH {start}" if start else ""
    457        increment = expression.args.get("increment")
    458        increment = f" INCREMENT BY {increment}" if increment else ""
    459        minvalue = expression.args.get("minvalue")
    460        minvalue = f" MINVALUE {minvalue}" if minvalue else ""
    461        maxvalue = expression.args.get("maxvalue")
    462        maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
    463        cycle = expression.args.get("cycle")
    464        cycle_sql = ""
    465        if cycle is not None:
    466            cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
    467            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
    468        sequence_opts = ""
    469        if start or increment or cycle_sql:
    470            sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
    471            sequence_opts = f" ({sequence_opts.strip()})"
    472        return f"GENERATED{this}AS IDENTITY{sequence_opts}"

def notnullcolumnconstraint_sql(self, expression: sqlglot.expressions.NotNullColumnConstraint) -> str: View Source

    474    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
    475        return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"

def primarykeycolumnconstraint_sql(self, expression: sqlglot.expressions.PrimaryKeyColumnConstraint) -> str: View Source

    477    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
    478        desc = expression.args.get("desc")
    479        if desc is not None:
    480            return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
    481        return f"PRIMARY KEY"

def uniquecolumnconstraint_sql(self, _) -> str: View Source

    483    def uniquecolumnconstraint_sql(self, _) -> str:
    484        return "UNIQUE"

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

    486    def create_sql(self, expression: exp.Create) -> str:
    487        kind = self.sql(expression, "kind").upper()
    488        properties = expression.args.get("properties")
    489        properties_exp = expression.copy()
    490        properties_locs = self.locate_properties(properties) if properties else {}
    491        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
    492            exp.Properties.Location.POST_WITH
    493        ):
    494            properties_exp.set(
    495                "properties",
    496                exp.Properties(
    497                    expressions=[
    498                        *properties_locs[exp.Properties.Location.POST_SCHEMA],
    499                        *properties_locs[exp.Properties.Location.POST_WITH],
    500                    ]
    501                ),
    502            )
    503        if kind == "TABLE" and properties_locs.get(exp.Properties.Location.POST_NAME):
    504            this_name = self.sql(expression.this, "this")
    505            this_properties = self.properties(
    506                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_NAME]),
    507                wrapped=False,
    508            )
    509            this_schema = f"({self.expressions(expression.this)})"
    510            this = f"{this_name}, {this_properties} {this_schema}"
    511            properties_sql = ""
    512        else:
    513            this = self.sql(expression, "this")
    514            properties_sql = self.sql(properties_exp, "properties")
    515        begin = " BEGIN" if expression.args.get("begin") else ""
    516        expression_sql = self.sql(expression, "expression")
    517        if expression_sql:
    518            expression_sql = f"{begin}{self.sep()}{expression_sql}"
    519
    520            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
    521                if properties_locs.get(exp.Properties.Location.POST_ALIAS):
    522                    postalias_props_sql = self.properties(
    523                        exp.Properties(
    524                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
    525                        ),
    526                        wrapped=False,
    527                    )
    528                    expression_sql = f" AS {postalias_props_sql}{expression_sql}"
    529                else:
    530                    expression_sql = f" AS{expression_sql}"
    531
    532        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
    533        transient = (
    534            " TRANSIENT" if self.CREATE_TRANSIENT and expression.args.get("transient") else ""
    535        )
    536        external = " EXTERNAL" if expression.args.get("external") else ""
    537        replace = " OR REPLACE" if expression.args.get("replace") else ""
    538        exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
    539        unique = " UNIQUE" if expression.args.get("unique") else ""
    540        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
    541        set_ = " SET" if expression.args.get("set") else ""
    542        multiset = " MULTISET" if expression.args.get("multiset") else ""
    543        global_temporary = " GLOBAL TEMPORARY" if expression.args.get("global_temporary") else ""
    544        volatile = " VOLATILE" if expression.args.get("volatile") else ""
    545        data = expression.args.get("data")
    546        if data is None:
    547            data = ""
    548        elif data:
    549            data = " WITH DATA"
    550        else:
    551            data = " WITH NO DATA"
    552        statistics = expression.args.get("statistics")
    553        if statistics is None:
    554            statistics = ""
    555        elif statistics:
    556            statistics = " AND STATISTICS"
    557        else:
    558            statistics = " AND NO STATISTICS"
    559        no_primary_index = " NO PRIMARY INDEX" if expression.args.get("no_primary_index") else ""
    560
    561        indexes = expression.args.get("indexes")
    562        index_sql = ""
    563        if indexes:
    564            indexes_sql = []
    565            for index in indexes:
    566                ind_unique = " UNIQUE" if index.args.get("unique") else ""
    567                ind_primary = " PRIMARY" if index.args.get("primary") else ""
    568                ind_amp = " AMP" if index.args.get("amp") else ""
    569                ind_name = f" {index.name}" if index.name else ""
    570                ind_columns = (
    571                    f' ({self.expressions(index, key="columns", flat=True)})'
    572                    if index.args.get("columns")
    573                    else ""
    574                )
    575                if index.args.get("primary") and properties_locs.get(
    576                    exp.Properties.Location.POST_INDEX
    577                ):
    578                    postindex_props_sql = self.properties(
    579                        exp.Properties(
    580                            expressions=properties_locs[exp.Properties.Location.POST_INDEX]
    581                        ),
    582                        wrapped=False,
    583                    )
    584                    ind_columns = f"{ind_columns} {postindex_props_sql}"
    585
    586                indexes_sql.append(
    587                    f"{ind_unique}{ind_primary}{ind_amp} INDEX{ind_name}{ind_columns}"
    588                )
    589            index_sql = "".join(indexes_sql)
    590
    591        postcreate_props_sql = ""
    592        if properties_locs.get(exp.Properties.Location.POST_CREATE):
    593            postcreate_props_sql = self.properties(
    594                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
    595                sep=" ",
    596                prefix=" ",
    597                wrapped=False,
    598            )
    599
    600        modifiers = "".join(
    601            (
    602                replace,
    603                temporary,
    604                transient,
    605                external,
    606                unique,
    607                materialized,
    608                set_,
    609                multiset,
    610                global_temporary,
    611                volatile,
    612                postcreate_props_sql,
    613            )
    614        )
    615        no_schema_binding = (
    616            " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
    617        )
    618
    619        post_expression_modifiers = "".join((data, statistics, no_primary_index))
    620
    621        expression_sql = f"CREATE{modifiers} {kind}{exists_sql} {this}{properties_sql}{expression_sql}{post_expression_modifiers}{index_sql}{no_schema_binding}"
    622        return self.prepend_ctes(expression, expression_sql)

def describe_sql(self, expression: sqlglot.expressions.Describe) -> str: View Source

    624    def describe_sql(self, expression: exp.Describe) -> str:
    625        return f"DESCRIBE {self.sql(expression, 'this')}"

def prepend_ctes(self, expression: sqlglot.expressions.Expression, sql: str) -> str: View Source

    627    def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
    628        with_ = self.sql(expression, "with")
    629        if with_:
    630            sql = f"{with_}{self.sep()}{sql}"
    631        return sql

def with_sql(self, expression: sqlglot.expressions.With) -> str: View Source

    633    def with_sql(self, expression: exp.With) -> str:
    634        sql = self.expressions(expression, flat=True)
    635        recursive = "RECURSIVE " if expression.args.get("recursive") else ""
    636
    637        return f"WITH {recursive}{sql}"

def cte_sql(self, expression: sqlglot.expressions.CTE) -> str: View Source

    639    def cte_sql(self, expression: exp.CTE) -> str:
    640        alias = self.sql(expression, "alias")
    641        return f"{alias} AS {self.wrap(expression)}"

def tablealias_sql(self, expression: sqlglot.expressions.TableAlias) -> str: View Source

    643    def tablealias_sql(self, expression: exp.TableAlias) -> str:
    644        alias = self.sql(expression, "this")
    645        columns = self.expressions(expression, key="columns", flat=True)
    646        columns = f"({columns})" if columns else ""
    647        return f"{alias}{columns}"

def bitstring_sql(self, expression: sqlglot.expressions.BitString) -> str: View Source

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

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

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

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

    655    def datatype_sql(self, expression: exp.DataType) -> str:
    656        type_value = expression.this
    657        type_sql = self.TYPE_MAPPING.get(type_value, type_value.value)
    658        nested = ""
    659        interior = self.expressions(expression, flat=True)
    660        values = ""
    661        if interior:
    662            if expression.args.get("nested"):
    663                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
    664                if expression.args.get("values") is not None:
    665                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
    666                    values = (
    667                        f"{delimiters[0]}{self.expressions(expression, 'values')}{delimiters[1]}"
    668                    )
    669            else:
    670                nested = f"({interior})"
    671
    672        return f"{type_sql}{nested}{values}"

def directory_sql(self, expression: sqlglot.expressions.Directory) -> str: View Source

    674    def directory_sql(self, expression: exp.Directory) -> str:
    675        local = "LOCAL " if expression.args.get("local") else ""
    676        row_format = self.sql(expression, "row_format")
    677        row_format = f" {row_format}" if row_format else ""
    678        return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"

def delete_sql(self, expression: sqlglot.expressions.Delete) -> str: View Source

    680    def delete_sql(self, expression: exp.Delete) -> str:
    681        this = self.sql(expression, "this")
    682        this = f" FROM {this}" if this else ""
    683        using_sql = (
    684            f" USING {self.expressions(expression, 'using', sep=', USING ')}"
    685            if expression.args.get("using")
    686            else ""
    687        )
    688        where_sql = self.sql(expression, "where")
    689        sql = f"DELETE{this}{using_sql}{where_sql}"
    690        return self.prepend_ctes(expression, sql)

def drop_sql(self, expression: sqlglot.expressions.Drop) -> str: View Source

    692    def drop_sql(self, expression: exp.Drop) -> str:
    693        this = self.sql(expression, "this")
    694        kind = expression.args["kind"]
    695        exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
    696        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
    697        materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
    698        cascade = " CASCADE" if expression.args.get("cascade") else ""
    699        return f"DROP{temporary}{materialized} {kind}{exists_sql}{this}{cascade}"

def except_sql(self, expression: sqlglot.expressions.Except) -> str: View Source

    701    def except_sql(self, expression: exp.Except) -> str:
    702        return self.prepend_ctes(
    703            expression,
    704            self.set_operation(expression, self.except_op(expression)),
    705        )

def except_op(self, expression: sqlglot.expressions.Except) -> str: View Source

    707    def except_op(self, expression: exp.Except) -> str:
    708        return f"EXCEPT{'' if expression.args.get('distinct') else ' ALL'}"

def fetch_sql(self, expression: sqlglot.expressions.Fetch) -> str: View Source

    710    def fetch_sql(self, expression: exp.Fetch) -> str:
    711        direction = expression.args.get("direction")
    712        direction = f" {direction.upper()}" if direction else ""
    713        count = expression.args.get("count")
    714        count = f" {count}" if count else ""
    715        return f"{self.seg('FETCH')}{direction}{count} ROWS ONLY"

def filter_sql(self, expression: sqlglot.expressions.Filter) -> str: View Source

    717    def filter_sql(self, expression: exp.Filter) -> str:
    718        this = self.sql(expression, "this")
    719        where = self.sql(expression, "expression")[1:]  # where has a leading space
    720        return f"{this} FILTER({where})"

def hint_sql(self, expression: sqlglot.expressions.Hint) -> str: View Source

    722    def hint_sql(self, expression: exp.Hint) -> str:
    723        if self.sql(expression, "this"):
    724            self.unsupported("Hints are not supported")
    725        return ""

def index_sql(self, expression: sqlglot.expressions.Index) -> str: View Source

    727    def index_sql(self, expression: exp.Index) -> str:
    728        this = self.sql(expression, "this")
    729        table = self.sql(expression, "table")
    730        columns = self.sql(expression, "columns")
    731        return f"{this} ON {table} {columns}"

def identifier_sql(self, expression: sqlglot.expressions.Identifier) -> str: View Source

    733    def identifier_sql(self, expression: exp.Identifier) -> str:
    734        text = expression.name
    735        text = text.lower() if self.normalize else text
    736        text = text.replace(self.identifier_end, self._escaped_identifier_end)
    737        if expression.args.get("quoted") or self.identify:
    738            text = f"{self.identifier_start}{text}{self.identifier_end}"
    739        return text

def national_sql(self, expression: sqlglot.expressions.National) -> str: View Source

    741    def national_sql(self, expression: exp.National) -> str:
    742        return f"N{self.sql(expression, 'this')}"

def partition_sql(self, expression: sqlglot.expressions.Partition) -> str: View Source

    744    def partition_sql(self, expression: exp.Partition) -> str:
    745        return f"PARTITION({self.expressions(expression)})"

def properties_sql(self, expression: sqlglot.expressions.Properties) -> str: View Source

    747    def properties_sql(self, expression: exp.Properties) -> str:
    748        root_properties = []
    749        with_properties = []
    750
    751        for p in expression.expressions:
    752            p_loc = self.PROPERTIES_LOCATION[p.__class__]
    753            if p_loc == exp.Properties.Location.POST_WITH:
    754                with_properties.append(p)
    755            elif p_loc == exp.Properties.Location.POST_SCHEMA:
    756                root_properties.append(p)
    757
    758        return self.root_properties(
    759            exp.Properties(expressions=root_properties)
    760        ) + self.with_properties(exp.Properties(expressions=with_properties))

def root_properties(self, properties: sqlglot.expressions.Properties) -> str: View Source

    762    def root_properties(self, properties: exp.Properties) -> str:
    763        if properties.expressions:
    764            return self.sep() + self.expressions(properties, indent=False, sep=" ")
    765        return ""

def properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str: View Source

    767    def properties(
    768        self,
    769        properties: exp.Properties,
    770        prefix: str = "",
    771        sep: str = ", ",
    772        suffix: str = "",
    773        wrapped: bool = True,
    774    ) -> str:
    775        if properties.expressions:
    776            expressions = self.expressions(properties, sep=sep, indent=False)
    777            expressions = self.wrap(expressions) if wrapped else expressions
    778            return f"{prefix}{' ' if prefix and prefix != ' ' else ''}{expressions}{suffix}"
    779        return ""

def with_properties(self, properties: sqlglot.expressions.Properties) -> str: View Source

    781    def with_properties(self, properties: exp.Properties) -> str:
    782        return self.properties(properties, prefix=self.seg("WITH"))

def locate_properties( self, properties: sqlglot.expressions.Properties) -> Dict[sqlglot.expressions.Properties.Location, list[sqlglot.expressions.Property]]: View Source

    784    def locate_properties(
    785        self, properties: exp.Properties
    786    ) -> t.Dict[exp.Properties.Location, list[exp.Property]]:
    787        properties_locs: t.Dict[exp.Properties.Location, list[exp.Property]] = {
    788            key: [] for key in exp.Properties.Location
    789        }
    790
    791        for p in properties.expressions:
    792            p_loc = self.PROPERTIES_LOCATION[p.__class__]
    793            if p_loc == exp.Properties.Location.POST_NAME:
    794                properties_locs[exp.Properties.Location.POST_NAME].append(p)
    795            elif p_loc == exp.Properties.Location.POST_INDEX:
    796                properties_locs[exp.Properties.Location.POST_INDEX].append(p)
    797            elif p_loc == exp.Properties.Location.POST_SCHEMA:
    798                properties_locs[exp.Properties.Location.POST_SCHEMA].append(p)
    799            elif p_loc == exp.Properties.Location.POST_WITH:
    800                properties_locs[exp.Properties.Location.POST_WITH].append(p)
    801            elif p_loc == exp.Properties.Location.POST_CREATE:
    802                properties_locs[exp.Properties.Location.POST_CREATE].append(p)
    803            elif p_loc == exp.Properties.Location.POST_ALIAS:
    804                properties_locs[exp.Properties.Location.POST_ALIAS].append(p)
    805            elif p_loc == exp.Properties.Location.UNSUPPORTED:
    806                self.unsupported(f"Unsupported property {p.key}")
    807
    808        return properties_locs

def property_sql(self, expression: sqlglot.expressions.Property) -> str: View Source

    810    def property_sql(self, expression: exp.Property) -> str:
    811        property_cls = expression.__class__
    812        if property_cls == exp.Property:
    813            return f"{expression.name}={self.sql(expression, 'value')}"
    814
    815        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
    816        if not property_name:
    817            self.unsupported(f"Unsupported property {expression.key}")
    818
    819        return f"{property_name}={self.sql(expression, 'this')}"

def likeproperty_sql(self, expression: sqlglot.expressions.LikeProperty) -> str: View Source

    821    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
    822        options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
    823        options = f" {options}" if options else ""
    824        return f"LIKE {self.sql(expression, 'this')}{options}"

def fallbackproperty_sql(self, expression: sqlglot.expressions.FallbackProperty) -> str: View Source

    826    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
    827        no = "NO " if expression.args.get("no") else ""
    828        protection = " PROTECTION" if expression.args.get("protection") else ""
    829        return f"{no}FALLBACK{protection}"

def journalproperty_sql(self, expression: sqlglot.expressions.JournalProperty) -> str: View Source

    831    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
    832        no = "NO " if expression.args.get("no") else ""
    833        dual = "DUAL " if expression.args.get("dual") else ""
    834        before = "BEFORE " if expression.args.get("before") else ""
    835        return f"{no}{dual}{before}JOURNAL"

def freespaceproperty_sql(self, expression: sqlglot.expressions.FreespaceProperty) -> str: View Source

    837    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
    838        freespace = self.sql(expression, "this")
    839        percent = " PERCENT" if expression.args.get("percent") else ""
    840        return f"FREESPACE={freespace}{percent}"

def afterjournalproperty_sql(self, expression: sqlglot.expressions.AfterJournalProperty) -> str: View Source

    842    def afterjournalproperty_sql(self, expression: exp.AfterJournalProperty) -> str:
    843        no = "NO " if expression.args.get("no") else ""
    844        dual = "DUAL " if expression.args.get("dual") else ""
    845        local = ""
    846        if expression.args.get("local") is not None:
    847            local = "LOCAL " if expression.args.get("local") else "NOT LOCAL "
    848        return f"{no}{dual}{local}AFTER JOURNAL"

def checksumproperty_sql(self, expression: sqlglot.expressions.ChecksumProperty) -> str: View Source

    850    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
    851        if expression.args.get("default"):
    852            property = "DEFAULT"
    853        elif expression.args.get("on"):
    854            property = "ON"
    855        else:
    856            property = "OFF"
    857        return f"CHECKSUM={property}"

def mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str: View Source

    859    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
    860        if expression.args.get("no"):
    861            return "NO MERGEBLOCKRATIO"
    862        if expression.args.get("default"):
    863            return "DEFAULT MERGEBLOCKRATIO"
    864
    865        percent = " PERCENT" if expression.args.get("percent") else ""
    866        return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"

def datablocksizeproperty_sql(self, expression: sqlglot.expressions.DataBlocksizeProperty) -> str: View Source

    868    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
    869        default = expression.args.get("default")
    870        min = expression.args.get("min")
    871        if default is not None or min is not None:
    872            if default:
    873                property = "DEFAULT"
    874            elif min:
    875                property = "MINIMUM"
    876            else:
    877                property = "MAXIMUM"
    878            return f"{property} DATABLOCKSIZE"
    879        else:
    880            units = expression.args.get("units")
    881            units = f" {units}" if units else ""
    882            return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"

def blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str: View Source

    884    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
    885        autotemp = expression.args.get("autotemp")
    886        always = expression.args.get("always")
    887        default = expression.args.get("default")
    888        manual = expression.args.get("manual")
    889        never = expression.args.get("never")
    890
    891        if autotemp is not None:
    892            property = f"AUTOTEMP({self.expressions(autotemp)})"
    893        elif always:
    894            property = "ALWAYS"
    895        elif default:
    896            property = "DEFAULT"
    897        elif manual:
    898            property = "MANUAL"
    899        elif never:
    900            property = "NEVER"
    901        return f"BLOCKCOMPRESSION={property}"

def isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str: View Source

    903    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
    904        no = expression.args.get("no")
    905        no = " NO" if no else ""
    906        concurrent = expression.args.get("concurrent")
    907        concurrent = " CONCURRENT" if concurrent else ""
    908
    909        for_ = ""
    910        if expression.args.get("for_all"):
    911            for_ = " FOR ALL"
    912        elif expression.args.get("for_insert"):
    913            for_ = " FOR INSERT"
    914        elif expression.args.get("for_none"):
    915            for_ = " FOR NONE"
    916        return f"WITH{no}{concurrent} ISOLATED LOADING{for_}"

def lockingproperty_sql(self, expression: sqlglot.expressions.LockingProperty) -> str: View Source

    918    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
    919        kind = expression.args.get("kind")
    920        this: str = f" {this}" if expression.this else ""
    921        for_or_in = expression.args.get("for_or_in")
    922        lock_type = expression.args.get("lock_type")
    923        override = " OVERRIDE" if expression.args.get("override") else ""
    924        return f"LOCKING {kind}{this} {for_or_in} {lock_type}{override}"

def insert_sql(self, expression: sqlglot.expressions.Insert) -> str: View Source

    926    def insert_sql(self, expression: exp.Insert) -> str:
    927        overwrite = expression.args.get("overwrite")
    928
    929        if isinstance(expression.this, exp.Directory):
    930            this = "OVERWRITE " if overwrite else "INTO "
    931        else:
    932            this = "OVERWRITE TABLE " if overwrite else "INTO "
    933
    934        alternative = expression.args.get("alternative")
    935        alternative = f" OR {alternative} " if alternative else " "
    936        this = f"{this}{self.sql(expression, 'this')}"
    937
    938        exists = " IF EXISTS " if expression.args.get("exists") else " "
    939        partition_sql = (
    940            self.sql(expression, "partition") if expression.args.get("partition") else ""
    941        )
    942        expression_sql = self.sql(expression, "expression")
    943        sep = self.sep() if partition_sql else ""
    944        sql = f"INSERT{alternative}{this}{exists}{partition_sql}{sep}{expression_sql}"
    945        return self.prepend_ctes(expression, sql)

def intersect_sql(self, expression: sqlglot.expressions.Intersect) -> str: View Source

    947    def intersect_sql(self, expression: exp.Intersect) -> str:
    948        return self.prepend_ctes(
    949            expression,
    950            self.set_operation(expression, self.intersect_op(expression)),
    951        )

def intersect_op(self, expression: sqlglot.expressions.Intersect) -> str: View Source

    953    def intersect_op(self, expression: exp.Intersect) -> str:
    954        return f"INTERSECT{'' if expression.args.get('distinct') else ' ALL'}"

def introducer_sql(self, expression: sqlglot.expressions.Introducer) -> str: View Source

    956    def introducer_sql(self, expression: exp.Introducer) -> str:
    957        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"

def pseudotype_sql(self, expression: sqlglot.expressions.PseudoType) -> str: View Source

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

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

    962    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
    963        fields = expression.args.get("fields")
    964        fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
    965        escaped = expression.args.get("escaped")
    966        escaped = f" ESCAPED BY {escaped}" if escaped else ""
    967        items = expression.args.get("collection_items")
    968        items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
    969        keys = expression.args.get("map_keys")
    970        keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
    971        lines = expression.args.get("lines")
    972        lines = f" LINES TERMINATED BY {lines}" if lines else ""
    973        null = expression.args.get("null")
    974        null = f" NULL DEFINED AS {null}" if null else ""
    975        return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"

def table_sql(self, expression: sqlglot.expressions.Table, sep: str = ' AS ') -> str: View Source

     977    def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
     978        table = ".".join(
     979            part
     980            for part in [
     981                self.sql(expression, "catalog"),
     982                self.sql(expression, "db"),
     983                self.sql(expression, "this"),
     984            ]
     985            if part
     986        )
     987
     988        alias = self.sql(expression, "alias")
     989        alias = f"{sep}{alias}" if alias else ""
     990        hints = self.expressions(expression, key="hints", sep=", ", flat=True)
     991        hints = f" WITH ({hints})" if hints else ""
     992        laterals = self.expressions(expression, key="laterals", sep="")
     993        joins = self.expressions(expression, key="joins", sep="")
     994        pivots = self.expressions(expression, key="pivots", sep="")
     995        system_time = expression.args.get("system_time")
     996        system_time = f" {self.sql(expression, 'system_time')}" if system_time else ""
     997
     998        if alias and pivots:
     999            pivots = f"{pivots}{alias}"
    1000            alias = ""
    1001
    1002        return f"{table}{system_time}{alias}{hints}{laterals}{joins}{pivots}"

def tablesample_sql(self, expression: sqlglot.expressions.TableSample) -> str: View Source

    1004    def tablesample_sql(self, expression: exp.TableSample) -> str:
    1005        if self.alias_post_tablesample and expression.this.alias:
    1006            this = self.sql(expression.this, "this")
    1007            alias = f" AS {self.sql(expression.this, 'alias')}"
    1008        else:
    1009            this = self.sql(expression, "this")
    1010            alias = ""
    1011        method = self.sql(expression, "method")
    1012        method = f" {method.upper()} " if method else ""
    1013        numerator = self.sql(expression, "bucket_numerator")
    1014        denominator = self.sql(expression, "bucket_denominator")
    1015        field = self.sql(expression, "bucket_field")
    1016        field = f" ON {field}" if field else ""
    1017        bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
    1018        percent = self.sql(expression, "percent")
    1019        percent = f"{percent} PERCENT" if percent else ""
    1020        rows = self.sql(expression, "rows")
    1021        rows = f"{rows} ROWS" if rows else ""
    1022        size = self.sql(expression, "size")
    1023        seed = self.sql(expression, "seed")
    1024        seed = f" SEED ({seed})" if seed else ""
    1025        return f"{this} TABLESAMPLE{method}({bucket}{percent}{rows}{size}){seed}{alias}"

def pivot_sql(self, expression: sqlglot.expressions.Pivot) -> str: View Source

    1027    def pivot_sql(self, expression: exp.Pivot) -> str:
    1028        this = self.sql(expression, "this")
    1029        unpivot = expression.args.get("unpivot")
    1030        direction = "UNPIVOT" if unpivot else "PIVOT"
    1031        expressions = self.expressions(expression, key="expressions")
    1032        field = self.sql(expression, "field")
    1033        return f"{this} {direction}({expressions} FOR {field})"

def tuple_sql(self, expression: sqlglot.expressions.Tuple) -> str: View Source

    1035    def tuple_sql(self, expression: exp.Tuple) -> str:
    1036        return f"({self.expressions(expression, flat=True)})"

def update_sql(self, expression: sqlglot.expressions.Update) -> str: View Source

    1038    def update_sql(self, expression: exp.Update) -> str:
    1039        this = self.sql(expression, "this")
    1040        set_sql = self.expressions(expression, flat=True)
    1041        from_sql = self.sql(expression, "from")
    1042        where_sql = self.sql(expression, "where")
    1043        sql = f"UPDATE {this} SET {set_sql}{from_sql}{where_sql}"
    1044        return self.prepend_ctes(expression, sql)

def values_sql(self, expression: sqlglot.expressions.Values) -> str: View Source

    1046    def values_sql(self, expression: exp.Values) -> str:
    1047        args = self.expressions(expression)
    1048        alias = self.sql(expression, "alias")
    1049        values = f"VALUES{self.seg('')}{args}"
    1050        values = (
    1051            f"({values})"
    1052            if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From))
    1053            else values
    1054        )
    1055        return f"{values} AS {alias}" if alias else values

def var_sql(self, expression: sqlglot.expressions.Var) -> str: View Source

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

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

    1060    def into_sql(self, expression: exp.Into) -> str:
    1061        temporary = " TEMPORARY" if expression.args.get("temporary") else ""
    1062        unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
    1063        return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"

def from_sql(self, expression: sqlglot.expressions.From) -> str: View Source

    1065    def from_sql(self, expression: exp.From) -> str:
    1066        expressions = self.expressions(expression, flat=True)
    1067        return f"{self.seg('FROM')} {expressions}"

def group_sql(self, expression: sqlglot.expressions.Group) -> str: View Source

    1069    def group_sql(self, expression: exp.Group) -> str:
    1070        group_by = self.op_expressions("GROUP BY", expression)
    1071        grouping_sets = self.expressions(expression, key="grouping_sets", indent=False)
    1072        grouping_sets = (
    1073            f"{self.seg('GROUPING SETS')} {self.wrap(grouping_sets)}" if grouping_sets else ""
    1074        )
    1075
    1076        cube = expression.args.get("cube", [])
    1077        if seq_get(cube, 0) is True:
    1078            return f"{group_by}{self.seg('WITH CUBE')}"
    1079        else:
    1080            cube_sql = self.expressions(expression, key="cube", indent=False)
    1081            cube_sql = f"{self.seg('CUBE')} {self.wrap(cube_sql)}" if cube_sql else ""
    1082
    1083        rollup = expression.args.get("rollup", [])
    1084        if seq_get(rollup, 0) is True:
    1085            return f"{group_by}{self.seg('WITH ROLLUP')}"
    1086        else:
    1087            rollup_sql = self.expressions(expression, key="rollup", indent=False)
    1088            rollup_sql = f"{self.seg('ROLLUP')} {self.wrap(rollup_sql)}" if rollup_sql else ""
    1089
    1090        groupings = csv(grouping_sets, cube_sql, rollup_sql, sep=",")
    1091
    1092        if expression.args.get("expressions") and groupings:
    1093            group_by = f"{group_by},"
    1094
    1095        return f"{group_by}{groupings}"

def having_sql(self, expression: sqlglot.expressions.Having) -> str: View Source

    1097    def having_sql(self, expression: exp.Having) -> str:
    1098        this = self.indent(self.sql(expression, "this"))
    1099        return f"{self.seg('HAVING')}{self.sep()}{this}"

def join_sql(self, expression: sqlglot.expressions.Join) -> str: View Source

    1101    def join_sql(self, expression: exp.Join) -> str:
    1102        op_sql = self.seg(
    1103            " ".join(
    1104                op
    1105                for op in (
    1106                    "NATURAL" if expression.args.get("natural") else None,
    1107                    expression.side,
    1108                    expression.kind,
    1109                    "JOIN",
    1110                )
    1111                if op
    1112            )
    1113        )
    1114        on_sql = self.sql(expression, "on")
    1115        using = expression.args.get("using")
    1116
    1117        if not on_sql and using:
    1118            on_sql = csv(*(self.sql(column) for column in using))
    1119
    1120        if on_sql:
    1121            on_sql = self.indent(on_sql, skip_first=True)
    1122            space = self.seg(" " * self.pad) if self.pretty else " "
    1123            if using:
    1124                on_sql = f"{space}USING ({on_sql})"
    1125            else:
    1126                on_sql = f"{space}ON {on_sql}"
    1127
    1128        expression_sql = self.sql(expression, "expression")
    1129        this_sql = self.sql(expression, "this")
    1130        return f"{expression_sql}{op_sql} {this_sql}{on_sql}"

def lambda_sql( self, expression: sqlglot.expressions.Lambda, arrow_sep: str = '->') -> str: View Source

    1132    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str:
    1133        args = self.expressions(expression, flat=True)
    1134        args = f"({args})" if len(args.split(",")) > 1 else args
    1135        return f"{args} {arrow_sep} {self.sql(expression, 'this')}"

def lateral_sql(self, expression: sqlglot.expressions.Lateral) -> str: View Source

    1137    def lateral_sql(self, expression: exp.Lateral) -> str:
    1138        this = self.sql(expression, "this")
    1139
    1140        if isinstance(expression.this, exp.Subquery):
    1141            return f"LATERAL {this}"
    1142
    1143        if expression.args.get("view"):
    1144            alias = expression.args["alias"]
    1145            columns = self.expressions(alias, key="columns", flat=True)
    1146            table = f" {alias.name}" if alias.name else ""
    1147            columns = f" AS {columns}" if columns else ""
    1148            op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
    1149            return f"{op_sql}{self.sep()}{this}{table}{columns}"
    1150
    1151        alias = self.sql(expression, "alias")
    1152        alias = f" AS {alias}" if alias else ""
    1153        return f"LATERAL {this}{alias}"

def limit_sql(self, expression: sqlglot.expressions.Limit) -> str: View Source

    1155    def limit_sql(self, expression: exp.Limit) -> str:
    1156        this = self.sql(expression, "this")
    1157        return f"{this}{self.seg('LIMIT')} {self.sql(expression, 'expression')}"

def offset_sql(self, expression: sqlglot.expressions.Offset) -> str: View Source

    1159    def offset_sql(self, expression: exp.Offset) -> str:
    1160        this = self.sql(expression, "this")
    1161        return f"{this}{self.seg('OFFSET')} {self.sql(expression, 'expression')}"

def lock_sql(self, expression: sqlglot.expressions.Lock) -> str: View Source

    1163    def lock_sql(self, expression: exp.Lock) -> str:
    1164        if self.LOCKING_READS_SUPPORTED:
    1165            lock_type = "UPDATE" if expression.args["update"] else "SHARE"
    1166            return self.seg(f"FOR {lock_type}")
    1167
    1168        self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
    1169        return ""

def literal_sql(self, expression: sqlglot.expressions.Literal) -> str: View Source

    1171    def literal_sql(self, expression: exp.Literal) -> str:
    1172        text = expression.this or ""
    1173        if expression.is_string:
    1174            text = text.replace(self.quote_end, self._escaped_quote_end)
    1175            if self.pretty:
    1176                text = text.replace("\n", self.SENTINEL_LINE_BREAK)
    1177            text = f"{self.quote_start}{text}{self.quote_end}"
    1178        return text

def loaddata_sql(self, expression: sqlglot.expressions.LoadData) -> str: View Source

    1180    def loaddata_sql(self, expression: exp.LoadData) -> str:
    1181        local = " LOCAL" if expression.args.get("local") else ""
    1182        inpath = f" INPATH {self.sql(expression, 'inpath')}"
    1183        overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
    1184        this = f" INTO TABLE {self.sql(expression, 'this')}"
    1185        partition = self.sql(expression, "partition")
    1186        partition = f" {partition}" if partition else ""
    1187        input_format = self.sql(expression, "input_format")
    1188        input_format = f" INPUTFORMAT {input_format}" if input_format else ""
    1189        serde = self.sql(expression, "serde")
    1190        serde = f" SERDE {serde}" if serde else ""
    1191        return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"

def null_sql(self, *_) -> str: View Source

    1193    def null_sql(self, *_) -> str:
    1194        return "NULL"

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

    1196    def boolean_sql(self, expression: exp.Boolean) -> str:
    1197        return "TRUE" if expression.this else "FALSE"

def order_sql(self, expression: sqlglot.expressions.Order, flat: bool = False) -> str: View Source

    1199    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
    1200        this = self.sql(expression, "this")
    1201        this = f"{this} " if this else this
    1202        return self.op_expressions(f"{this}ORDER BY", expression, flat=this or flat)  # type: ignore

def cluster_sql(self, expression: sqlglot.expressions.Cluster) -> str: View Source

    1204    def cluster_sql(self, expression: exp.Cluster) -> str:
    1205        return self.op_expressions("CLUSTER BY", expression)

def distribute_sql(self, expression: sqlglot.expressions.Distribute) -> str: View Source

    1207    def distribute_sql(self, expression: exp.Distribute) -> str:
    1208        return self.op_expressions("DISTRIBUTE BY", expression)

def sort_sql(self, expression: sqlglot.expressions.Sort) -> str: View Source

    1210    def sort_sql(self, expression: exp.Sort) -> str:
    1211        return self.op_expressions("SORT BY", expression)

def ordered_sql(self, expression: sqlglot.expressions.Ordered) -> str: View Source

    1213    def ordered_sql(self, expression: exp.Ordered) -> str:
    1214        desc = expression.args.get("desc")
    1215        asc = not desc
    1216
    1217        nulls_first = expression.args.get("nulls_first")
    1218        nulls_last = not nulls_first
    1219        nulls_are_large = self.null_ordering == "nulls_are_large"
    1220        nulls_are_small = self.null_ordering == "nulls_are_small"
    1221        nulls_are_last = self.null_ordering == "nulls_are_last"
    1222
    1223        sort_order = " DESC" if desc else ""
    1224        nulls_sort_change = ""
    1225        if nulls_first and (
    1226            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
    1227        ):
    1228            nulls_sort_change = " NULLS FIRST"
    1229        elif (
    1230            nulls_last
    1231            and ((asc and nulls_are_small) or (desc and nulls_are_large))
    1232            and not nulls_are_last
    1233        ):
    1234            nulls_sort_change = " NULLS LAST"
    1235
    1236        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
    1237            self.unsupported(
    1238                "Sorting in an ORDER BY on NULLS FIRST/NULLS LAST is not supported by this dialect"
    1239            )
    1240            nulls_sort_change = ""
    1241
    1242        return f"{self.sql(expression, 'this')}{sort_order}{nulls_sort_change}"

def matchrecognize_sql(self, expression: sqlglot.expressions.MatchRecognize) -> str: View Source

    1244    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
    1245        partition = self.partition_by_sql(expression)
    1246        order = self.sql(expression, "order")
    1247        measures = self.sql(expression, "measures")
    1248        measures = self.seg(f"MEASURES {measures}") if measures else ""
    1249        rows = self.sql(expression, "rows")
    1250        rows = self.seg(rows) if rows else ""
    1251        after = self.sql(expression, "after")
    1252        after = self.seg(after) if after else ""
    1253        pattern = self.sql(expression, "pattern")
    1254        pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
    1255        define = self.sql(expression, "define")
    1256        define = self.seg(f"DEFINE {define}") if define else ""
    1257        body = "".join(
    1258            (
    1259                partition,
    1260                order,
    1261                measures,
    1262                rows,
    1263                after,
    1264                pattern,
    1265                define,
    1266            )
    1267        )
    1268        return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}"

def query_modifiers(self, expression: sqlglot.expressions.Expression, *sqls: str) -> str: View Source

    1270    def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
    1271        return csv(
    1272            *sqls,
    1273            *[self.sql(sql) for sql in expression.args.get("joins") or []],
    1274            self.sql(expression, "match"),
    1275            *[self.sql(sql) for sql in expression.args.get("laterals") or []],
    1276            self.sql(expression, "where"),
    1277            self.sql(expression, "group"),
    1278            self.sql(expression, "having"),
    1279            self.sql(expression, "qualify"),
    1280            self.seg("WINDOW ") + self.expressions(expression, "windows", flat=True)
    1281            if expression.args.get("windows")
    1282            else "",
    1283            self.sql(expression, "distribute"),
    1284            self.sql(expression, "sort"),
    1285            self.sql(expression, "cluster"),
    1286            self.sql(expression, "order"),
    1287            self.sql(expression, "limit"),
    1288            self.sql(expression, "offset"),
    1289            self.sql(expression, "lock"),
    1290            sep="",
    1291        )

def select_sql(self, expression: sqlglot.expressions.Select) -> str: View Source

    1293    def select_sql(self, expression: exp.Select) -> str:
    1294        hint = self.sql(expression, "hint")
    1295        distinct = self.sql(expression, "distinct")
    1296        distinct = f" {distinct}" if distinct else ""
    1297        expressions = self.expressions(expression)
    1298        expressions = f"{self.sep()}{expressions}" if expressions else expressions
    1299        sql = self.query_modifiers(
    1300            expression,
    1301            f"SELECT{hint}{distinct}{expressions}",
    1302            self.sql(expression, "into", comment=False),
    1303            self.sql(expression, "from", comment=False),
    1304        )
    1305        return self.prepend_ctes(expression, sql)

def schema_sql(self, expression: sqlglot.expressions.Schema) -> str: View Source

    1307    def schema_sql(self, expression: exp.Schema) -> str:
    1308        this = self.sql(expression, "this")
    1309        this = f"{this} " if this else ""
    1310        sql = f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
    1311        return f"{this}{sql}"

def star_sql(self, expression: sqlglot.expressions.Star) -> str: View Source

    1313    def star_sql(self, expression: exp.Star) -> str:
    1314        except_ = self.expressions(expression, key="except", flat=True)
    1315        except_ = f"{self.seg(self.STAR_MAPPING['except'])} ({except_})" if except_ else ""
    1316        replace = self.expressions(expression, key="replace", flat=True)
    1317        replace = f"{self.seg(self.STAR_MAPPING['replace'])} ({replace})" if replace else ""
    1318        return f"*{except_}{replace}"

def structkwarg_sql(self, expression: sqlglot.expressions.StructKwarg) -> str: View Source

    1320    def structkwarg_sql(self, expression: exp.StructKwarg) -> str:
    1321        return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"

def parameter_sql(self, expression: sqlglot.expressions.Parameter) -> str: View Source

    1323    def parameter_sql(self, expression: exp.Parameter) -> str:
    1324        this = self.sql(expression, "this")
    1325        this = f"{{{this}}}" if expression.args.get("wrapped") else f"{this}"
    1326        return f"{self.PARAMETER_TOKEN}{this}"

def sessionparameter_sql(self, expression: sqlglot.expressions.SessionParameter) -> str: View Source

    1328    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
    1329        this = self.sql(expression, "this")
    1330        kind = expression.text("kind")
    1331        if kind:
    1332            kind = f"{kind}."
    1333        return f"@@{kind}{this}"

def placeholder_sql(self, expression: sqlglot.expressions.Placeholder) -> str: View Source

    1335    def placeholder_sql(self, expression: exp.Placeholder) -> str:
    1336        return f":{expression.name}" if expression.name else "?"

def subquery_sql(self, expression: sqlglot.expressions.Subquery) -> str: View Source

    1338    def subquery_sql(self, expression: exp.Subquery) -> str:
    1339        alias = self.sql(expression, "alias")
    1340
    1341        sql = self.query_modifiers(
    1342            expression,
    1343            self.wrap(expression),
    1344            self.expressions(expression, key="pivots", sep=" "),
    1345            f" AS {alias}" if alias else "",
    1346        )
    1347
    1348        return self.prepend_ctes(expression, sql)

def qualify_sql(self, expression: sqlglot.expressions.Qualify) -> str: View Source

    1350    def qualify_sql(self, expression: exp.Qualify) -> str:
    1351        this = self.indent(self.sql(expression, "this"))
    1352        return f"{self.seg('QUALIFY')}{self.sep()}{this}"

def union_sql(self, expression: sqlglot.expressions.Union) -> str: View Source

    1354    def union_sql(self, expression: exp.Union) -> str:
    1355        return self.prepend_ctes(
    1356            expression,
    1357            self.set_operation(expression, self.union_op(expression)),
    1358        )

def union_op(self, expression: sqlglot.expressions.Union) -> str: View Source

    1360    def union_op(self, expression: exp.Union) -> str:
    1361        kind = " DISTINCT" if self.EXPLICIT_UNION else ""
    1362        kind = kind if expression.args.get("distinct") else " ALL"
    1363        return f"UNION{kind}"

def unnest_sql(self, expression: sqlglot.expressions.Unnest) -> str: View Source

    1365    def unnest_sql(self, expression: exp.Unnest) -> str:
    1366        args = self.expressions(expression, flat=True)
    1367        alias = expression.args.get("alias")
    1368        if alias and self.unnest_column_only:
    1369            columns = alias.columns
    1370            alias = self.sql(columns[0]) if columns else ""
    1371        else:
    1372            alias = self.sql(expression, "alias")
    1373        alias = f" AS {alias}" if alias else alias
    1374        ordinality = " WITH ORDINALITY" if expression.args.get("ordinality") else ""
    1375        offset = expression.args.get("offset")
    1376        offset = f" WITH OFFSET AS {self.sql(offset)}" if offset else ""
    1377        return f"UNNEST({args}){ordinality}{alias}{offset}"

def where_sql(self, expression: sqlglot.expressions.Where) -> str: View Source

    1379    def where_sql(self, expression: exp.Where) -> str:
    1380        this = self.indent(self.sql(expression, "this"))
    1381        return f"{self.seg('WHERE')}{self.sep()}{this}"

def window_sql(self, expression: sqlglot.expressions.Window) -> str: View Source

    1383    def window_sql(self, expression: exp.Window) -> str:
    1384        this = self.sql(expression, "this")
    1385
    1386        partition = self.partition_by_sql(expression)
    1387
    1388        order = expression.args.get("order")
    1389        order_sql = self.order_sql(order, flat=True) if order else ""
    1390
    1391        partition_sql = partition + " " if partition and order else partition
    1392
    1393        spec = expression.args.get("spec")
    1394        spec_sql = " " + self.window_spec_sql(spec) if spec else ""
    1395
    1396        alias = self.sql(expression, "alias")
    1397        this = f"{this} {'AS' if expression.arg_key == 'windows' else 'OVER'}"
    1398
    1399        if not partition and not order and not spec and alias:
    1400            return f"{this} {alias}"
    1401
    1402        window_args = alias + partition_sql + order_sql + spec_sql
    1403
    1404        return f"{this} ({window_args.strip()})"

def partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str: View Source

    1406    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
    1407        partition = self.expressions(expression, key="partition_by", flat=True)
    1408        return f"PARTITION BY {partition}" if partition else ""

def window_spec_sql(self, expression: sqlglot.expressions.WindowSpec) -> str: View Source

    1410    def window_spec_sql(self, expression: exp.WindowSpec) -> str:
    1411        kind = self.sql(expression, "kind")
    1412        start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
    1413        end = (
    1414            csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
    1415            or "CURRENT ROW"
    1416        )
    1417        return f"{kind} BETWEEN {start} AND {end}"

def withingroup_sql(self, expression: sqlglot.expressions.WithinGroup) -> str: View Source

    1419    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
    1420        this = self.sql(expression, "this")
    1421        expression_sql = self.sql(expression, "expression")[1:]  # order has a leading space
    1422        return f"{this} WITHIN GROUP ({expression_sql})"

def between_sql(self, expression: sqlglot.expressions.Between) -> str: View Source

    1424    def between_sql(self, expression: exp.Between) -> str:
    1425        this = self.sql(expression, "this")
    1426        low = self.sql(expression, "low")
    1427        high = self.sql(expression, "high")
    1428        return f"{this} BETWEEN {low} AND {high}"

def bracket_sql(self, expression: sqlglot.expressions.Bracket) -> str: View Source

    1430    def bracket_sql(self, expression: exp.Bracket) -> str:
    1431        expressions = apply_index_offset(expression.expressions, self.index_offset)
    1432        expressions_sql = ", ".join(self.sql(e) for e in expressions)
    1433
    1434        return f"{self.sql(expression, 'this')}[{expressions_sql}]"

def all_sql(self, expression: sqlglot.expressions.All) -> str: View Source

    1436    def all_sql(self, expression: exp.All) -> str:
    1437        return f"ALL {self.wrap(expression)}"

def any_sql(self, expression: sqlglot.expressions.Any) -> str: View Source

    1439    def any_sql(self, expression: exp.Any) -> str:
    1440        this = self.sql(expression, "this")
    1441        if isinstance(expression.this, exp.Subqueryable):
    1442            this = self.wrap(this)
    1443        return f"ANY {this}"

def exists_sql(self, expression: sqlglot.expressions.Exists) -> str: View Source

    1445    def exists_sql(self, expression: exp.Exists) -> str:
    1446        return f"EXISTS{self.wrap(expression)}"

def case_sql(self, expression: sqlglot.expressions.Case) -> str: View Source

    1448    def case_sql(self, expression: exp.Case) -> str:
    1449        this = self.sql(expression, "this")
    1450        statements = [f"CASE {this}" if this else "CASE"]
    1451
    1452        for e in expression.args["ifs"]:
    1453            statements.append(f"WHEN {self.sql(e, 'this')}")
    1454            statements.append(f"THEN {self.sql(e, 'true')}")
    1455
    1456        default = self.sql(expression, "default")
    1457
    1458        if default:
    1459            statements.append(f"ELSE {default}")
    1460
    1461        statements.append("END")
    1462
    1463        if self.pretty and self.text_width(statements) > self._max_text_width:
    1464            return self.indent("\n".join(statements), skip_first=True, skip_last=True)
    1465
    1466        return " ".join(statements)

def constraint_sql(self, expression: sqlglot.expressions.Constraint) -> str: View Source

    1468    def constraint_sql(self, expression: exp.Constraint) -> str:
    1469        this = self.sql(expression, "this")
    1470        expressions = self.expressions(expression, flat=True)
    1471        return f"CONSTRAINT {this} {expressions}"

def extract_sql(self, expression: sqlglot.expressions.Extract) -> str: View Source

    1473    def extract_sql(self, expression: exp.Extract) -> str:
    1474        this = self.sql(expression, "this")
    1475        expression_sql = self.sql(expression, "expression")
    1476        return f"EXTRACT({this} FROM {expression_sql})"

def trim_sql(self, expression: sqlglot.expressions.Trim) -> str: View Source

    1478    def trim_sql(self, expression: exp.Trim) -> str:
    1479        trim_type = self.sql(expression, "position")
    1480
    1481        if trim_type == "LEADING":
    1482            return self.func("LTRIM", expression.this)
    1483        elif trim_type == "TRAILING":
    1484            return self.func("RTRIM", expression.this)
    1485        else:
    1486            return self.func("TRIM", expression.this, expression.expression)

def concat_sql(self, expression: sqlglot.expressions.Concat) -> str: View Source

    1488    def concat_sql(self, expression: exp.Concat) -> str:
    1489        if len(expression.expressions) == 1:
    1490            return self.sql(expression.expressions[0])
    1491        return self.function_fallback_sql(expression)

def check_sql(self, expression: sqlglot.expressions.Check) -> str: View Source

    1493    def check_sql(self, expression: exp.Check) -> str:
    1494        this = self.sql(expression, key="this")
    1495        return f"CHECK ({this})"

def foreignkey_sql(self, expression: sqlglot.expressions.ForeignKey) -> str: View Source

    1497    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
    1498        expressions = self.expressions(expression, flat=True)
    1499        reference = self.sql(expression, "reference")
    1500        reference = f" {reference}" if reference else ""
    1501        delete = self.sql(expression, "delete")
    1502        delete = f" ON DELETE {delete}" if delete else ""
    1503        update = self.sql(expression, "update")
    1504        update = f" ON UPDATE {update}" if update else ""
    1505        return f"FOREIGN KEY ({expressions}){reference}{delete}{update}"

def primarykey_sql(self, expression: sqlglot.expressions.ForeignKey) -> str: View Source

    1507    def primarykey_sql(self, expression: exp.ForeignKey) -> str:
    1508        expressions = self.expressions(expression, flat=True)
    1509        options = self.expressions(expression, "options", flat=True, sep=" ")
    1510        options = f" {options}" if options else ""
    1511        return f"PRIMARY KEY ({expressions}){options}"

def unique_sql(self, expression: sqlglot.expressions.Unique) -> str: View Source

    1513    def unique_sql(self, expression: exp.Unique) -> str:
    1514        columns = self.expressions(expression, key="expressions")
    1515        return f"UNIQUE ({columns})"

def if_sql(self, expression: sqlglot.expressions.If) -> str: View Source

    1517    def if_sql(self, expression: exp.If) -> str:
    1518        return self.case_sql(
    1519            exp.Case(ifs=[expression.copy()], default=expression.args.get("false"))
    1520        )

def in_sql(self, expression: sqlglot.expressions.In) -> str: View Source

    1522    def in_sql(self, expression: exp.In) -> str:
    1523        query = expression.args.get("query")
    1524        unnest = expression.args.get("unnest")
    1525        field = expression.args.get("field")
    1526        is_global = " GLOBAL" if expression.args.get("is_global") else ""
    1527
    1528        if query:
    1529            in_sql = self.wrap(query)
    1530        elif unnest:
    1531            in_sql = self.in_unnest_op(unnest)
    1532        elif field:
    1533            in_sql = self.sql(field)
    1534        else:
    1535            in_sql = f"({self.expressions(expression, flat=True)})"
    1536
    1537        return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"

def in_unnest_op(self, unnest: sqlglot.expressions.Unnest) -> str: View Source

    1539    def in_unnest_op(self, unnest: exp.Unnest) -> str:
    1540        return f"(SELECT {self.sql(unnest)})"

def interval_sql(self, expression: sqlglot.expressions.Interval) -> str: View Source

    1542    def interval_sql(self, expression: exp.Interval) -> str:
    1543        this = expression.args.get("this")
    1544        if this:
    1545            this = (
    1546                f" {this}"
    1547                if isinstance(this, exp.Literal) or isinstance(this, exp.Paren)
    1548                else f" ({this})"
    1549            )
    1550        else:
    1551            this = ""
    1552        unit = expression.args.get("unit")
    1553        unit = f" {unit}" if unit else ""
    1554        return f"INTERVAL{this}{unit}"

def return_sql(self, expression: sqlglot.expressions.Return) -> str: View Source

    1556    def return_sql(self, expression: exp.Return) -> str:
    1557        return f"RETURN {self.sql(expression, 'this')}"

def reference_sql(self, expression: sqlglot.expressions.Reference) -> str: View Source

    1559    def reference_sql(self, expression: exp.Reference) -> str:
    1560        this = self.sql(expression, "this")
    1561        expressions = self.expressions(expression, flat=True)
    1562        expressions = f"({expressions})" if expressions else ""
    1563        options = self.expressions(expression, "options", flat=True, sep=" ")
    1564        options = f" {options}" if options else ""
    1565        return f"REFERENCES {this}{expressions}{options}"

def anonymous_sql(self, expression: sqlglot.expressions.Anonymous) -> str: View Source

    1567    def anonymous_sql(self, expression: exp.Anonymous) -> str:
    1568        return self.func(expression.name, *expression.expressions)

def paren_sql(self, expression: sqlglot.expressions.Paren) -> str: View Source

    1570    def paren_sql(self, expression: exp.Paren) -> str:
    1571        if isinstance(expression.unnest(), exp.Select):
    1572            sql = self.wrap(expression)
    1573        else:
    1574            sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
    1575            sql = f"({sql}{self.seg(')', sep='')}"
    1576
    1577        return self.prepend_ctes(expression, sql)

def neg_sql(self, expression: sqlglot.expressions.Neg) -> str: View Source

    1579    def neg_sql(self, expression: exp.Neg) -> str:
    1580        # This makes sure we don't convert "- - 5" to "--5", which is a comment
    1581        this_sql = self.sql(expression, "this")
    1582        sep = " " if this_sql[0] == "-" else ""
    1583        return f"-{sep}{this_sql}"

def not_sql(self, expression: sqlglot.expressions.Not) -> str: View Source

    1585    def not_sql(self, expression: exp.Not) -> str:
    1586        return f"NOT {self.sql(expression, 'this')}"

def alias_sql(self, expression: sqlglot.expressions.Alias) -> str: View Source

    1588    def alias_sql(self, expression: exp.Alias) -> str:
    1589        to_sql = self.sql(expression, "alias")
    1590        to_sql = f" AS {to_sql}" if to_sql else ""
    1591        return f"{self.sql(expression, 'this')}{to_sql}"

def aliases_sql(self, expression: sqlglot.expressions.Aliases) -> str: View Source

    1593    def aliases_sql(self, expression: exp.Aliases) -> str:
    1594        return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"

def attimezone_sql(self, expression: sqlglot.expressions.AtTimeZone) -> str: View Source

    1596    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
    1597        this = self.sql(expression, "this")
    1598        zone = self.sql(expression, "zone")
    1599        return f"{this} AT TIME ZONE {zone}"

def add_sql(self, expression: sqlglot.expressions.Add) -> str: View Source

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

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

    1604    def and_sql(self, expression: exp.And) -> str:
    1605        return self.connector_sql(expression, "AND")

def connector_sql(self, expression: sqlglot.expressions.Connector, op: str) -> str: View Source

    1607    def connector_sql(self, expression: exp.Connector, op: str) -> str:
    1608        if not self.pretty:
    1609            return self.binary(expression, op)
    1610
    1611        sqls = tuple(self.sql(e) for e in expression.flatten(unnest=False))
    1612        sep = "\n" if self.text_width(sqls) > self._max_text_width else " "
    1613        return f"{sep}{op} ".join(sqls)

def bitwiseand_sql(self, expression: sqlglot.expressions.BitwiseAnd) -> str: View Source

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

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

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

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

    1621    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
    1622        return f"~{self.sql(expression, 'this')}"

def bitwiseor_sql(self, expression: sqlglot.expressions.BitwiseOr) -> str: View Source

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

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

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

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

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

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

    1633    def cast_sql(self, expression: exp.Cast) -> str:
    1634        return f"CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')})"

def currentdate_sql(self, expression: sqlglot.expressions.CurrentDate) -> str: View Source

    1636    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
    1637        zone = self.sql(expression, "this")
    1638        return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"

def collate_sql(self, expression: sqlglot.expressions.Collate) -> str: View Source

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

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

    1643    def command_sql(self, expression: exp.Command) -> str:
    1644        return f"{self.sql(expression, 'this').upper()} {expression.text('expression').strip()}"

def transaction_sql(self, *_) -> str: View Source

    1646    def transaction_sql(self, *_) -> str:
    1647        return "BEGIN"

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

    1649    def commit_sql(self, expression: exp.Commit) -> str:
    1650        chain = expression.args.get("chain")
    1651        if chain is not None:
    1652            chain = " AND CHAIN" if chain else " AND NO CHAIN"
    1653
    1654        return f"COMMIT{chain or ''}"

def rollback_sql(self, expression: sqlglot.expressions.Rollback) -> str: View Source

    1656    def rollback_sql(self, expression: exp.Rollback) -> str:
    1657        savepoint = expression.args.get("savepoint")
    1658        savepoint = f" TO {savepoint}" if savepoint else ""
    1659        return f"ROLLBACK{savepoint}"

def altercolumn_sql(self, expression: sqlglot.expressions.AlterColumn) -> str: View Source

    1661    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
    1662        this = self.sql(expression, "this")
    1663
    1664        dtype = self.sql(expression, "dtype")
    1665        if dtype:
    1666            collate = self.sql(expression, "collate")
    1667            collate = f" COLLATE {collate}" if collate else ""
    1668            using = self.sql(expression, "using")
    1669            using = f" USING {using}" if using else ""
    1670            return f"ALTER COLUMN {this} TYPE {dtype}{collate}{using}"
    1671
    1672        default = self.sql(expression, "default")
    1673        if default:
    1674            return f"ALTER COLUMN {this} SET DEFAULT {default}"
    1675
    1676        if not expression.args.get("drop"):
    1677            self.unsupported("Unsupported ALTER COLUMN syntax")
    1678
    1679        return f"ALTER COLUMN {this} DROP DEFAULT"

def renametable_sql(self, expression: sqlglot.expressions.RenameTable) -> str: View Source

    1681    def renametable_sql(self, expression: exp.RenameTable) -> str:
    1682        this = self.sql(expression, "this")
    1683        return f"RENAME TO {this}"

def altertable_sql(self, expression: sqlglot.expressions.AlterTable) -> str: View Source

    1685    def altertable_sql(self, expression: exp.AlterTable) -> str:
    1686        actions = expression.args["actions"]
    1687
    1688        if isinstance(actions[0], exp.ColumnDef):
    1689            actions = self.expressions(expression, "actions", prefix="ADD COLUMN ")
    1690        elif isinstance(actions[0], exp.Schema):
    1691            actions = self.expressions(expression, "actions", prefix="ADD COLUMNS ")
    1692        elif isinstance(actions[0], exp.Delete):
    1693            actions = self.expressions(expression, "actions", flat=True)
    1694        else:
    1695            actions = self.expressions(expression, "actions")
    1696
    1697        exists = " IF EXISTS" if expression.args.get("exists") else ""
    1698        return f"ALTER TABLE{exists} {self.sql(expression, 'this')} {actions}"

def droppartition_sql(self, expression: sqlglot.expressions.DropPartition) -> str: View Source

    1700    def droppartition_sql(self, expression: exp.DropPartition) -> str:
    1701        expressions = self.expressions(expression)
    1702        exists = " IF EXISTS " if expression.args.get("exists") else " "
    1703        return f"DROP{exists}{expressions}"

def addconstraint_sql(self, expression: sqlglot.expressions.AddConstraint) -> str: View Source

    1705    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
    1706        this = self.sql(expression, "this")
    1707        expression_ = self.sql(expression, "expression")
    1708        add_constraint = f"ADD CONSTRAINT {this}" if this else "ADD"
    1709
    1710        enforced = expression.args.get("enforced")
    1711        if enforced is not None:
    1712            return f"{add_constraint} CHECK ({expression_}){' ENFORCED' if enforced else ''}"
    1713
    1714        return f"{add_constraint} {expression_}"

def distinct_sql(self, expression: sqlglot.expressions.Distinct) -> str: View Source

    1716    def distinct_sql(self, expression: exp.Distinct) -> str:
    1717        this = self.expressions(expression, flat=True)
    1718        this = f" {this}" if this else ""
    1719
    1720        on = self.sql(expression, "on")
    1721        on = f" ON {on}" if on else ""
    1722        return f"DISTINCT{this}{on}"

def ignorenulls_sql(self, expression: sqlglot.expressions.IgnoreNulls) -> str: View Source

    1724    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
    1725        return f"{self.sql(expression, 'this')} IGNORE NULLS"

def respectnulls_sql(self, expression: sqlglot.expressions.RespectNulls) -> str: View Source

    1727    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
    1728        return f"{self.sql(expression, 'this')} RESPECT NULLS"

def intdiv_sql(self, expression: sqlglot.expressions.IntDiv) -> str: View Source

    1730    def intdiv_sql(self, expression: exp.IntDiv) -> str:
    1731        return self.sql(
    1732            exp.Cast(
    1733                this=exp.Div(this=expression.this, expression=expression.expression),
    1734                to=exp.DataType(this=exp.DataType.Type.INT),
    1735            )
    1736        )

def dpipe_sql(self, expression: sqlglot.expressions.DPipe) -> str: View Source

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

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

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

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

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

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

    1747    def dot_sql(self, expression: exp.Dot) -> str:
    1748        return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"

def eq_sql(self, expression: sqlglot.expressions.EQ) -> str: View Source

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    1774    def similarto_sql(self, expression: exp.SimilarTo) -> str:
    1775        return self.binary(expression, "SIMILAR TO")

def lt_sql(self, expression: sqlglot.expressions.LT) -> str: View Source

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

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

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

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

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

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

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

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

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

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

    1792    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
    1793        return self.binary(expression, "IS NOT DISTINCT FROM")

def nullsafeneq_sql(self, expression: sqlglot.expressions.NullSafeNEQ) -> str: View Source

    1795    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
    1796        return self.binary(expression, "IS DISTINCT FROM")

def or_sql(self, expression: sqlglot.expressions.Or) -> str: View Source

    1798    def or_sql(self, expression: exp.Or) -> str:
    1799        return self.connector_sql(expression, "OR")

def slice_sql(self, expression: sqlglot.expressions.Slice) -> str: View Source

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

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

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

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

    1807    def trycast_sql(self, expression: exp.TryCast) -> str:
    1808        return f"TRY_CAST({self.sql(expression, 'this')} AS {self.sql(expression, 'to')})"

def use_sql(self, expression: sqlglot.expressions.Use) -> str: View Source

    1810    def use_sql(self, expression: exp.Use) -> str:
    1811        kind = self.sql(expression, "kind")
    1812        kind = f" {kind}" if kind else ""
    1813        this = self.sql(expression, "this")
    1814        this = f" {this}" if this else ""
    1815        return f"USE{kind}{this}"

def binary(self, expression: sqlglot.expressions.Binary, op: str) -> str: View Source

    1817    def binary(self, expression: exp.Binary, op: str) -> str:
    1818        return f"{self.sql(expression, 'this')} {op} {self.sql(expression, 'expression')}"

def function_fallback_sql(self, expression: sqlglot.expressions.Func) -> str: View Source

    1820    def function_fallback_sql(self, expression: exp.Func) -> str:
    1821        args = []
    1822        for arg_value in expression.args.values():
    1823            if isinstance(arg_value, list):
    1824                for value in arg_value:
    1825                    args.append(value)
    1826            else:
    1827                args.append(arg_value)
    1828
    1829        return self.func(expression.sql_name(), *args)

def func( self, name: str, *args: Union[str, sqlglot.expressions.Expression, NoneType]) -> str: View Source

    1831    def func(self, name: str, *args: t.Optional[exp.Expression | str]) -> str:
    1832        return f"{self.normalize_func(name)}({self.format_args(*args)})"

def format_args(self, *args: Union[str, sqlglot.expressions.Expression, NoneType]) -> str: View Source

    1834    def format_args(self, *args: t.Optional[str | exp.Expression]) -> str:
    1835        arg_sqls = tuple(self.sql(arg) for arg in args if arg is not None)
    1836        if self.pretty and self.text_width(arg_sqls) > self._max_text_width:
    1837            return self.indent("\n" + f",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True)
    1838        return ", ".join(arg_sqls)

def text_width(self, args: Iterable) -> int: View Source

    1840    def text_width(self, args: t.Iterable) -> int:
    1841        return sum(len(arg) for arg in args)

def format_time(self, expression: sqlglot.expressions.Expression) -> Optional[str]: View Source

    1843    def format_time(self, expression: exp.Expression) -> t.Optional[str]:
    1844        return format_time(self.sql(expression, "format"), self.time_mapping, self.time_trie)

def expressions( self, expression: sqlglot.expressions.Expression, key: Optional[str] = None, flat: bool = False, indent: bool = True, sep: str = ', ', prefix: str = '') -> str: View Source

    1846    def expressions(
    1847        self,
    1848        expression: exp.Expression,
    1849        key: t.Optional[str] = None,
    1850        flat: bool = False,
    1851        indent: bool = True,
    1852        sep: str = ", ",
    1853        prefix: str = "",
    1854    ) -> str:
    1855        expressions = expression.args.get(key or "expressions")
    1856
    1857        if not expressions:
    1858            return ""
    1859
    1860        if flat:
    1861            return sep.join(self.sql(e) for e in expressions)
    1862
    1863        num_sqls = len(expressions)
    1864
    1865        # These are calculated once in case we have the leading_comma / pretty option set, correspondingly
    1866        pad = " " * self.pad
    1867        stripped_sep = sep.strip()
    1868
    1869        result_sqls = []
    1870        for i, e in enumerate(expressions):
    1871            sql = self.sql(e, comment=False)
    1872            comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
    1873
    1874            if self.pretty:
    1875                if self._leading_comma:
    1876                    result_sqls.append(f"{sep if i > 0 else pad}{prefix}{sql}{comments}")
    1877                else:
    1878                    result_sqls.append(
    1879                        f"{prefix}{sql}{stripped_sep if i + 1 < num_sqls else ''}{comments}"
    1880                    )
    1881            else:
    1882                result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
    1883
    1884        result_sql = "\n".join(result_sqls) if self.pretty else "".join(result_sqls)
    1885        return self.indent(result_sql, skip_first=False) if indent else result_sql

def op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str: View Source

    1887    def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
    1888        flat = flat or isinstance(expression.parent, exp.Properties)
    1889        expressions_sql = self.expressions(expression, flat=flat)
    1890        if flat:
    1891            return f"{op} {expressions_sql}"
    1892        return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"

def naked_property(self, expression: sqlglot.expressions.Property) -> str: View Source

    1894    def naked_property(self, expression: exp.Property) -> str:
    1895        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
    1896        if not property_name:
    1897            self.unsupported(f"Unsupported property {expression.__class__.__name__}")
    1898        return f"{property_name} {self.sql(expression, 'this')}"

def set_operation(self, expression: sqlglot.expressions.Expression, op: str) -> str: View Source

    1900    def set_operation(self, expression: exp.Expression, op: str) -> str:
    1901        this = self.sql(expression, "this")
    1902        op = self.seg(op)
    1903        return self.query_modifiers(
    1904            expression, f"{this}{op}{self.sep()}{self.sql(expression, 'expression')}"
    1905        )

def tag_sql(self, expression: sqlglot.expressions.Tag) -> str: View Source

    1907    def tag_sql(self, expression: exp.Tag) -> str:
    1908        return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"

def token_sql(self, token_type: sqlglot.tokens.TokenType) -> str: View Source

    1910    def token_sql(self, token_type: TokenType) -> str:
    1911        return self.TOKEN_MAPPING.get(token_type, token_type.name)

def userdefinedfunction_sql(self, expression: sqlglot.expressions.UserDefinedFunction) -> str: View Source

    1913    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
    1914        this = self.sql(expression, "this")
    1915        expressions = self.no_identify(self.expressions, expression)
    1916        expressions = (
    1917            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
    1918        )
    1919        return f"{this}{expressions}"

def joinhint_sql(self, expression: sqlglot.expressions.JoinHint) -> str: View Source

    1921    def joinhint_sql(self, expression: exp.JoinHint) -> str:
    1922        this = self.sql(expression, "this")
    1923        expressions = self.expressions(expression, flat=True)
    1924        return f"{this}({expressions})"

def kwarg_sql(self, expression: sqlglot.expressions.Kwarg) -> str: View Source

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

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

    1929    def when_sql(self, expression: exp.When) -> str:
    1930        this = self.sql(expression, "this")
    1931        then_expression = expression.args.get("then")
    1932        if isinstance(then_expression, exp.Insert):
    1933            then = f"INSERT {self.sql(then_expression, 'this')}"
    1934            if "expression" in then_expression.args:
    1935                then += f" VALUES {self.sql(then_expression, 'expression')}"
    1936        elif isinstance(then_expression, exp.Update):
    1937            if isinstance(then_expression.args.get("expressions"), exp.Star):
    1938                then = f"UPDATE {self.sql(then_expression, 'expressions')}"
    1939            else:
    1940                then = f"UPDATE SET {self.expressions(then_expression, flat=True)}"
    1941        else:
    1942            then = self.sql(then_expression)
    1943        return f"WHEN {this} THEN {then}"

def merge_sql(self, expression: sqlglot.expressions.Merge) -> str: View Source

    1945    def merge_sql(self, expression: exp.Merge) -> str:
    1946        this = self.sql(expression, "this")
    1947        using = f"USING {self.sql(expression, 'using')}"
    1948        on = f"ON {self.sql(expression, 'on')}"
    1949        return f"MERGE INTO {this} {using} {on} {self.expressions(expression, sep=' ')}"