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'}
BIT_TYPES =
{<class 'sqlglot.expressions.core.In'>, <class 'sqlglot.expressions.core.NEQ'>, <class 'sqlglot.expressions.core.Is'>, <class 'sqlglot.expressions.query.Select'>, <class 'sqlglot.expressions.core.EQ'>, <class 'sqlglot.expressions.core.Alias'>}
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.
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
WHEREclause. 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
EXPRESSIONS_WITHOUT_NESTED_CTES =
{<class 'sqlglot.expressions.query.Subquery'>, <class 'sqlglot.expressions.query.Intersect'>, <class 'sqlglot.expressions.query.Except'>, <class 'sqlglot.expressions.query.Union'>, <class 'sqlglot.expressions.ddl.Create'>, <class 'sqlglot.expressions.dml.Merge'>, <class 'sqlglot.expressions.dml.Delete'>, <class 'sqlglot.expressions.query.Select'>, <class 'sqlglot.expressions.dml.Update'>, <class 'sqlglot.expressions.dml.Insert'>}
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.query.JSONPathKey'>, <class 'sqlglot.expressions.query.JSONPathSubscript'>, <class 'sqlglot.expressions.query.JSONPathRoot'>}
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'>}
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)
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)
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
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"
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 )
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)
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
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
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')}"
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}"
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}"
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}"
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)
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}"
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
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}"
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}"
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