Edit on GitHub

sqlglot.generators.tsql

  1from __future__ import annotations
  2
  3from functools import reduce
  4
  5from sqlglot import exp, generator, transforms
  6from sqlglot.dialects.dialect import (
  7    any_value_to_max_sql,
  8    date_delta_sql,
  9    datestrtodate_sql,
 10    generatedasidentitycolumnconstraint_sql,
 11    max_or_greatest,
 12    min_or_least,
 13    no_filter_sql,
 14    remove_ts_or_ds_to_date,
 15    rename_func,
 16    strposition_sql,
 17    timestrtotime_sql,
 18    trim_sql,
 19)
 20from sqlglot.helper import seq_get
 21from sqlglot.optimizer.scope import find_in_scope
 22from sqlglot.parsers.tsql import OPTIONS_THAT_REQUIRE_EQUAL
 23from sqlglot.time import format_time
 24from collections import defaultdict
 25
 26DATE_PART_UNMAPPING = {
 27    "WEEKISO": "ISO_WEEK",
 28    "DAYOFWEEK": "WEEKDAY",
 29    "TIMEZONE_MINUTE": "TZOFFSET",
 30}
 31
 32BIT_TYPES = {exp.EQ, exp.NEQ, exp.Is, exp.In, exp.Select, exp.Alias}
 33
 34SET_OP_MODIFIERS = ("limit", "offset", "order", "for_", "options")
 35
 36
 37def _format_sql(self: TSQLGenerator, expression: exp.NumberToStr | exp.TimeToStr) -> str:
 38    fmt = expression.args["format"]
 39
 40    if not isinstance(expression, exp.NumberToStr):
 41        if fmt.is_string:
 42            from sqlglot.dialects.tsql import TSQL
 43
 44            mapped_fmt = format_time(fmt.name, TSQL.INVERSE_TIME_MAPPING)
 45            fmt_sql = self.sql(exp.Literal.string(mapped_fmt))
 46        else:
 47            fmt_sql = self.format_time(expression) or self.sql(fmt)
 48    else:
 49        fmt_sql = self.sql(fmt)
 50
 51    return self.func("FORMAT", expression.this, fmt_sql, expression.args.get("culture"))
 52
 53
 54def _string_agg_sql(self: TSQLGenerator, expression: exp.GroupConcat) -> str:
 55    this = expression.this
 56    distinct = find_in_scope(expression, exp.Distinct)
 57    if distinct:
 58        # exp.Distinct can appear below an exp.Order or an exp.GroupConcat expression
 59        self.unsupported("T-SQL STRING_AGG doesn't support DISTINCT.")
 60        this = distinct.pop().expressions[0]
 61
 62    order = ""
 63    if isinstance(expression.this, exp.Order):
 64        if expression.this.this:
 65            this = expression.this.this.pop()
 66        # Order has a leading space
 67        order = f" WITHIN GROUP ({self.sql(expression.this)[1:]})"
 68
 69    separator = expression.args.get("separator") or exp.Literal.string(",")
 70    return f"STRING_AGG({self.format_args(this, separator)}){order}"
 71
 72
 73def qualify_derived_table_outputs(expression: exp.Expr) -> exp.Expr:
 74    """Ensures all (unnamed) output columns are aliased for CTEs and Subqueries."""
 75    alias = expression.args.get("alias")
 76
 77    if (
 78        isinstance(expression, (exp.CTE, exp.Subquery))
 79        and isinstance(alias, exp.TableAlias)
 80        and not alias.columns
 81        and isinstance(expression.this, exp.Query)
 82    ):
 83        from sqlglot.dialects.tsql import TSQL
 84        from sqlglot.optimizer.qualify_columns import qualify_outputs
 85
 86        # We keep track of the unaliased column projection indexes instead of the expressions
 87        # themselves, because the latter are going to be replaced by new nodes when the aliases
 88        # are added and hence we won't be able to reach these newly added Alias parents
 89        query = expression.this
 90        unaliased_column_indexes = (
 91            i for i, c in enumerate(query.selects) if isinstance(c, exp.Column) and not c.alias
 92        )
 93
 94        qualify_outputs(query, dialect=TSQL())
 95
 96        # Preserve the quoting information of columns for newly added Alias nodes
 97        query_selects = query.selects
 98        for select_index in unaliased_column_indexes:
 99            alias = query_selects[select_index]
100            column = alias.this
101            if isinstance(column.this, exp.Identifier):
102                alias.args["alias"].set("quoted", column.this.quoted)
103
104    return expression
105
106
107def _json_extract_sql(
108    self: TSQLGenerator, expression: exp.JSONExtract | exp.JSONExtractScalar
109) -> str:
110    # JSON_QUERY returns objects and arrays, JSON_VALUE returns scalars. A scalar-only
111    # extraction maps to JSON_VALUE; a source that also returns non-scalar values as
112    # text (e.g. SQLite's ->>) needs to try both, like a generic JSONExtract does
113    if isinstance(expression, exp.JSONExtractScalar) and expression.args.get("scalar_only"):
114        return self.func("JSON_VALUE", expression.this, expression.expression)
115
116    json_query = self.func("JSON_QUERY", expression.this, expression.expression)
117    if expression.args.get("json_query"):
118        return json_query
119
120    json_value = self.func("JSON_VALUE", expression.this, expression.expression)
121    return self.func("ISNULL", json_query, json_value)
122
123
124def _timestrtotime_sql(self: TSQLGenerator, expression: exp.TimeStrToTime):
125    sql = timestrtotime_sql(self, expression)
126    if expression.args.get("zone"):
127        # If there is a timezone, produce an expression like:
128        # CAST('2020-01-01 12:13:14-08:00' AS DATETIMEOFFSET) AT TIME ZONE 'UTC'
129        # If you dont have AT TIME ZONE 'UTC', wrapping that expression in another cast back to DATETIME2 just drops the timezone information
130        return self.sql(exp.AtTimeZone(this=sql, zone=exp.Literal.string("UTC")))
131    return sql
132
133
134class TSQLGenerator(generator.Generator):
135    SELECT_KINDS: tuple[str, ...] = ()
136    TRY_SUPPORTED = False
137    SUPPORTS_UESCAPE = False
138    SUPPORTS_DECODE_CASE = False
139
140    AFTER_HAVING_MODIFIER_TRANSFORMS = generator.AFTER_HAVING_MODIFIER_TRANSFORMS
141
142    LIMIT_IS_TOP = True
143    SET_OP_LIMITS = True
144    QUERY_HINTS = False
145    RETURNING_END = False
146    NVL2_SUPPORTED = False
147    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = False
148    ALTER_TABLE_ADD_COLUMNS_WRAPPED = False
149    LIMIT_FETCH = "FETCH"
150    COMPUTED_COLUMN_WITH_TYPE = False
151    CTE_RECURSIVE_KEYWORD_REQUIRED = False
152    ENSURE_BOOLS = True
153    NULL_ORDERING_SUPPORTED: bool | None = None
154    SUPPORTS_SINGLE_ARG_CONCAT = False
155    TABLESAMPLE_SEED_KEYWORD = "REPEATABLE"
156    SUPPORTS_SELECT_INTO = True
157    JSON_PATH_BRACKETED_KEY_SUPPORTED = False
158    SUPPORTS_TO_NUMBER = False
159    COPY_PARAMS_EQ_REQUIRED = True
160    PARSE_JSON_NAME: str | None = None
161    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
162    ALTER_SET_WRAPPED = True
163    ALTER_SET_TYPE = ""
164    SUPPORTS_ALTER_COLUMN_NULLABILITY = True
165
166    EXPRESSIONS_WITHOUT_NESTED_CTES = {
167        exp.Create,
168        exp.Delete,
169        exp.Insert,
170        exp.Intersect,
171        exp.Except,
172        exp.Merge,
173        exp.Select,
174        exp.Subquery,
175        exp.Union,
176        exp.Update,
177    }
178
179    SUPPORTED_JSON_PATH_PARTS = {
180        exp.JSONPathKey,
181        exp.JSONPathRoot,
182        exp.JSONPathSubscript,
183    }
184
185    TYPE_MAPPING = {
186        **{
187            k: v
188            for k, v in generator.Generator.TYPE_MAPPING.items()
189            if k not in (exp.DType.NCHAR, exp.DType.NVARCHAR)
190        },
191        exp.DType.BOOLEAN: "BIT",
192        exp.DType.DATETIME2: "DATETIME2",
193        exp.DType.DECIMAL: "NUMERIC",
194        exp.DType.DOUBLE: "FLOAT",
195        exp.DType.INT: "INTEGER",
196        exp.DType.ROWVERSION: "ROWVERSION",
197        exp.DType.TEXT: "VARCHAR(MAX)",
198        exp.DType.TIMESTAMP: "DATETIME2",
199        exp.DType.TIMESTAMPNTZ: "DATETIME2",
200        exp.DType.TIMESTAMPTZ: "DATETIMEOFFSET",
201        exp.DType.SMALLDATETIME: "SMALLDATETIME",
202        exp.DType.UTINYINT: "TINYINT",
203        exp.DType.VARIANT: "SQL_VARIANT",
204        exp.DType.UUID: "UNIQUEIDENTIFIER",
205    }
206
207    TRANSFORMS = {
208        **{k: v for k, v in generator.Generator.TRANSFORMS.items() if k != exp.ReturnsProperty},
209        exp.AnyValue: any_value_to_max_sql,
210        exp.Atan2: rename_func("ATN2"),
211        exp.ArrayToString: rename_func("STRING_AGG"),
212        exp.AutoIncrementColumnConstraint: lambda *_: "IDENTITY",
213        exp.Ceil: rename_func("CEILING"),
214        exp.Chr: rename_func("CHAR"),
215        exp.DateAdd: date_delta_sql("DATEADD"),
216        exp.CTE: transforms.preprocess([qualify_derived_table_outputs]),
217        exp.CurrentDate: rename_func("GETDATE"),
218        exp.CurrentTimestamp: rename_func("GETDATE"),
219        exp.CurrentTimestampLTZ: rename_func("SYSDATETIMEOFFSET"),
220        exp.DateStrToDate: datestrtodate_sql,
221        exp.Day: remove_ts_or_ds_to_date(),
222        exp.Filter: no_filter_sql,
223        exp.GeneratedAsIdentityColumnConstraint: generatedasidentitycolumnconstraint_sql,
224        exp.GroupConcat: _string_agg_sql,
225        exp.If: rename_func("IIF"),
226        exp.JSONExtract: _json_extract_sql,
227        exp.JSONExtractScalar: _json_extract_sql,
228        exp.LastDay: lambda self, e: self.func("EOMONTH", e.this),
229        exp.Ln: rename_func("LOG"),
230        exp.Max: max_or_greatest,
231        exp.MD5: lambda self, e: self.func("HASHBYTES", exp.Literal.string("MD5"), e.this),
232        exp.Min: min_or_least,
233        exp.Month: remove_ts_or_ds_to_date(),
234        exp.NumberToStr: _format_sql,
235        exp.Repeat: rename_func("REPLICATE"),
236        exp.CurrentSchema: rename_func("SCHEMA_NAME"),
237        exp.Select: transforms.preprocess(
238            [
239                transforms.eliminate_distinct_on,
240                transforms.eliminate_semi_and_anti_joins,
241                transforms.eliminate_qualify,
242                transforms.unnest_generate_date_array_using_recursive_cte,
243            ]
244        ),
245        exp.Stddev: rename_func("STDEV"),
246        exp.StrPosition: lambda self, e: strposition_sql(
247            self, e, func_name="CHARINDEX", supports_position=True
248        ),
249        exp.Subquery: transforms.preprocess([qualify_derived_table_outputs]),
250        exp.SHA: lambda self, e: self.func("HASHBYTES", exp.Literal.string("SHA1"), e.this),
251        exp.SHA1Digest: lambda self, e: self.func("HASHBYTES", exp.Literal.string("SHA1"), e.this),
252        exp.SHA2: lambda self, e: self.func(
253            "HASHBYTES", exp.Literal.string(f"SHA2_{e.args.get('length', 256)}"), e.this
254        ),
255        exp.TemporaryProperty: lambda self, e: "",
256        exp.TimeStrToTime: _timestrtotime_sql,
257        exp.TimeToStr: _format_sql,
258        exp.TimestampAdd: date_delta_sql("DATEADD"),
259        exp.Trim: trim_sql,
260        exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True),
261        exp.TsOrDsDiff: date_delta_sql("DATEDIFF"),
262        exp.TimestampTrunc: lambda self, e: self.func("DATETRUNC", e.unit, e.this),
263        exp.Trunc: lambda self, e: self.func(
264            "ROUND",
265            e.this,
266            e.args.get("decimals") or exp.Literal.number(0),
267            exp.Literal.number(1),
268        ),
269        exp.Uuid: lambda *_: "NEWID()",
270        exp.Year: remove_ts_or_ds_to_date(),
271        exp.DateFromParts: rename_func("DATEFROMPARTS"),
272    }
273
274    PROPERTIES_LOCATION = {
275        **generator.Generator.PROPERTIES_LOCATION,
276        exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
277    }
278
279    def scope_resolution(self, rhs: str, scope_name: str) -> str:
280        return f"{scope_name}::{rhs}"
281
282    def select_sql(self, expression: exp.Select) -> str:
283        self._prepare_limit_offset(expression)
284
285        # Handles transpiling a query like the following to T-SQL:
286        #   SELECT 1 AS x ORDER BY x NULLS FIRST FETCH FIRST 1 ROWS ONLY UNION ALL SELECT 2 AS x
287        if isinstance(expression.parent, exp.SetOperation) and isinstance(
288            expression.args.get("limit"), exp.Fetch
289        ):
290            return self.sql(
291                exp.select("*").from_(expression.subquery("_l_0", copy=False), copy=False)
292            )
293
294        return super().select_sql(expression)
295
296    def set_operations(self, expression: exp.SetOperation) -> str:
297        limit = expression.args.get("limit")
298        offset = expression.args.get("offset")
299        order = expression.args.get("order")
300
301        # Set operations cannot order by the CASE used to emulate null ordering,
302        # or by expressions that aren't in their select list.
303        wrap_order = False
304        if order:
305            selects = {select.unalias().unnest() for select in expression.selects}
306            for ordered in order.expressions:
307                this = ordered.this.unnest()
308                if this.is_int:
309                    continue
310
311                desc = ordered.args.get("desc")
312                nulls_first = ordered.args.get("nulls_first")
313
314                emulate_null_ordering = (desc and nulls_first) or (not desc and not nulls_first)
315                if emulate_null_ordering or (
316                    not isinstance(this, exp.Column) and this not in selects
317                ):
318                    wrap_order = True
319                    break
320
321        if (
322            wrap_order
323            or (isinstance(limit, exp.Limit) and not offset)
324            or (not order and (offset or isinstance(limit, exp.Fetch)))
325        ):
326            select = self._move_ctes_to_top_level(
327                exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
328            )
329            for arg in SET_OP_MODIFIERS:
330                value = expression.args.get(arg)
331                if value:
332                    expression.set(arg, None)
333                    select.set(arg, value)
334
335            return self.sql(select)
336
337        self._prepare_limit_offset(expression)
338        return super().set_operations(expression)
339
340    def _prepare_limit_offset(self, expression: exp.Query) -> None:
341        limit = expression.args.get("limit")
342        offset = expression.args.get("offset")
343
344        if isinstance(limit, exp.Fetch) and not offset:
345            # Dialects like Oracle can FETCH directly from a row set but
346            # T-SQL requires an ORDER BY + OFFSET clause in order to FETCH
347            offset = exp.Offset(expression=exp.Literal.number(0))
348            expression.set("offset", offset)
349
350        if offset:
351            if not expression.args.get("order"):
352                # ORDER BY is required in order to use OFFSET in a query, so we use
353                # a noop order by, since we don't really care about the order.
354                # See: https://www.microsoftpressstore.com/articles/article.aspx?p=2314819
355                expression.order_by(exp.select(exp.null()).subquery(), copy=False)
356
357            if isinstance(limit, exp.Limit) and not limit.is_limit_all:
358                # TOP and OFFSET can't be combined, we need use FETCH instead of TOP
359                # we replace here because otherwise TOP would be generated in select_sql
360                limit.replace(exp.Fetch(direction="FIRST", count=limit.expression))
361
362    def convert_sql(self, expression: exp.Convert) -> str:
363        name = "TRY_CONVERT" if expression.args.get("safe") else "CONVERT"
364        return self.func(name, expression.this, expression.expression, expression.args.get("style"))
365
366    def queryoption_sql(self, expression: exp.QueryOption) -> str:
367        option = self.sql(expression, "this")
368        value = self.sql(expression, "expression")
369        if value:
370            optional_equal_sign = "= " if option in OPTIONS_THAT_REQUIRE_EQUAL else ""
371            return f"{option} {optional_equal_sign}{value}"
372        return option
373
374    def lateral_op(self, expression: exp.Lateral) -> str:
375        cross_apply = expression.args.get("cross_apply")
376        if cross_apply is True:
377            return "CROSS APPLY"
378        if cross_apply is False:
379            return "OUTER APPLY"
380
381        # TODO: perhaps we can check if the parent is a Join and transpile it appropriately
382        self.unsupported("LATERAL clause is not supported.")
383        return "LATERAL"
384
385    def splitpart_sql(self, expression: exp.SplitPart) -> str:
386        this = expression.this
387        split_count = len(this.name.split("."))
388        delimiter = expression.args.get("delimiter")
389        part_index = expression.args.get("part_index")
390
391        if (
392            not all(isinstance(arg, exp.Literal) for arg in (this, delimiter, part_index))
393            or (delimiter and delimiter.name != ".")
394            or not part_index
395            or split_count > 4
396        ):
397            self.unsupported(
398                "SPLIT_PART can be transpiled to PARSENAME only for '.' delimiter and literal values"
399            )
400            return ""
401
402        return self.func(
403            "PARSENAME", this, exp.Literal.number(split_count + 1 - part_index.to_py())
404        )
405
406    def extract_sql(self, expression: exp.Extract) -> str:
407        part = expression.this
408        name = DATE_PART_UNMAPPING.get(part.name.upper()) or part
409
410        return self.func("DATEPART", name, expression.expression)
411
412    def timefromparts_sql(self, expression: exp.TimeFromParts) -> str:
413        nano = expression.args.get("nano")
414        if nano is not None:
415            nano.pop()
416            self.unsupported("Specifying nanoseconds is not supported in TIMEFROMPARTS.")
417
418        if expression.args.get("fractions") is None:
419            expression.set("fractions", exp.Literal.number(0))
420        if expression.args.get("precision") is None:
421            expression.set("precision", exp.Literal.number(0))
422
423        return rename_func("TIMEFROMPARTS")(self, expression)
424
425    def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str:
426        zone = expression.args.get("zone")
427        if zone is not None:
428            zone.pop()
429            self.unsupported("Time zone is not supported in DATETIMEFROMPARTS.")
430
431        nano = expression.args.get("nano")
432        if nano is not None:
433            nano.pop()
434            self.unsupported("Specifying nanoseconds is not supported in DATETIMEFROMPARTS.")
435
436        if expression.args.get("milli") is None:
437            expression.set("milli", exp.Literal.number(0))
438
439        return rename_func("DATETIMEFROMPARTS")(self, expression)
440
441    def setitem_sql(self, expression: exp.SetItem) -> str:
442        this = expression.this
443        if isinstance(this, exp.EQ) and not isinstance(this.left, exp.Parameter):
444            # T-SQL does not use '=' in SET command, except when the LHS is a variable.
445            return f"{self.sql(this.left)} {self.sql(this.right)}"
446
447        return super().setitem_sql(expression)
448
449    def boolean_sql(self, expression: exp.Boolean) -> str:
450        if type(expression.parent) in BIT_TYPES or isinstance(
451            expression.find_ancestor(exp.Values, exp.Select), exp.Values
452        ):
453            return "1" if expression.this else "0"
454
455        return "(1 = 1)" if expression.this else "(1 = 0)"
456
457    def is_sql(self, expression: exp.Is) -> str:
458        negate = expression.args.get("negate")
459        if isinstance(expression.expression, exp.Boolean):
460            return self.binary(expression, "<>" if negate else "=")
461        return self.binary(expression, "IS NOT" if negate else "IS")
462
463    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
464        sql = self.sql(expression, "this")
465        properties = expression.args.get("properties")
466
467        start = self._identifier_start
468        if (
469            not sql.startswith("#")
470            and not sql.startswith(f"{start}#")
471            and any(
472                isinstance(prop, exp.TemporaryProperty)
473                for prop in (properties.expressions if properties else [])
474            )
475        ):
476            sql = f"{start}#{sql[len(start) :]}" if sql.startswith(start) else f"#{sql}"
477
478        return sql
479
480    def create_sql(self, expression: exp.Create) -> str:
481        kind = expression.kind
482        exists = expression.args.get("exists")
483        expression.set("exists", None)
484
485        like_property = expression.find(exp.LikeProperty)
486        if like_property:
487            ctas_expression = like_property.this
488        else:
489            ctas_expression = expression.expression
490
491        if kind == "VIEW":
492            expression.this.set("catalog", None)
493            with_ = expression.args.get("with_")
494            if ctas_expression and with_:
495                # We've already preprocessed the Create expression to bubble up any nested CTEs,
496                # but CREATE VIEW actually requires the WITH clause to come after it so we need
497                # to amend the AST by moving the CTEs to the CREATE VIEW statement's query.
498                ctas_expression.set("with_", with_.pop())
499        elif (
500            kind == "FUNCTION"
501            and isinstance(ctas_expression, exp.Return)
502            and isinstance(body := ctas_expression.this.unnest(), exp.Query)
503            and (with_ := expression.args.get("with_"))
504        ):
505            # Similar to the VIEW branch, the table-valued functions require the WITH clause
506            # to stay inside the RETURN body, so we move back any CTEs that were bubbled up.
507            body.set("with_", with_.pop())
508
509        table = expression.find(exp.Table)
510
511        # Convert CTAS statement to SELECT .. INTO ..
512        if kind == "TABLE" and ctas_expression:
513            if isinstance(ctas_expression, exp.UNWRAPPED_QUERIES):
514                ctas_expression = ctas_expression.subquery()
515
516            properties = expression.args.get("properties") or exp.Properties()
517            is_temp = any(isinstance(p, exp.TemporaryProperty) for p in properties.expressions)
518
519            select_into = exp.select("*").from_(exp.alias_(ctas_expression, "temp", table=True))
520            select_into.set("into", exp.Into(this=table, temporary=is_temp))
521
522            if like_property:
523                select_into.limit(0, copy=False)
524
525            sql = self.sql(select_into)
526        else:
527            sql = super().create_sql(expression)
528
529        if exists:
530            identifier = self.sql(exp.Literal.string(exp.table_name(table) if table else ""))
531            sql_with_ctes = self.prepend_ctes(expression, sql)
532            sql_literal = self.sql(exp.Literal.string(sql_with_ctes))
533            if kind == "SCHEMA":
534                return f"""IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = {identifier}) EXEC({sql_literal})"""
535            elif kind == "TABLE":
536                assert table
537                where = exp.and_(
538                    exp.column("TABLE_NAME").eq(table.name),
539                    exp.column("TABLE_SCHEMA").eq(table.db) if table.db else None,
540                    exp.column("TABLE_CATALOG").eq(table.catalog) if table.catalog else None,
541                )
542                return f"""IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE {where}) EXEC({sql_literal})"""
543            elif kind == "INDEX":
544                index = self.sql(exp.Literal.string(expression.this.text("this")))
545                return f"""IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = object_id({identifier}) AND name = {index}) EXEC({sql_literal})"""
546        elif expression.args.get("replace"):
547            sql = sql.replace("CREATE OR REPLACE ", "CREATE OR ALTER ", 1)
548
549        return self.prepend_ctes(expression, sql)
550
551    @generator.unsupported_args("unlogged", "expressions")
552    def into_sql(self, expression: exp.Into) -> str:
553        if expression.args.get("temporary"):
554            # If the Into expression has a temporary property, push this down to the Identifier
555            table = expression.find(exp.Table)
556            if table and isinstance(table.this, exp.Identifier):
557                table.this.set("temporary", True)
558
559        return f"{self.seg('INTO')} {self.sql(expression, 'this')}"
560
561    def count_sql(self, expression: exp.Count) -> str:
562        func_name = "COUNT_BIG" if expression.args.get("big_int") else "COUNT"
563        return rename_func(func_name)(self, expression)
564
565    def countif_sql(self, expression: exp.CountIf) -> str:
566        # T-SQL has no COUNT_IF, so count the rows where the predicate holds instead
567        return self.func(
568            "COUNT", exp.If(this=expression.this, true=exp.Literal.number(1), false=exp.null())
569        )
570
571    def datediff_sql(self, expression: exp.DateDiff) -> str:
572        func_name = "DATEDIFF_BIG" if expression.args.get("big_int") else "DATEDIFF"
573        return date_delta_sql(func_name)(self, expression)
574
575    def offset_sql(self, expression: exp.Offset) -> str:
576        return f"{super().offset_sql(expression)} ROWS"
577
578    def version_sql(self, expression: exp.Version) -> str:
579        name = "SYSTEM_TIME" if expression.name == "TIMESTAMP" else expression.name
580        this = f"FOR {name}"
581        expr = expression.expression
582        kind = expression.text("kind")
583        if kind in ("FROM", "BETWEEN"):
584            args = expr.expressions
585            sep = "TO" if kind == "FROM" else "AND"
586            expr_sql = f"{self.sql(seq_get(args, 0))} {sep} {self.sql(seq_get(args, 1))}"
587        else:
588            expr_sql = self.sql(expr)
589
590        expr_sql = f" {expr_sql}" if expr_sql else ""
591        return f"{this} {kind}{expr_sql}"
592
593    def returnsproperty_sql(self, expression: exp.ReturnsProperty) -> str:
594        table = expression.args.get("table")
595        table = f"{table} " if table else ""
596        return f"RETURNS {table}{self.sql(expression, 'this')}"
597
598    def returning_sql(self, expression: exp.Returning) -> str:
599        into = self.sql(expression, "into")
600        into = self.seg(f"INTO {into}") if into else ""
601        return f"{self.seg('OUTPUT')} {self.expressions(expression, flat=True)}{into}"
602
603    def transaction_sql(self, expression: exp.Transaction) -> str:
604        this = self.sql(expression, "this")
605        this = f" {this}" if this else ""
606        mark = self.sql(expression, "mark")
607        mark = f" WITH MARK {mark}" if mark else ""
608        return f"BEGIN TRANSACTION{this}{mark}"
609
610    def commit_sql(self, expression: exp.Commit) -> str:
611        this = self.sql(expression, "this")
612        this = f" {this}" if this else ""
613        durability = expression.args.get("durability")
614        durability = (
615            f" WITH (DELAYED_DURABILITY = {'ON' if durability else 'OFF'})"
616            if durability is not None
617            else ""
618        )
619        return f"COMMIT TRANSACTION{this}{durability}"
620
621    def rollback_sql(self, expression: exp.Rollback) -> str:
622        this = self.sql(expression, "this")
623        this = f" {this}" if this else ""
624        return f"ROLLBACK TRANSACTION{this}"
625
626    def identifier_sql(self, expression: exp.Identifier) -> str:
627        identifier = super().identifier_sql(expression)
628
629        if expression.args.get("global_"):
630            prefix = "##"
631        elif expression.args.get("temporary"):
632            prefix = "#"
633        else:
634            return identifier
635
636        start = self._identifier_start
637        if expression.quoted and identifier.startswith(start):
638            return f"{start}{prefix}{identifier[len(start) :]}"
639
640        return f"{prefix}{identifier}"
641
642    def constraint_sql(self, expression: exp.Constraint) -> str:
643        this = self.sql(expression, "this")
644        expressions = self.expressions(expression, flat=True, sep=" ")
645        return f"CONSTRAINT {this} {expressions}"
646
647    def length_sql(self, expression: exp.Length) -> str:
648        return self._uncast_text(expression, "LEN")
649
650    def right_sql(self, expression: exp.Right) -> str:
651        return self._uncast_text(expression, "RIGHT")
652
653    def left_sql(self, expression: exp.Left) -> str:
654        return self._uncast_text(expression, "LEFT")
655
656    def _uncast_text(self, expression: exp.Expr, name: str) -> str:
657        this = expression.this
658        if isinstance(this, exp.Cast) and this.is_type(exp.DType.TEXT):
659            this_sql = self.sql(this, "this")
660        else:
661            this_sql = self.sql(this)
662        expression_sql = self.sql(expression, "expression")
663        return self.func(name, this_sql, expression_sql if expression_sql else None)
664
665    def partition_sql(self, expression: exp.Partition) -> str:
666        return f"WITH (PARTITIONS({self.expressions(expression, flat=True)}))"
667
668    def alter_sql(self, expression: exp.Alter) -> str:
669        actions = expression.args.get("actions") or []
670        if len(actions) == 1 and isinstance(actions[0], exp.AlterRename):
671            old_name = self.sql(exp.Literal.string(self.sql(expression.this)))
672            new_name = self.sql(exp.Literal.string(actions[0].this.name))
673            return f"EXEC sp_rename {old_name}, {new_name}"
674        return super().alter_sql(expression)
675
676    def drop_sql(self, expression: exp.Drop) -> str:
677        if expression.args["kind"] == "VIEW":
678            for table in expression.args.get("tables") or []:
679                table.set("catalog", None)
680        return super().drop_sql(expression)
681
682    def options_modifier(self, expression: exp.Expr) -> str:
683        options = self.expressions(expression, key="options")
684        return f" OPTION{self.wrap(options)}" if options else ""
685
686    def dpipe_sql(self, expression: exp.DPipe) -> str:
687        return self.sql(reduce(lambda x, y: exp.Add(this=x, expression=y), expression.flatten()))
688
689    def isascii_sql(self, expression: exp.IsAscii) -> str:
690        return f"(PATINDEX(CONVERT(VARCHAR(MAX), 0x255b5e002d7f5d25) COLLATE Latin1_General_BIN, {self.sql(expression.this)}) = 0)"
691
692    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
693        this = super().columndef_sql(expression, sep)
694        default = self.sql(expression, "default")
695        default = f" = {default}" if default else ""
696        output = self.sql(expression, "output")
697        output = f" {output}" if output else ""
698        return f"{this}{default}{output}"
699
700    def coalesce_sql(self, expression: exp.Coalesce) -> str:
701        func_name = "ISNULL" if expression.args.get("is_null") else "COALESCE"
702        return rename_func(func_name)(self, expression)
703
704    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
705        this = self.sql(expression, "this")
706        expressions = self.expressions(expression)
707        expressions = (
708            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
709        )
710        return f"{this}{expressions}" if expressions.strip() != "" else this
711
712    def ifblock_sql(self, expression: exp.IfBlock) -> str:
713        this = self.sql(expression, "this")
714        true = self.sql(expression, "true")
715        true = f" {true}" if true else " "
716        false = self.sql(expression, "false")
717        false = f"; ELSE BEGIN {false}" if false else ""
718        return f"IF {this} BEGIN{true}{false}"
719
720    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
721        this = self.sql(expression, "this")
722        body = self.sql(expression, "body")
723        body = f" {body}" if body else " "
724        return f"WHILE {this} BEGIN{body}"
725
726    def execute_sql(self, expression: exp.Execute) -> str:
727        this = self.sql(expression, "this")
728        expressions = self.expressions(expression)
729        expressions = f" {expressions}" if expressions else ""
730        return_status = self.sql(expression, "return_status")
731        return_status = f"{return_status} = " if return_status else ""
732        return f"EXECUTE {return_status}{this}{expressions}"
733
734    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
735        return self.execute_sql(expression)
DATE_PART_UNMAPPING = {'WEEKISO': 'ISO_WEEK', 'DAYOFWEEK': 'WEEKDAY', 'TIMEZONE_MINUTE': 'TZOFFSET'}
SET_OP_MODIFIERS = ('limit', 'offset', 'order', 'for_', 'options')
def qualify_derived_table_outputs( expression: sqlglot.expressions.core.Expr) -> sqlglot.expressions.core.Expr:
 74def qualify_derived_table_outputs(expression: exp.Expr) -> exp.Expr:
 75    """Ensures all (unnamed) output columns are aliased for CTEs and Subqueries."""
 76    alias = expression.args.get("alias")
 77
 78    if (
 79        isinstance(expression, (exp.CTE, exp.Subquery))
 80        and isinstance(alias, exp.TableAlias)
 81        and not alias.columns
 82        and isinstance(expression.this, exp.Query)
 83    ):
 84        from sqlglot.dialects.tsql import TSQL
 85        from sqlglot.optimizer.qualify_columns import qualify_outputs
 86
 87        # We keep track of the unaliased column projection indexes instead of the expressions
 88        # themselves, because the latter are going to be replaced by new nodes when the aliases
 89        # are added and hence we won't be able to reach these newly added Alias parents
 90        query = expression.this
 91        unaliased_column_indexes = (
 92            i for i, c in enumerate(query.selects) if isinstance(c, exp.Column) and not c.alias
 93        )
 94
 95        qualify_outputs(query, dialect=TSQL())
 96
 97        # Preserve the quoting information of columns for newly added Alias nodes
 98        query_selects = query.selects
 99        for select_index in unaliased_column_indexes:
100            alias = query_selects[select_index]
101            column = alias.this
102            if isinstance(column.this, exp.Identifier):
103                alias.args["alias"].set("quoted", column.this.quoted)
104
105    return expression

Ensures all (unnamed) output columns are aliased for CTEs and Subqueries.

class TSQLGenerator(sqlglot.generator.Generator):
135class TSQLGenerator(generator.Generator):
136    SELECT_KINDS: tuple[str, ...] = ()
137    TRY_SUPPORTED = False
138    SUPPORTS_UESCAPE = False
139    SUPPORTS_DECODE_CASE = False
140
141    AFTER_HAVING_MODIFIER_TRANSFORMS = generator.AFTER_HAVING_MODIFIER_TRANSFORMS
142
143    LIMIT_IS_TOP = True
144    SET_OP_LIMITS = True
145    QUERY_HINTS = False
146    RETURNING_END = False
147    NVL2_SUPPORTED = False
148    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = False
149    ALTER_TABLE_ADD_COLUMNS_WRAPPED = False
150    LIMIT_FETCH = "FETCH"
151    COMPUTED_COLUMN_WITH_TYPE = False
152    CTE_RECURSIVE_KEYWORD_REQUIRED = False
153    ENSURE_BOOLS = True
154    NULL_ORDERING_SUPPORTED: bool | None = None
155    SUPPORTS_SINGLE_ARG_CONCAT = False
156    TABLESAMPLE_SEED_KEYWORD = "REPEATABLE"
157    SUPPORTS_SELECT_INTO = True
158    JSON_PATH_BRACKETED_KEY_SUPPORTED = False
159    SUPPORTS_TO_NUMBER = False
160    COPY_PARAMS_EQ_REQUIRED = True
161    PARSE_JSON_NAME: str | None = None
162    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
163    ALTER_SET_WRAPPED = True
164    ALTER_SET_TYPE = ""
165    SUPPORTS_ALTER_COLUMN_NULLABILITY = True
166
167    EXPRESSIONS_WITHOUT_NESTED_CTES = {
168        exp.Create,
169        exp.Delete,
170        exp.Insert,
171        exp.Intersect,
172        exp.Except,
173        exp.Merge,
174        exp.Select,
175        exp.Subquery,
176        exp.Union,
177        exp.Update,
178    }
179
180    SUPPORTED_JSON_PATH_PARTS = {
181        exp.JSONPathKey,
182        exp.JSONPathRoot,
183        exp.JSONPathSubscript,
184    }
185
186    TYPE_MAPPING = {
187        **{
188            k: v
189            for k, v in generator.Generator.TYPE_MAPPING.items()
190            if k not in (exp.DType.NCHAR, exp.DType.NVARCHAR)
191        },
192        exp.DType.BOOLEAN: "BIT",
193        exp.DType.DATETIME2: "DATETIME2",
194        exp.DType.DECIMAL: "NUMERIC",
195        exp.DType.DOUBLE: "FLOAT",
196        exp.DType.INT: "INTEGER",
197        exp.DType.ROWVERSION: "ROWVERSION",
198        exp.DType.TEXT: "VARCHAR(MAX)",
199        exp.DType.TIMESTAMP: "DATETIME2",
200        exp.DType.TIMESTAMPNTZ: "DATETIME2",
201        exp.DType.TIMESTAMPTZ: "DATETIMEOFFSET",
202        exp.DType.SMALLDATETIME: "SMALLDATETIME",
203        exp.DType.UTINYINT: "TINYINT",
204        exp.DType.VARIANT: "SQL_VARIANT",
205        exp.DType.UUID: "UNIQUEIDENTIFIER",
206    }
207
208    TRANSFORMS = {
209        **{k: v for k, v in generator.Generator.TRANSFORMS.items() if k != exp.ReturnsProperty},
210        exp.AnyValue: any_value_to_max_sql,
211        exp.Atan2: rename_func("ATN2"),
212        exp.ArrayToString: rename_func("STRING_AGG"),
213        exp.AutoIncrementColumnConstraint: lambda *_: "IDENTITY",
214        exp.Ceil: rename_func("CEILING"),
215        exp.Chr: rename_func("CHAR"),
216        exp.DateAdd: date_delta_sql("DATEADD"),
217        exp.CTE: transforms.preprocess([qualify_derived_table_outputs]),
218        exp.CurrentDate: rename_func("GETDATE"),
219        exp.CurrentTimestamp: rename_func("GETDATE"),
220        exp.CurrentTimestampLTZ: rename_func("SYSDATETIMEOFFSET"),
221        exp.DateStrToDate: datestrtodate_sql,
222        exp.Day: remove_ts_or_ds_to_date(),
223        exp.Filter: no_filter_sql,
224        exp.GeneratedAsIdentityColumnConstraint: generatedasidentitycolumnconstraint_sql,
225        exp.GroupConcat: _string_agg_sql,
226        exp.If: rename_func("IIF"),
227        exp.JSONExtract: _json_extract_sql,
228        exp.JSONExtractScalar: _json_extract_sql,
229        exp.LastDay: lambda self, e: self.func("EOMONTH", e.this),
230        exp.Ln: rename_func("LOG"),
231        exp.Max: max_or_greatest,
232        exp.MD5: lambda self, e: self.func("HASHBYTES", exp.Literal.string("MD5"), e.this),
233        exp.Min: min_or_least,
234        exp.Month: remove_ts_or_ds_to_date(),
235        exp.NumberToStr: _format_sql,
236        exp.Repeat: rename_func("REPLICATE"),
237        exp.CurrentSchema: rename_func("SCHEMA_NAME"),
238        exp.Select: transforms.preprocess(
239            [
240                transforms.eliminate_distinct_on,
241                transforms.eliminate_semi_and_anti_joins,
242                transforms.eliminate_qualify,
243                transforms.unnest_generate_date_array_using_recursive_cte,
244            ]
245        ),
246        exp.Stddev: rename_func("STDEV"),
247        exp.StrPosition: lambda self, e: strposition_sql(
248            self, e, func_name="CHARINDEX", supports_position=True
249        ),
250        exp.Subquery: transforms.preprocess([qualify_derived_table_outputs]),
251        exp.SHA: lambda self, e: self.func("HASHBYTES", exp.Literal.string("SHA1"), e.this),
252        exp.SHA1Digest: lambda self, e: self.func("HASHBYTES", exp.Literal.string("SHA1"), e.this),
253        exp.SHA2: lambda self, e: self.func(
254            "HASHBYTES", exp.Literal.string(f"SHA2_{e.args.get('length', 256)}"), e.this
255        ),
256        exp.TemporaryProperty: lambda self, e: "",
257        exp.TimeStrToTime: _timestrtotime_sql,
258        exp.TimeToStr: _format_sql,
259        exp.TimestampAdd: date_delta_sql("DATEADD"),
260        exp.Trim: trim_sql,
261        exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True),
262        exp.TsOrDsDiff: date_delta_sql("DATEDIFF"),
263        exp.TimestampTrunc: lambda self, e: self.func("DATETRUNC", e.unit, e.this),
264        exp.Trunc: lambda self, e: self.func(
265            "ROUND",
266            e.this,
267            e.args.get("decimals") or exp.Literal.number(0),
268            exp.Literal.number(1),
269        ),
270        exp.Uuid: lambda *_: "NEWID()",
271        exp.Year: remove_ts_or_ds_to_date(),
272        exp.DateFromParts: rename_func("DATEFROMPARTS"),
273    }
274
275    PROPERTIES_LOCATION = {
276        **generator.Generator.PROPERTIES_LOCATION,
277        exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
278    }
279
280    def scope_resolution(self, rhs: str, scope_name: str) -> str:
281        return f"{scope_name}::{rhs}"
282
283    def select_sql(self, expression: exp.Select) -> str:
284        self._prepare_limit_offset(expression)
285
286        # Handles transpiling a query like the following to T-SQL:
287        #   SELECT 1 AS x ORDER BY x NULLS FIRST FETCH FIRST 1 ROWS ONLY UNION ALL SELECT 2 AS x
288        if isinstance(expression.parent, exp.SetOperation) and isinstance(
289            expression.args.get("limit"), exp.Fetch
290        ):
291            return self.sql(
292                exp.select("*").from_(expression.subquery("_l_0", copy=False), copy=False)
293            )
294
295        return super().select_sql(expression)
296
297    def set_operations(self, expression: exp.SetOperation) -> str:
298        limit = expression.args.get("limit")
299        offset = expression.args.get("offset")
300        order = expression.args.get("order")
301
302        # Set operations cannot order by the CASE used to emulate null ordering,
303        # or by expressions that aren't in their select list.
304        wrap_order = False
305        if order:
306            selects = {select.unalias().unnest() for select in expression.selects}
307            for ordered in order.expressions:
308                this = ordered.this.unnest()
309                if this.is_int:
310                    continue
311
312                desc = ordered.args.get("desc")
313                nulls_first = ordered.args.get("nulls_first")
314
315                emulate_null_ordering = (desc and nulls_first) or (not desc and not nulls_first)
316                if emulate_null_ordering or (
317                    not isinstance(this, exp.Column) and this not in selects
318                ):
319                    wrap_order = True
320                    break
321
322        if (
323            wrap_order
324            or (isinstance(limit, exp.Limit) and not offset)
325            or (not order and (offset or isinstance(limit, exp.Fetch)))
326        ):
327            select = self._move_ctes_to_top_level(
328                exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
329            )
330            for arg in SET_OP_MODIFIERS:
331                value = expression.args.get(arg)
332                if value:
333                    expression.set(arg, None)
334                    select.set(arg, value)
335
336            return self.sql(select)
337
338        self._prepare_limit_offset(expression)
339        return super().set_operations(expression)
340
341    def _prepare_limit_offset(self, expression: exp.Query) -> None:
342        limit = expression.args.get("limit")
343        offset = expression.args.get("offset")
344
345        if isinstance(limit, exp.Fetch) and not offset:
346            # Dialects like Oracle can FETCH directly from a row set but
347            # T-SQL requires an ORDER BY + OFFSET clause in order to FETCH
348            offset = exp.Offset(expression=exp.Literal.number(0))
349            expression.set("offset", offset)
350
351        if offset:
352            if not expression.args.get("order"):
353                # ORDER BY is required in order to use OFFSET in a query, so we use
354                # a noop order by, since we don't really care about the order.
355                # See: https://www.microsoftpressstore.com/articles/article.aspx?p=2314819
356                expression.order_by(exp.select(exp.null()).subquery(), copy=False)
357
358            if isinstance(limit, exp.Limit) and not limit.is_limit_all:
359                # TOP and OFFSET can't be combined, we need use FETCH instead of TOP
360                # we replace here because otherwise TOP would be generated in select_sql
361                limit.replace(exp.Fetch(direction="FIRST", count=limit.expression))
362
363    def convert_sql(self, expression: exp.Convert) -> str:
364        name = "TRY_CONVERT" if expression.args.get("safe") else "CONVERT"
365        return self.func(name, expression.this, expression.expression, expression.args.get("style"))
366
367    def queryoption_sql(self, expression: exp.QueryOption) -> str:
368        option = self.sql(expression, "this")
369        value = self.sql(expression, "expression")
370        if value:
371            optional_equal_sign = "= " if option in OPTIONS_THAT_REQUIRE_EQUAL else ""
372            return f"{option} {optional_equal_sign}{value}"
373        return option
374
375    def lateral_op(self, expression: exp.Lateral) -> str:
376        cross_apply = expression.args.get("cross_apply")
377        if cross_apply is True:
378            return "CROSS APPLY"
379        if cross_apply is False:
380            return "OUTER APPLY"
381
382        # TODO: perhaps we can check if the parent is a Join and transpile it appropriately
383        self.unsupported("LATERAL clause is not supported.")
384        return "LATERAL"
385
386    def splitpart_sql(self, expression: exp.SplitPart) -> str:
387        this = expression.this
388        split_count = len(this.name.split("."))
389        delimiter = expression.args.get("delimiter")
390        part_index = expression.args.get("part_index")
391
392        if (
393            not all(isinstance(arg, exp.Literal) for arg in (this, delimiter, part_index))
394            or (delimiter and delimiter.name != ".")
395            or not part_index
396            or split_count > 4
397        ):
398            self.unsupported(
399                "SPLIT_PART can be transpiled to PARSENAME only for '.' delimiter and literal values"
400            )
401            return ""
402
403        return self.func(
404            "PARSENAME", this, exp.Literal.number(split_count + 1 - part_index.to_py())
405        )
406
407    def extract_sql(self, expression: exp.Extract) -> str:
408        part = expression.this
409        name = DATE_PART_UNMAPPING.get(part.name.upper()) or part
410
411        return self.func("DATEPART", name, expression.expression)
412
413    def timefromparts_sql(self, expression: exp.TimeFromParts) -> str:
414        nano = expression.args.get("nano")
415        if nano is not None:
416            nano.pop()
417            self.unsupported("Specifying nanoseconds is not supported in TIMEFROMPARTS.")
418
419        if expression.args.get("fractions") is None:
420            expression.set("fractions", exp.Literal.number(0))
421        if expression.args.get("precision") is None:
422            expression.set("precision", exp.Literal.number(0))
423
424        return rename_func("TIMEFROMPARTS")(self, expression)
425
426    def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str:
427        zone = expression.args.get("zone")
428        if zone is not None:
429            zone.pop()
430            self.unsupported("Time zone is not supported in DATETIMEFROMPARTS.")
431
432        nano = expression.args.get("nano")
433        if nano is not None:
434            nano.pop()
435            self.unsupported("Specifying nanoseconds is not supported in DATETIMEFROMPARTS.")
436
437        if expression.args.get("milli") is None:
438            expression.set("milli", exp.Literal.number(0))
439
440        return rename_func("DATETIMEFROMPARTS")(self, expression)
441
442    def setitem_sql(self, expression: exp.SetItem) -> str:
443        this = expression.this
444        if isinstance(this, exp.EQ) and not isinstance(this.left, exp.Parameter):
445            # T-SQL does not use '=' in SET command, except when the LHS is a variable.
446            return f"{self.sql(this.left)} {self.sql(this.right)}"
447
448        return super().setitem_sql(expression)
449
450    def boolean_sql(self, expression: exp.Boolean) -> str:
451        if type(expression.parent) in BIT_TYPES or isinstance(
452            expression.find_ancestor(exp.Values, exp.Select), exp.Values
453        ):
454            return "1" if expression.this else "0"
455
456        return "(1 = 1)" if expression.this else "(1 = 0)"
457
458    def is_sql(self, expression: exp.Is) -> str:
459        negate = expression.args.get("negate")
460        if isinstance(expression.expression, exp.Boolean):
461            return self.binary(expression, "<>" if negate else "=")
462        return self.binary(expression, "IS NOT" if negate else "IS")
463
464    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
465        sql = self.sql(expression, "this")
466        properties = expression.args.get("properties")
467
468        start = self._identifier_start
469        if (
470            not sql.startswith("#")
471            and not sql.startswith(f"{start}#")
472            and any(
473                isinstance(prop, exp.TemporaryProperty)
474                for prop in (properties.expressions if properties else [])
475            )
476        ):
477            sql = f"{start}#{sql[len(start) :]}" if sql.startswith(start) else f"#{sql}"
478
479        return sql
480
481    def create_sql(self, expression: exp.Create) -> str:
482        kind = expression.kind
483        exists = expression.args.get("exists")
484        expression.set("exists", None)
485
486        like_property = expression.find(exp.LikeProperty)
487        if like_property:
488            ctas_expression = like_property.this
489        else:
490            ctas_expression = expression.expression
491
492        if kind == "VIEW":
493            expression.this.set("catalog", None)
494            with_ = expression.args.get("with_")
495            if ctas_expression and with_:
496                # We've already preprocessed the Create expression to bubble up any nested CTEs,
497                # but CREATE VIEW actually requires the WITH clause to come after it so we need
498                # to amend the AST by moving the CTEs to the CREATE VIEW statement's query.
499                ctas_expression.set("with_", with_.pop())
500        elif (
501            kind == "FUNCTION"
502            and isinstance(ctas_expression, exp.Return)
503            and isinstance(body := ctas_expression.this.unnest(), exp.Query)
504            and (with_ := expression.args.get("with_"))
505        ):
506            # Similar to the VIEW branch, the table-valued functions require the WITH clause
507            # to stay inside the RETURN body, so we move back any CTEs that were bubbled up.
508            body.set("with_", with_.pop())
509
510        table = expression.find(exp.Table)
511
512        # Convert CTAS statement to SELECT .. INTO ..
513        if kind == "TABLE" and ctas_expression:
514            if isinstance(ctas_expression, exp.UNWRAPPED_QUERIES):
515                ctas_expression = ctas_expression.subquery()
516
517            properties = expression.args.get("properties") or exp.Properties()
518            is_temp = any(isinstance(p, exp.TemporaryProperty) for p in properties.expressions)
519
520            select_into = exp.select("*").from_(exp.alias_(ctas_expression, "temp", table=True))
521            select_into.set("into", exp.Into(this=table, temporary=is_temp))
522
523            if like_property:
524                select_into.limit(0, copy=False)
525
526            sql = self.sql(select_into)
527        else:
528            sql = super().create_sql(expression)
529
530        if exists:
531            identifier = self.sql(exp.Literal.string(exp.table_name(table) if table else ""))
532            sql_with_ctes = self.prepend_ctes(expression, sql)
533            sql_literal = self.sql(exp.Literal.string(sql_with_ctes))
534            if kind == "SCHEMA":
535                return f"""IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = {identifier}) EXEC({sql_literal})"""
536            elif kind == "TABLE":
537                assert table
538                where = exp.and_(
539                    exp.column("TABLE_NAME").eq(table.name),
540                    exp.column("TABLE_SCHEMA").eq(table.db) if table.db else None,
541                    exp.column("TABLE_CATALOG").eq(table.catalog) if table.catalog else None,
542                )
543                return f"""IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE {where}) EXEC({sql_literal})"""
544            elif kind == "INDEX":
545                index = self.sql(exp.Literal.string(expression.this.text("this")))
546                return f"""IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = object_id({identifier}) AND name = {index}) EXEC({sql_literal})"""
547        elif expression.args.get("replace"):
548            sql = sql.replace("CREATE OR REPLACE ", "CREATE OR ALTER ", 1)
549
550        return self.prepend_ctes(expression, sql)
551
552    @generator.unsupported_args("unlogged", "expressions")
553    def into_sql(self, expression: exp.Into) -> str:
554        if expression.args.get("temporary"):
555            # If the Into expression has a temporary property, push this down to the Identifier
556            table = expression.find(exp.Table)
557            if table and isinstance(table.this, exp.Identifier):
558                table.this.set("temporary", True)
559
560        return f"{self.seg('INTO')} {self.sql(expression, 'this')}"
561
562    def count_sql(self, expression: exp.Count) -> str:
563        func_name = "COUNT_BIG" if expression.args.get("big_int") else "COUNT"
564        return rename_func(func_name)(self, expression)
565
566    def countif_sql(self, expression: exp.CountIf) -> str:
567        # T-SQL has no COUNT_IF, so count the rows where the predicate holds instead
568        return self.func(
569            "COUNT", exp.If(this=expression.this, true=exp.Literal.number(1), false=exp.null())
570        )
571
572    def datediff_sql(self, expression: exp.DateDiff) -> str:
573        func_name = "DATEDIFF_BIG" if expression.args.get("big_int") else "DATEDIFF"
574        return date_delta_sql(func_name)(self, expression)
575
576    def offset_sql(self, expression: exp.Offset) -> str:
577        return f"{super().offset_sql(expression)} ROWS"
578
579    def version_sql(self, expression: exp.Version) -> str:
580        name = "SYSTEM_TIME" if expression.name == "TIMESTAMP" else expression.name
581        this = f"FOR {name}"
582        expr = expression.expression
583        kind = expression.text("kind")
584        if kind in ("FROM", "BETWEEN"):
585            args = expr.expressions
586            sep = "TO" if kind == "FROM" else "AND"
587            expr_sql = f"{self.sql(seq_get(args, 0))} {sep} {self.sql(seq_get(args, 1))}"
588        else:
589            expr_sql = self.sql(expr)
590
591        expr_sql = f" {expr_sql}" if expr_sql else ""
592        return f"{this} {kind}{expr_sql}"
593
594    def returnsproperty_sql(self, expression: exp.ReturnsProperty) -> str:
595        table = expression.args.get("table")
596        table = f"{table} " if table else ""
597        return f"RETURNS {table}{self.sql(expression, 'this')}"
598
599    def returning_sql(self, expression: exp.Returning) -> str:
600        into = self.sql(expression, "into")
601        into = self.seg(f"INTO {into}") if into else ""
602        return f"{self.seg('OUTPUT')} {self.expressions(expression, flat=True)}{into}"
603
604    def transaction_sql(self, expression: exp.Transaction) -> str:
605        this = self.sql(expression, "this")
606        this = f" {this}" if this else ""
607        mark = self.sql(expression, "mark")
608        mark = f" WITH MARK {mark}" if mark else ""
609        return f"BEGIN TRANSACTION{this}{mark}"
610
611    def commit_sql(self, expression: exp.Commit) -> str:
612        this = self.sql(expression, "this")
613        this = f" {this}" if this else ""
614        durability = expression.args.get("durability")
615        durability = (
616            f" WITH (DELAYED_DURABILITY = {'ON' if durability else 'OFF'})"
617            if durability is not None
618            else ""
619        )
620        return f"COMMIT TRANSACTION{this}{durability}"
621
622    def rollback_sql(self, expression: exp.Rollback) -> str:
623        this = self.sql(expression, "this")
624        this = f" {this}" if this else ""
625        return f"ROLLBACK TRANSACTION{this}"
626
627    def identifier_sql(self, expression: exp.Identifier) -> str:
628        identifier = super().identifier_sql(expression)
629
630        if expression.args.get("global_"):
631            prefix = "##"
632        elif expression.args.get("temporary"):
633            prefix = "#"
634        else:
635            return identifier
636
637        start = self._identifier_start
638        if expression.quoted and identifier.startswith(start):
639            return f"{start}{prefix}{identifier[len(start) :]}"
640
641        return f"{prefix}{identifier}"
642
643    def constraint_sql(self, expression: exp.Constraint) -> str:
644        this = self.sql(expression, "this")
645        expressions = self.expressions(expression, flat=True, sep=" ")
646        return f"CONSTRAINT {this} {expressions}"
647
648    def length_sql(self, expression: exp.Length) -> str:
649        return self._uncast_text(expression, "LEN")
650
651    def right_sql(self, expression: exp.Right) -> str:
652        return self._uncast_text(expression, "RIGHT")
653
654    def left_sql(self, expression: exp.Left) -> str:
655        return self._uncast_text(expression, "LEFT")
656
657    def _uncast_text(self, expression: exp.Expr, name: str) -> str:
658        this = expression.this
659        if isinstance(this, exp.Cast) and this.is_type(exp.DType.TEXT):
660            this_sql = self.sql(this, "this")
661        else:
662            this_sql = self.sql(this)
663        expression_sql = self.sql(expression, "expression")
664        return self.func(name, this_sql, expression_sql if expression_sql else None)
665
666    def partition_sql(self, expression: exp.Partition) -> str:
667        return f"WITH (PARTITIONS({self.expressions(expression, flat=True)}))"
668
669    def alter_sql(self, expression: exp.Alter) -> str:
670        actions = expression.args.get("actions") or []
671        if len(actions) == 1 and isinstance(actions[0], exp.AlterRename):
672            old_name = self.sql(exp.Literal.string(self.sql(expression.this)))
673            new_name = self.sql(exp.Literal.string(actions[0].this.name))
674            return f"EXEC sp_rename {old_name}, {new_name}"
675        return super().alter_sql(expression)
676
677    def drop_sql(self, expression: exp.Drop) -> str:
678        if expression.args["kind"] == "VIEW":
679            for table in expression.args.get("tables") or []:
680                table.set("catalog", None)
681        return super().drop_sql(expression)
682
683    def options_modifier(self, expression: exp.Expr) -> str:
684        options = self.expressions(expression, key="options")
685        return f" OPTION{self.wrap(options)}" if options else ""
686
687    def dpipe_sql(self, expression: exp.DPipe) -> str:
688        return self.sql(reduce(lambda x, y: exp.Add(this=x, expression=y), expression.flatten()))
689
690    def isascii_sql(self, expression: exp.IsAscii) -> str:
691        return f"(PATINDEX(CONVERT(VARCHAR(MAX), 0x255b5e002d7f5d25) COLLATE Latin1_General_BIN, {self.sql(expression.this)}) = 0)"
692
693    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
694        this = super().columndef_sql(expression, sep)
695        default = self.sql(expression, "default")
696        default = f" = {default}" if default else ""
697        output = self.sql(expression, "output")
698        output = f" {output}" if output else ""
699        return f"{this}{default}{output}"
700
701    def coalesce_sql(self, expression: exp.Coalesce) -> str:
702        func_name = "ISNULL" if expression.args.get("is_null") else "COALESCE"
703        return rename_func(func_name)(self, expression)
704
705    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
706        this = self.sql(expression, "this")
707        expressions = self.expressions(expression)
708        expressions = (
709            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
710        )
711        return f"{this}{expressions}" if expressions.strip() != "" else this
712
713    def ifblock_sql(self, expression: exp.IfBlock) -> str:
714        this = self.sql(expression, "this")
715        true = self.sql(expression, "true")
716        true = f" {true}" if true else " "
717        false = self.sql(expression, "false")
718        false = f"; ELSE BEGIN {false}" if false else ""
719        return f"IF {this} BEGIN{true}{false}"
720
721    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
722        this = self.sql(expression, "this")
723        body = self.sql(expression, "body")
724        body = f" {body}" if body else " "
725        return f"WHILE {this} BEGIN{body}"
726
727    def execute_sql(self, expression: exp.Execute) -> str:
728        this = self.sql(expression, "this")
729        expressions = self.expressions(expression)
730        expressions = f" {expressions}" if expressions else ""
731        return_status = self.sql(expression, "return_status")
732        return_status = f"{return_status} = " if return_status else ""
733        return f"EXECUTE {return_status}{this}{expressions}"
734
735    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
736        return self.execute_sql(expression)

Generator converts a given syntax tree to the corresponding SQL string.

Arguments:
  • pretty: Whether to format the produced SQL string. Default: False.
  • identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True: Always quote except for specials cases. 'safe': Only quote identifiers that are case insensitive.
  • normalize: Whether to normalize identifiers to lowercase. Default: False.
  • pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
  • indent: The indentation size in a formatted string. For example, this affects the indentation of subqueries and filters under a WHERE clause. Default: 2.
  • normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
  • unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
  • max_unsupported: 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: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. 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 to preserve comments in the output SQL code. Default: True
SELECT_KINDS: tuple[str, ...] = ()
TRY_SUPPORTED = False
SUPPORTS_UESCAPE = False
SUPPORTS_DECODE_CASE = False
AFTER_HAVING_MODIFIER_TRANSFORMS = {'windows': <function <lambda>>, 'qualify': <function <lambda>>}
LIMIT_IS_TOP = True
SET_OP_LIMITS = True
QUERY_HINTS = False
RETURNING_END = False
NVL2_SUPPORTED = False
ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = False
ALTER_TABLE_ADD_COLUMNS_WRAPPED = False
LIMIT_FETCH = 'FETCH'
COMPUTED_COLUMN_WITH_TYPE = False
CTE_RECURSIVE_KEYWORD_REQUIRED = False
ENSURE_BOOLS = True
NULL_ORDERING_SUPPORTED: bool | None = None
SUPPORTS_SINGLE_ARG_CONCAT = False
TABLESAMPLE_SEED_KEYWORD = 'REPEATABLE'
SUPPORTS_SELECT_INTO = True
JSON_PATH_BRACKETED_KEY_SUPPORTED = False
SUPPORTS_TO_NUMBER = False
COPY_PARAMS_EQ_REQUIRED = True
PARSE_JSON_NAME: str | None = None
EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
ALTER_SET_WRAPPED = True
ALTER_SET_TYPE = ''
SUPPORTS_ALTER_COLUMN_NULLABILITY = True
TYPE_MAPPING = {<DType.DATETIME2: 'DATETIME2'>: 'DATETIME2', <DType.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <DType.LONGTEXT: 'LONGTEXT'>: 'TEXT', <DType.TINYTEXT: 'TINYTEXT'>: 'TEXT', <DType.BLOB: 'BLOB'>: 'VARBINARY', <DType.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <DType.LONGBLOB: 'LONGBLOB'>: 'BLOB', <DType.TINYBLOB: 'TINYBLOB'>: 'BLOB', <DType.INET: 'INET'>: 'INET', <DType.ROWVERSION: 'ROWVERSION'>: 'ROWVERSION', <DType.SMALLDATETIME: 'SMALLDATETIME'>: 'SMALLDATETIME', <DType.BOOLEAN: 'BOOLEAN'>: 'BIT', <DType.DECIMAL: 'DECIMAL'>: 'NUMERIC', <DType.DOUBLE: 'DOUBLE'>: 'FLOAT', <DType.INT: 'INT'>: 'INTEGER', <DType.TEXT: 'TEXT'>: 'VARCHAR(MAX)', <DType.TIMESTAMP: 'TIMESTAMP'>: 'DATETIME2', <DType.TIMESTAMPNTZ: 'TIMESTAMPNTZ'>: 'DATETIME2', <DType.TIMESTAMPTZ: 'TIMESTAMPTZ'>: 'DATETIMEOFFSET', <DType.UTINYINT: 'UTINYINT'>: 'TINYINT', <DType.VARIANT: 'VARIANT'>: 'SQL_VARIANT', <DType.UUID: 'UUID'>: 'UNIQUEIDENTIFIER'}
TRANSFORMS = {<class 'sqlglot.expressions.query.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.core.Adjacent'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainedBy'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.AssumeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.BinaryColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Ceil'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.constraints.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.string.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CopyTagsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.SessionUser'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApiProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.EndStatement'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HybridProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.IgnoreGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.datatypes.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsTopKey'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBPathExists'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObject'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObjectAgg'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.NetFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsRight'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InvisibleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ZeroFillColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.SafeFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <function TSQLGenerator.<lambda>>, <class 'sqlglot.expressions.constraints.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.TriggerExecute'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Variadic'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithoutRowidProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ForceProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.aggregate.AnyValue'>: <function any_value_to_max_sql>, <class 'sqlglot.expressions.math.Atan2'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.ArrayToString'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.constraints.AutoIncrementColumnConstraint'>: <function TSQLGenerator.<lambda>>, <class 'sqlglot.expressions.string.Chr'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.DateAdd'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.query.CTE'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.temporal.CurrentDate'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.CurrentTimestamp'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.CurrentTimestampLTZ'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.DateStrToDate'>: <function datestrtodate_sql>, <class 'sqlglot.expressions.temporal.Day'>: <function remove_ts_or_ds_to_date.<locals>.func>, <class 'sqlglot.expressions.core.Filter'>: <function no_filter_sql>, <class 'sqlglot.expressions.constraints.GeneratedAsIdentityColumnConstraint'>: <function generatedasidentitycolumnconstraint_sql>, <class 'sqlglot.expressions.aggregate.GroupConcat'>: <function _string_agg_sql>, <class 'sqlglot.expressions.functions.If'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.json.JSONExtract'>: <function _json_extract_sql>, <class 'sqlglot.expressions.json.JSONExtractScalar'>: <function _json_extract_sql>, <class 'sqlglot.expressions.temporal.LastDay'>: <function TSQLGenerator.<lambda>>, <class 'sqlglot.expressions.math.Ln'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.Max'>: <function max_or_greatest>, <class 'sqlglot.expressions.string.MD5'>: <function TSQLGenerator.<lambda>>, <class 'sqlglot.expressions.aggregate.Min'>: <function min_or_least>, <class 'sqlglot.expressions.temporal.Month'>: <function remove_ts_or_ds_to_date.<locals>.func>, <class 'sqlglot.expressions.string.NumberToStr'>: <function _format_sql>, <class 'sqlglot.expressions.string.Repeat'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.functions.CurrentSchema'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.query.Select'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.aggregate.Stddev'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.StrPosition'>: <function TSQLGenerator.<lambda>>, <class 'sqlglot.expressions.query.Subquery'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.string.SHA'>: <function TSQLGenerator.<lambda>>, <class 'sqlglot.expressions.string.SHA1Digest'>: <function TSQLGenerator.<lambda>>, <class 'sqlglot.expressions.string.SHA2'>: <function TSQLGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.TimeStrToTime'>: <function _timestrtotime_sql>, <class 'sqlglot.expressions.temporal.TimeToStr'>: <function _format_sql>, <class 'sqlglot.expressions.temporal.TimestampAdd'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.string.Trim'>: <function trim_sql>, <class 'sqlglot.expressions.temporal.TsOrDsAdd'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.TsOrDsDiff'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.TimestampTrunc'>: <function TSQLGenerator.<lambda>>, <class 'sqlglot.expressions.math.Trunc'>: <function TSQLGenerator.<lambda>>, <class 'sqlglot.expressions.functions.Uuid'>: <function TSQLGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.Year'>: <function remove_ts_or_ds_to_date.<locals>.func>, <class 'sqlglot.expressions.temporal.DateFromParts'>: <function rename_func.<locals>.<lambda>>}
PROPERTIES_LOCATION = {<class 'sqlglot.expressions.properties.AggregateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AlgorithmProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApiProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.AutoIncrementProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BackupProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BlockCompressionProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ChecksumProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CollateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.CopyTagsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.query.Cluster'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusteredByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusterProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistributedByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DuplicateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DataBlocksizeProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DataDeletionProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DefinerProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DictRange'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DistKeyProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.DistStyleProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EncodeProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.EngineProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.FallbackProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.FreespaceProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.HeapProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.HybridProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.IgnoreGrantsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IncludeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InteractiveProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.IsolatedLoadingProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.JournalProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LikeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LocationProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockingProperty'>: <PropertiesLocation.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.properties.LogProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MergeBlockRatioProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.ModuleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.OnProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.OptionsProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.query.Order'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.PartitionedOfProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PhysicalProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.constraints.PrimaryKey'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Property'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.RefreshTriggerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RollupProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowFormatProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatDelimitedProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatSerdeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SampleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SecureProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SerdeProperties'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.Set'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SetProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SharingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.SequenceProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.TriggerProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.SortKeyProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageHandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.StrictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Tags'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.TableSpaceProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithoutRowidProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
def scope_resolution(self, rhs: str, scope_name: str) -> str:
280    def scope_resolution(self, rhs: str, scope_name: str) -> str:
281        return f"{scope_name}::{rhs}"
def select_sql(self, expression: sqlglot.expressions.query.Select) -> str:
283    def select_sql(self, expression: exp.Select) -> str:
284        self._prepare_limit_offset(expression)
285
286        # Handles transpiling a query like the following to T-SQL:
287        #   SELECT 1 AS x ORDER BY x NULLS FIRST FETCH FIRST 1 ROWS ONLY UNION ALL SELECT 2 AS x
288        if isinstance(expression.parent, exp.SetOperation) and isinstance(
289            expression.args.get("limit"), exp.Fetch
290        ):
291            return self.sql(
292                exp.select("*").from_(expression.subquery("_l_0", copy=False), copy=False)
293            )
294
295        return super().select_sql(expression)
def set_operations(self, expression: sqlglot.expressions.query.SetOperation) -> str:
297    def set_operations(self, expression: exp.SetOperation) -> str:
298        limit = expression.args.get("limit")
299        offset = expression.args.get("offset")
300        order = expression.args.get("order")
301
302        # Set operations cannot order by the CASE used to emulate null ordering,
303        # or by expressions that aren't in their select list.
304        wrap_order = False
305        if order:
306            selects = {select.unalias().unnest() for select in expression.selects}
307            for ordered in order.expressions:
308                this = ordered.this.unnest()
309                if this.is_int:
310                    continue
311
312                desc = ordered.args.get("desc")
313                nulls_first = ordered.args.get("nulls_first")
314
315                emulate_null_ordering = (desc and nulls_first) or (not desc and not nulls_first)
316                if emulate_null_ordering or (
317                    not isinstance(this, exp.Column) and this not in selects
318                ):
319                    wrap_order = True
320                    break
321
322        if (
323            wrap_order
324            or (isinstance(limit, exp.Limit) and not offset)
325            or (not order and (offset or isinstance(limit, exp.Fetch)))
326        ):
327            select = self._move_ctes_to_top_level(
328                exp.subquery(expression, "_l_0", copy=False).select("*", copy=False)
329            )
330            for arg in SET_OP_MODIFIERS:
331                value = expression.args.get(arg)
332                if value:
333                    expression.set(arg, None)
334                    select.set(arg, value)
335
336            return self.sql(select)
337
338        self._prepare_limit_offset(expression)
339        return super().set_operations(expression)
def convert_sql(self, expression: sqlglot.expressions.functions.Convert) -> str:
363    def convert_sql(self, expression: exp.Convert) -> str:
364        name = "TRY_CONVERT" if expression.args.get("safe") else "CONVERT"
365        return self.func(name, expression.this, expression.expression, expression.args.get("style"))
def queryoption_sql(self, expression: sqlglot.expressions.query.QueryOption) -> str:
367    def queryoption_sql(self, expression: exp.QueryOption) -> str:
368        option = self.sql(expression, "this")
369        value = self.sql(expression, "expression")
370        if value:
371            optional_equal_sign = "= " if option in OPTIONS_THAT_REQUIRE_EQUAL else ""
372            return f"{option} {optional_equal_sign}{value}"
373        return option
def lateral_op(self, expression: sqlglot.expressions.query.Lateral) -> str:
375    def lateral_op(self, expression: exp.Lateral) -> str:
376        cross_apply = expression.args.get("cross_apply")
377        if cross_apply is True:
378            return "CROSS APPLY"
379        if cross_apply is False:
380            return "OUTER APPLY"
381
382        # TODO: perhaps we can check if the parent is a Join and transpile it appropriately
383        self.unsupported("LATERAL clause is not supported.")
384        return "LATERAL"
def splitpart_sql(self, expression: sqlglot.expressions.string.SplitPart) -> str:
386    def splitpart_sql(self, expression: exp.SplitPart) -> str:
387        this = expression.this
388        split_count = len(this.name.split("."))
389        delimiter = expression.args.get("delimiter")
390        part_index = expression.args.get("part_index")
391
392        if (
393            not all(isinstance(arg, exp.Literal) for arg in (this, delimiter, part_index))
394            or (delimiter and delimiter.name != ".")
395            or not part_index
396            or split_count > 4
397        ):
398            self.unsupported(
399                "SPLIT_PART can be transpiled to PARSENAME only for '.' delimiter and literal values"
400            )
401            return ""
402
403        return self.func(
404            "PARSENAME", this, exp.Literal.number(split_count + 1 - part_index.to_py())
405        )
def extract_sql(self, expression: sqlglot.expressions.temporal.Extract) -> str:
407    def extract_sql(self, expression: exp.Extract) -> str:
408        part = expression.this
409        name = DATE_PART_UNMAPPING.get(part.name.upper()) or part
410
411        return self.func("DATEPART", name, expression.expression)
def timefromparts_sql(self, expression: sqlglot.expressions.temporal.TimeFromParts) -> str:
413    def timefromparts_sql(self, expression: exp.TimeFromParts) -> str:
414        nano = expression.args.get("nano")
415        if nano is not None:
416            nano.pop()
417            self.unsupported("Specifying nanoseconds is not supported in TIMEFROMPARTS.")
418
419        if expression.args.get("fractions") is None:
420            expression.set("fractions", exp.Literal.number(0))
421        if expression.args.get("precision") is None:
422            expression.set("precision", exp.Literal.number(0))
423
424        return rename_func("TIMEFROMPARTS")(self, expression)
def timestampfromparts_sql(self, expression: sqlglot.expressions.temporal.TimestampFromParts) -> str:
426    def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str:
427        zone = expression.args.get("zone")
428        if zone is not None:
429            zone.pop()
430            self.unsupported("Time zone is not supported in DATETIMEFROMPARTS.")
431
432        nano = expression.args.get("nano")
433        if nano is not None:
434            nano.pop()
435            self.unsupported("Specifying nanoseconds is not supported in DATETIMEFROMPARTS.")
436
437        if expression.args.get("milli") is None:
438            expression.set("milli", exp.Literal.number(0))
439
440        return rename_func("DATETIMEFROMPARTS")(self, expression)
def setitem_sql(self, expression: sqlglot.expressions.ddl.SetItem) -> str:
442    def setitem_sql(self, expression: exp.SetItem) -> str:
443        this = expression.this
444        if isinstance(this, exp.EQ) and not isinstance(this.left, exp.Parameter):
445            # T-SQL does not use '=' in SET command, except when the LHS is a variable.
446            return f"{self.sql(this.left)} {self.sql(this.right)}"
447
448        return super().setitem_sql(expression)
def boolean_sql(self, expression: sqlglot.expressions.core.Boolean) -> str:
450    def boolean_sql(self, expression: exp.Boolean) -> str:
451        if type(expression.parent) in BIT_TYPES or isinstance(
452            expression.find_ancestor(exp.Values, exp.Select), exp.Values
453        ):
454            return "1" if expression.this else "0"
455
456        return "(1 = 1)" if expression.this else "(1 = 0)"
def is_sql(self, expression: sqlglot.expressions.core.Is) -> str:
458    def is_sql(self, expression: exp.Is) -> str:
459        negate = expression.args.get("negate")
460        if isinstance(expression.expression, exp.Boolean):
461            return self.binary(expression, "<>" if negate else "=")
462        return self.binary(expression, "IS NOT" if negate else "IS")
def createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
464    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
465        sql = self.sql(expression, "this")
466        properties = expression.args.get("properties")
467
468        start = self._identifier_start
469        if (
470            not sql.startswith("#")
471            and not sql.startswith(f"{start}#")
472            and any(
473                isinstance(prop, exp.TemporaryProperty)
474                for prop in (properties.expressions if properties else [])
475            )
476        ):
477            sql = f"{start}#{sql[len(start) :]}" if sql.startswith(start) else f"#{sql}"
478
479        return sql
def create_sql(self, expression: sqlglot.expressions.ddl.Create) -> str:
481    def create_sql(self, expression: exp.Create) -> str:
482        kind = expression.kind
483        exists = expression.args.get("exists")
484        expression.set("exists", None)
485
486        like_property = expression.find(exp.LikeProperty)
487        if like_property:
488            ctas_expression = like_property.this
489        else:
490            ctas_expression = expression.expression
491
492        if kind == "VIEW":
493            expression.this.set("catalog", None)
494            with_ = expression.args.get("with_")
495            if ctas_expression and with_:
496                # We've already preprocessed the Create expression to bubble up any nested CTEs,
497                # but CREATE VIEW actually requires the WITH clause to come after it so we need
498                # to amend the AST by moving the CTEs to the CREATE VIEW statement's query.
499                ctas_expression.set("with_", with_.pop())
500        elif (
501            kind == "FUNCTION"
502            and isinstance(ctas_expression, exp.Return)
503            and isinstance(body := ctas_expression.this.unnest(), exp.Query)
504            and (with_ := expression.args.get("with_"))
505        ):
506            # Similar to the VIEW branch, the table-valued functions require the WITH clause
507            # to stay inside the RETURN body, so we move back any CTEs that were bubbled up.
508            body.set("with_", with_.pop())
509
510        table = expression.find(exp.Table)
511
512        # Convert CTAS statement to SELECT .. INTO ..
513        if kind == "TABLE" and ctas_expression:
514            if isinstance(ctas_expression, exp.UNWRAPPED_QUERIES):
515                ctas_expression = ctas_expression.subquery()
516
517            properties = expression.args.get("properties") or exp.Properties()
518            is_temp = any(isinstance(p, exp.TemporaryProperty) for p in properties.expressions)
519
520            select_into = exp.select("*").from_(exp.alias_(ctas_expression, "temp", table=True))
521            select_into.set("into", exp.Into(this=table, temporary=is_temp))
522
523            if like_property:
524                select_into.limit(0, copy=False)
525
526            sql = self.sql(select_into)
527        else:
528            sql = super().create_sql(expression)
529
530        if exists:
531            identifier = self.sql(exp.Literal.string(exp.table_name(table) if table else ""))
532            sql_with_ctes = self.prepend_ctes(expression, sql)
533            sql_literal = self.sql(exp.Literal.string(sql_with_ctes))
534            if kind == "SCHEMA":
535                return f"""IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = {identifier}) EXEC({sql_literal})"""
536            elif kind == "TABLE":
537                assert table
538                where = exp.and_(
539                    exp.column("TABLE_NAME").eq(table.name),
540                    exp.column("TABLE_SCHEMA").eq(table.db) if table.db else None,
541                    exp.column("TABLE_CATALOG").eq(table.catalog) if table.catalog else None,
542                )
543                return f"""IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE {where}) EXEC({sql_literal})"""
544            elif kind == "INDEX":
545                index = self.sql(exp.Literal.string(expression.this.text("this")))
546                return f"""IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = object_id({identifier}) AND name = {index}) EXEC({sql_literal})"""
547        elif expression.args.get("replace"):
548            sql = sql.replace("CREATE OR REPLACE ", "CREATE OR ALTER ", 1)
549
550        return self.prepend_ctes(expression, sql)
@generator.unsupported_args('unlogged', 'expressions')
def into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
552    @generator.unsupported_args("unlogged", "expressions")
553    def into_sql(self, expression: exp.Into) -> str:
554        if expression.args.get("temporary"):
555            # If the Into expression has a temporary property, push this down to the Identifier
556            table = expression.find(exp.Table)
557            if table and isinstance(table.this, exp.Identifier):
558                table.this.set("temporary", True)
559
560        return f"{self.seg('INTO')} {self.sql(expression, 'this')}"
def count_sql(self, expression: sqlglot.expressions.aggregate.Count) -> str:
562    def count_sql(self, expression: exp.Count) -> str:
563        func_name = "COUNT_BIG" if expression.args.get("big_int") else "COUNT"
564        return rename_func(func_name)(self, expression)
def countif_sql(self, expression: sqlglot.expressions.aggregate.CountIf) -> str:
566    def countif_sql(self, expression: exp.CountIf) -> str:
567        # T-SQL has no COUNT_IF, so count the rows where the predicate holds instead
568        return self.func(
569            "COUNT", exp.If(this=expression.this, true=exp.Literal.number(1), false=exp.null())
570        )
def datediff_sql(self, expression: sqlglot.expressions.temporal.DateDiff) -> str:
572    def datediff_sql(self, expression: exp.DateDiff) -> str:
573        func_name = "DATEDIFF_BIG" if expression.args.get("big_int") else "DATEDIFF"
574        return date_delta_sql(func_name)(self, expression)
def offset_sql(self, expression: sqlglot.expressions.query.Offset) -> str:
576    def offset_sql(self, expression: exp.Offset) -> str:
577        return f"{super().offset_sql(expression)} ROWS"
def version_sql(self, expression: sqlglot.expressions.query.Version) -> str:
579    def version_sql(self, expression: exp.Version) -> str:
580        name = "SYSTEM_TIME" if expression.name == "TIMESTAMP" else expression.name
581        this = f"FOR {name}"
582        expr = expression.expression
583        kind = expression.text("kind")
584        if kind in ("FROM", "BETWEEN"):
585            args = expr.expressions
586            sep = "TO" if kind == "FROM" else "AND"
587            expr_sql = f"{self.sql(seq_get(args, 0))} {sep} {self.sql(seq_get(args, 1))}"
588        else:
589            expr_sql = self.sql(expr)
590
591        expr_sql = f" {expr_sql}" if expr_sql else ""
592        return f"{this} {kind}{expr_sql}"
def returnsproperty_sql(self, expression: sqlglot.expressions.properties.ReturnsProperty) -> str:
594    def returnsproperty_sql(self, expression: exp.ReturnsProperty) -> str:
595        table = expression.args.get("table")
596        table = f"{table} " if table else ""
597        return f"RETURNS {table}{self.sql(expression, 'this')}"
def returning_sql(self, expression: sqlglot.expressions.dml.Returning) -> str:
599    def returning_sql(self, expression: exp.Returning) -> str:
600        into = self.sql(expression, "into")
601        into = self.seg(f"INTO {into}") if into else ""
602        return f"{self.seg('OUTPUT')} {self.expressions(expression, flat=True)}{into}"
def transaction_sql(self, expression: sqlglot.expressions.ddl.Transaction) -> str:
604    def transaction_sql(self, expression: exp.Transaction) -> str:
605        this = self.sql(expression, "this")
606        this = f" {this}" if this else ""
607        mark = self.sql(expression, "mark")
608        mark = f" WITH MARK {mark}" if mark else ""
609        return f"BEGIN TRANSACTION{this}{mark}"
def commit_sql(self, expression: sqlglot.expressions.ddl.Commit) -> str:
611    def commit_sql(self, expression: exp.Commit) -> str:
612        this = self.sql(expression, "this")
613        this = f" {this}" if this else ""
614        durability = expression.args.get("durability")
615        durability = (
616            f" WITH (DELAYED_DURABILITY = {'ON' if durability else 'OFF'})"
617            if durability is not None
618            else ""
619        )
620        return f"COMMIT TRANSACTION{this}{durability}"
def rollback_sql(self, expression: sqlglot.expressions.ddl.Rollback) -> str:
622    def rollback_sql(self, expression: exp.Rollback) -> str:
623        this = self.sql(expression, "this")
624        this = f" {this}" if this else ""
625        return f"ROLLBACK TRANSACTION{this}"
def identifier_sql(self, expression: sqlglot.expressions.core.Identifier) -> str:
627    def identifier_sql(self, expression: exp.Identifier) -> str:
628        identifier = super().identifier_sql(expression)
629
630        if expression.args.get("global_"):
631            prefix = "##"
632        elif expression.args.get("temporary"):
633            prefix = "#"
634        else:
635            return identifier
636
637        start = self._identifier_start
638        if expression.quoted and identifier.startswith(start):
639            return f"{start}{prefix}{identifier[len(start) :]}"
640
641        return f"{prefix}{identifier}"
def constraint_sql(self, expression: sqlglot.expressions.constraints.Constraint) -> str:
643    def constraint_sql(self, expression: exp.Constraint) -> str:
644        this = self.sql(expression, "this")
645        expressions = self.expressions(expression, flat=True, sep=" ")
646        return f"CONSTRAINT {this} {expressions}"
def length_sql(self, expression: sqlglot.expressions.string.Length) -> str:
648    def length_sql(self, expression: exp.Length) -> str:
649        return self._uncast_text(expression, "LEN")
def right_sql(self, expression: sqlglot.expressions.string.Right) -> str:
651    def right_sql(self, expression: exp.Right) -> str:
652        return self._uncast_text(expression, "RIGHT")
def left_sql(self, expression: sqlglot.expressions.string.Left) -> str:
654    def left_sql(self, expression: exp.Left) -> str:
655        return self._uncast_text(expression, "LEFT")
def partition_sql(self, expression: sqlglot.expressions.query.Partition) -> str:
666    def partition_sql(self, expression: exp.Partition) -> str:
667        return f"WITH (PARTITIONS({self.expressions(expression, flat=True)}))"
def alter_sql(self, expression: sqlglot.expressions.ddl.Alter) -> str:
669    def alter_sql(self, expression: exp.Alter) -> str:
670        actions = expression.args.get("actions") or []
671        if len(actions) == 1 and isinstance(actions[0], exp.AlterRename):
672            old_name = self.sql(exp.Literal.string(self.sql(expression.this)))
673            new_name = self.sql(exp.Literal.string(actions[0].this.name))
674            return f"EXEC sp_rename {old_name}, {new_name}"
675        return super().alter_sql(expression)
def drop_sql(self, expression: sqlglot.expressions.ddl.Drop) -> str:
677    def drop_sql(self, expression: exp.Drop) -> str:
678        if expression.args["kind"] == "VIEW":
679            for table in expression.args.get("tables") or []:
680                table.set("catalog", None)
681        return super().drop_sql(expression)
def options_modifier(self, expression: sqlglot.expressions.core.Expr) -> str:
683    def options_modifier(self, expression: exp.Expr) -> str:
684        options = self.expressions(expression, key="options")
685        return f" OPTION{self.wrap(options)}" if options else ""
def dpipe_sql(self, expression: sqlglot.expressions.core.DPipe) -> str:
687    def dpipe_sql(self, expression: exp.DPipe) -> str:
688        return self.sql(reduce(lambda x, y: exp.Add(this=x, expression=y), expression.flatten()))
def isascii_sql(self, expression: sqlglot.expressions.string.IsAscii) -> str:
690    def isascii_sql(self, expression: exp.IsAscii) -> str:
691        return f"(PATINDEX(CONVERT(VARCHAR(MAX), 0x255b5e002d7f5d25) COLLATE Latin1_General_BIN, {self.sql(expression.this)}) = 0)"
def columndef_sql( self, expression: sqlglot.expressions.query.ColumnDef, sep: str = ' ') -> str:
693    def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
694        this = super().columndef_sql(expression, sep)
695        default = self.sql(expression, "default")
696        default = f" = {default}" if default else ""
697        output = self.sql(expression, "output")
698        output = f" {output}" if output else ""
699        return f"{this}{default}{output}"
def coalesce_sql(self, expression: sqlglot.expressions.functions.Coalesce) -> str:
701    def coalesce_sql(self, expression: exp.Coalesce) -> str:
702        func_name = "ISNULL" if expression.args.get("is_null") else "COALESCE"
703        return rename_func(func_name)(self, expression)
def storedprocedure_sql(self, expression: sqlglot.expressions.query.StoredProcedure) -> str:
705    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:
706        this = self.sql(expression, "this")
707        expressions = self.expressions(expression)
708        expressions = (
709            self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
710        )
711        return f"{this}{expressions}" if expressions.strip() != "" else this
def ifblock_sql(self, expression: sqlglot.expressions.query.IfBlock) -> str:
713    def ifblock_sql(self, expression: exp.IfBlock) -> str:
714        this = self.sql(expression, "this")
715        true = self.sql(expression, "true")
716        true = f" {true}" if true else " "
717        false = self.sql(expression, "false")
718        false = f"; ELSE BEGIN {false}" if false else ""
719        return f"IF {this} BEGIN{true}{false}"
def whileblock_sql(self, expression: sqlglot.expressions.query.WhileBlock) -> str:
721    def whileblock_sql(self, expression: exp.WhileBlock) -> str:
722        this = self.sql(expression, "this")
723        body = self.sql(expression, "body")
724        body = f" {body}" if body else " "
725        return f"WHILE {this} BEGIN{body}"
def execute_sql(self, expression: sqlglot.expressions.ddl.Execute) -> str:
727    def execute_sql(self, expression: exp.Execute) -> str:
728        this = self.sql(expression, "this")
729        expressions = self.expressions(expression)
730        expressions = f" {expressions}" if expressions else ""
731        return_status = self.sql(expression, "return_status")
732        return_status = f"{return_status} = " if return_status else ""
733        return f"EXECUTE {return_status}{this}{expressions}"
def executesql_sql(self, expression: sqlglot.expressions.ddl.ExecuteSql) -> str:
735    def executesql_sql(self, expression: exp.ExecuteSql) -> str:
736        return self.execute_sql(expression)
Inherited Members
sqlglot.generator.Generator
Generator
WINDOW_FUNCS_WITH_NULL_ORDERING
IGNORE_NULLS_IN_FUNC
IGNORE_NULLS_BEFORE_ORDER
LOCKING_READS_SUPPORTED
WRAP_DERIVED_VALUES
CREATE_FUNCTION_RETURN_AS
MATCHED_BY_SOURCE
SUPPORTS_MERGE_WHERE
MERGE_ALL_BY_NAME
SINGLE_STRING_INTERVAL
INTERVAL_ALLOWS_PLURAL_FORM
AUTO_REFRESH_BARE_INTERVALS
LIMIT_ONLY_LITERALS
RENAME_TABLE_WITH_DB
GROUPINGS_SEP
SUPPORTS_GROUPING_SETS_AS_SUFFIX
INDEX_ON
INOUT_SEPARATOR
JOIN_HINTS
DIRECTED_JOINS
SUPPORTS_NEAREST_JOIN
TABLE_HINTS
QUERY_HINT_SEP
IS_BOOL_ALLOWED
CAST_TO_INT_ROUNDS
SAFE_INT_DIVISION
DUPLICATE_KEY_UPDATE_WITH_SET
EXTRACT_ALLOWS_QUOTES
TZ_TO_WITH_TIME_ZONE
VALUES_AS_TABLE
UNNEST_WITH_ORDINALITY
SEMI_ANTI_JOIN_WITH_SIDE
SUPPORTS_TABLE_COPY
TABLESAMPLE_REQUIRES_PARENS
TABLESAMPLE_SIZE_IS_ROWS
TABLESAMPLE_KEYWORDS
TABLESAMPLE_WITH_METHOD
HISTORICAL_DATA_POST_ALIAS
COLLATE_IS_FUNC
DATA_TYPE_SPECIFIERS_ALLOWED
LAST_DAY_SUPPORTS_DATE_PART
SUPPORTS_TABLE_ALIAS_COLUMNS
SUPPORTS_NAMED_CTE_COLUMNS
UNPIVOT_ALIASES_ARE_IDENTIFIERS
PIVOT_ALIAS_WITH_AS
JSON_KEY_VALUE_PAIR_SEP
INSERT_OVERWRITE
SUPPORTS_UNLOGGED_TABLES
SUPPORTS_CREATE_TABLE_LIKE
SUPPORTS_MODIFY_COLUMN
SUPPORTS_CHANGE_COLUMN
SUPPORTS_ALTER_COLUMN_IF_EXISTS
LIKE_PROPERTY_INSIDE_SCHEMA
MULTI_ARG_DISTINCT
JSON_TYPE_REQUIRED_FOR_EXTRACTION
JSON_PATH_SINGLE_QUOTE_ESCAPE
JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
CAN_IMPLEMENT_ARRAY_ANY
SUPPORTS_WINDOW_EXCLUDE
SET_OP_MODIFIERS
SET_OP_PARENTHESIZED_OPERANDS
COPY_PARAMS_ARE_WRAPPED
COPY_HAS_INTO_KEYWORD
UNICODE_SUBSTITUTE
STAR_EXCEPT
HEX_FUNC
BYTE_STRING_FUNCTION
WITH_PROPERTIES_PREFIX
QUOTE_JSON_PATH
PAD_FILL_PATTERN_IS_REQUIRED
SUPPORTS_EXPLODING_PROJECTIONS
ARRAY_CONCAT_IS_VAR_LEN
SUPPORTS_CONVERT_TIMEZONE
SUPPORTS_MEDIAN
SUPPORTS_UNIX_SECONDS
NORMALIZE_EXTRACT_DATE_PARTS
ARRAY_SIZE_NAME
ARRAY_SIZE_DIM_REQUIRED
SUPPORTS_BETWEEN_FLAGS
SUPPORTS_LIKE_QUANTIFIERS
MATCH_AGAINST_TABLE_PREFIX
SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD
DECLARE_DEFAULT_ASSIGNMENT
UPDATE_STATEMENT_SUPPORTS_FROM
STAR_EXCLUDE_REQUIRES_DERIVED_TABLE
SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
UNSUPPORTED_TYPES
TYPE_PARAM_SETTINGS
TIME_PART_SINGULARS
TOKEN_MAPPING
STRUCT_DELIMITER
PARAMETER_TOKEN
NAMED_PLACEHOLDER_TOKEN
EXPRESSION_PRECEDES_PROPERTIES_CREATABLES
RESERVED_KEYWORDS
WITH_SEPARATED_COMMENTS
EXCLUDE_COMMENTS
UNWRAPPED_INTERVAL_VALUES
PARAMETERIZABLE_TEXT_TYPES
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS
MOD_OPERATOR
MOD_PAREN_PARENT_TYPES
ALTER_PARTITION_OPTIONS
SAFE_JSON_PATH_KEY_RE
SENTINEL_LINE_BREAK
pretty
identify
normalize
pad
unsupported_level
max_unsupported
leading_comma
max_text_width
comments
dialect
normalize_functions
unsupported_messages
generate
preprocess
unsupported
sep
seg
sanitize_comment
maybe_comment
wrap
no_identify
normalize_func
indent
sql
uncache_sql
cache_sql
characterset_sql
column_parts
column_sql
pseudocolumn_sql
columnposition_sql
columnconstraint_sql
computedcolumnconstraint_sql
autoincrementcolumnconstraint_sql
compresscolumnconstraint_sql
generatedasidentitycolumnconstraint_sql
generatedasrowcolumnconstraint_sql
periodforsystemtimeconstraint_sql
notnullcolumnconstraint_sql
primarykeycolumnconstraint_sql
uniquecolumnconstraint_sql
inoutcolumnconstraint_sql
sequenceproperties_sql
triggerproperties_sql
triggerreferencing_sql
triggerevent_sql
clone_sql
describe_sql
heredoc_sql
prepend_ctes
with_sql
cte_sql
tablealias_sql
bitstring_sql
hexstring_sql
bytestring_sql
unicodestring_sql
rawstring_sql
datatypeparam_sql
datatype_param_bound_limiter
datatype_sql
directory_sql
delete_sql
set_operation
fetch_sql
limitoptions_sql
filter_sql
per_sql
hint_sql
indexparameters_sql
index_sql
dynamicidentifier_sql
hex_sql
lowerhex_sql
inputoutputformat_sql
national_sql
properties_sql
root_properties
properties
with_properties
locate_properties
property_name
property_sql
uuidproperty_sql
likeproperty_sql
fallbackproperty_sql
journalproperty_sql
freespaceproperty_sql
checksumproperty_sql
mergeblockratioproperty_sql
moduleproperty_sql
datablocksizeproperty_sql
blockcompressionproperty_sql
isolatedloadingproperty_sql
partitionboundspec_sql
partitionedofproperty_sql
lockingproperty_sql
lockingstatement_sql
withdataproperty_sql
withsystemversioningproperty_sql
insert_sql
introducer_sql
kill_sql
pseudotype_sql
objectidentifier_sql
onconflict_sql
rowformatdelimitedproperty_sql
withtablehint_sql
indextablehint_sql
historicaldata_sql
table_parts
table_sql
tablefromrows_sql
tablesample_sql
pivot_sql
tuple_sql
update_sql
values_sql
var_sql
from_sql
groupingsets_sql
rollup_sql
rollupindex_sql
rollupproperty_sql
cube_sql
group_sql
having_sql
connectto_sql
disconnect_sql
connect_sql
prior_sql
join_sql
nearest_sql
lambda_sql
lateral_sql
limit_sql
set_sql
queryband_sql
pragma_sql
lock_sql
literal_sql
escape_str
loaddata_sql
null_sql
booland_sql
boolor_sql
order_sql
withfill_sql
cluster_sql
clusterproperty_sql
distribute_sql
sort_sql
ordered_sql
matchrecognizemeasure_sql
matchrecognize_sql
query_modifiers
forclause_sql
offset_limit_modifiers
after_limit_modifiers
schema_sql
schema_columns_sql
star_sql
parameter_sql
sessionparameter_sql
placeholder_sql
subquery_sql
qualify_sql
unnest_sql
prewhere_sql
where_sql
window_sql
partition_by_sql
partitionbyexcluding_sql
windowspec_sql
withingroup_sql
between_sql
bracket_offset_expressions
bracket_sql
all_sql
any_sql
exists_sql
case_sql
nextvaluefor_sql
trim_sql
convert_concat_args
concat_sql
concatws_sql
check_sql
foreignkey_sql
primarykey_sql
timeserieskey_sql
if_sql
matchagainst_sql
jsonkeyvalue_sql
jsonpath_sql
json_path_part
formatjson_sql
formatphrase_sql
teradatatitle_sql
jsonarray_sql
jsonarrayagg_sql
jsoncolumndef_sql
jsonschema_sql
jsontable_sql
openjsoncolumndef_sql
openjson_sql
in_sql
in_unnest_op
interval_sql
return_sql
reference_sql
anonymous_sql
paren_sql
neg_sql
not_sql
alias_sql
pivotalias_sql
aliases_sql
atindex_sql
attimezone_sql
fromtimezone_sql
fromiso8601date_sql
fromiso8601timestamp_sql
fromiso8601timestampnanos_sql
add_sql
and_sql
or_sql
xor_sql
connector_sql
bitwiseand_sql
bitwiseleftshift_sql
bitwisenot_sql
bitwiseor_sql
bitwiserightshift_sql
bitwisexor_sql
cast_sql
strtotime_sql
strtodate_sql
parsedatetime_sql
currentdate_sql
collate_sql
command_sql
comment_sql
mergetreettlaction_sql
mergetreettl_sql
altercolumn_sql
modifycolumn_sql
alterindex_sql
alterdiststyle_sql
altersortkey_sql
alterrename_sql
renamecolumn_sql
alterset_sql
altersession_sql
add_column_sql
droppartition_sql
dropprimarykey_sql
addconstraint_sql
replacecolumns_sql
addpartition_sql
distinct_sql
ignorenulls_sql
respectnulls_sql
havingmax_sql
intdiv_sql
nullif_zero_divisor
div_sql
safedivide_sql
overlaps_sql
distance_sql
distancend_sql
dot_sql
eq_sql
propertyeq_sql
escape_sql
glob_sql
gt_sql
gte_sql
like_sql
ilike_sql
match_sql
similarto_sql
lt_sql
lte_sql
mod_sql
mul_sql
neq_sql
nullsafeeq_sql
nullsafeneq_sql
sub_sql
trycast_sql
jsoncast_sql
try_sql
log_sql
use_sql
binary
ceil_floor
function_fallback_sql
function_name
func
format_args
too_wide
format_time
expressions
op_expressions
naked_property
tag_sql
token_sql
userdefinedfunction_sql
macrooverloads_sql
macrooverload_sql
joinhint_sql
kwarg_sql
when_sql
whens_sql
merge_sql
tochar_sql
tonumber_sql
dictproperty_sql
dictrange_sql
dictsubproperty_sql
aggregatekeyproperty_sql
duplicatekeyproperty_sql
uniquekeyproperty_sql
distributedbyproperty_sql
oncluster_sql
clusteredbyproperty_sql
anyvalue_sql
querytransform_sql
indexconstraintoption_sql
checkcolumnconstraint_sql
indexcolumnconstraint_sql
nvl2_sql
nthvalue_sql
comprehension_sql
columnprefix_sql
opclass_sql
predict_sql
generateembedding_sql
generatetext_sql
generatetable_sql
generatebool_sql
generateint_sql
generatedouble_sql
mltranslate_sql
mlforecast_sql
aiforecast_sql
featuresattime_sql
vectorsearch_sql
forin_sql
refresh_sql
toarray_sql
tsordstotime_sql
tsordstotimestamp_sql
tsordstodatetime_sql
tsordstodate_sql
unixdate_sql
lastday_sql
dateadd_sql
arrayinsert_sql
arrayany_sql
struct_sql
partitionrange_sql
truncatetable_sql
copyparameter_sql
credentials_sql
copy_sql
semicolon_sql
datadeletionproperty_sql
maskingpolicycolumnconstraint_sql
gapfill_sql
scoperesolution_sql
parsejson_sql
rand_sql
changes_sql
pad_sql
summarize_sql
explodinggenerateseries_sql
converttimezone_sql
json_sql
jsonvalue_sql
skipjsoncolumn_sql
conditionalinsert_sql
multitableinserts_sql
oncondition_sql
jsonextractquote_sql
jsonexists_sql
min_sql
max_sql
arrayagg_sql
timeslice_sql
slice_sql
apply_sql
grant_sql
revoke_sql
grantprivilege_sql
grantprincipal_sql
columns_sql
overlay_sql
todouble_sql
string_sql
median_sql
overflowtruncatebehavior_sql
unixseconds_sql
arraysort_sql
arraysize_sql
attach_sql
detach_sql
attachoption_sql
watermarkcolumnconstraint_sql
encodeproperty_sql
includeproperty_sql
xmlelement_sql
xmlkeyvalueoption_sql
partitionbyrangeproperty_sql
partitionbyrangepropertydynamic_sql
unpivotcolumns_sql
analyzesample_sql
analyzestatistics_sql
analyzehistogram_sql
analyzedelete_sql
analyzelistchainedrows_sql
analyzevalidate_sql
analyze_sql
xmltable_sql
xmlnamespace_sql
export_sql
declare_sql
declareitem_sql
recursivewithsearch_sql
parameterizedagg_sql
anonymousaggfunc_sql
combinedaggfunc_sql
combinedparameterizedagg_sql
show_sql
install_sql
liststage_sql
get_put_sql
translatecharacters_sql
decodecase_sql
semanticview_sql
getextract_sql
datefromunixdate_sql
space_sql
buildproperty_sql
refreshtriggerproperty_sql
modelattribute_sql
directorystage_sql
uuid_sql
initcap_sql
localtime_sql
localtimestamp_sql
weekstart_name
weekstart_sql
chr_sql
weightstring_sql
block_sql
functionspecification_sql
casestatement_sql
loopblock_sql
repeatblock_sql
leave_sql
iterate_sql
altermodifysqlsecurity_sql
usingproperty_sql
renameindex_sql