sqlglot.dialects.snowflake
1from __future__ import annotations 2 3import typing as t 4 5from sqlglot import exp, generator, parser, tokens, transforms 6from sqlglot._typing import E 7from sqlglot.dialects.dialect import ( 8 Dialect, 9 NormalizationStrategy, 10 binary_from_function, 11 date_delta_sql, 12 date_trunc_to_time, 13 datestrtodate_sql, 14 format_time_lambda, 15 if_sql, 16 inline_array_sql, 17 max_or_greatest, 18 min_or_least, 19 rename_func, 20 timestamptrunc_sql, 21 timestrtotime_sql, 22 var_map_sql, 23) 24from sqlglot.expressions import Literal 25from sqlglot.helper import seq_get 26from sqlglot.tokens import TokenType 27 28 29def _check_int(s: str) -> bool: 30 if s[0] in ("-", "+"): 31 return s[1:].isdigit() 32 return s.isdigit() 33 34 35# from https://docs.snowflake.com/en/sql-reference/functions/to_timestamp.html 36def _parse_to_timestamp(args: t.List) -> t.Union[exp.StrToTime, exp.UnixToTime, exp.TimeStrToTime]: 37 if len(args) == 2: 38 first_arg, second_arg = args 39 if second_arg.is_string: 40 # case: <string_expr> [ , <format> ] 41 return format_time_lambda(exp.StrToTime, "snowflake")(args) 42 return exp.UnixToTime(this=first_arg, scale=second_arg) 43 44 from sqlglot.optimizer.simplify import simplify_literals 45 46 # The first argument might be an expression like 40 * 365 * 86400, so we try to 47 # reduce it using `simplify_literals` first and then check if it's a Literal. 48 first_arg = seq_get(args, 0) 49 if not isinstance(simplify_literals(first_arg, root=True), Literal): 50 # case: <variant_expr> or other expressions such as columns 51 return exp.TimeStrToTime.from_arg_list(args) 52 53 if first_arg.is_string: 54 if _check_int(first_arg.this): 55 # case: <integer> 56 return exp.UnixToTime.from_arg_list(args) 57 58 # case: <date_expr> 59 return format_time_lambda(exp.StrToTime, "snowflake", default=True)(args) 60 61 # case: <numeric_expr> 62 return exp.UnixToTime.from_arg_list(args) 63 64 65def _parse_object_construct(args: t.List) -> t.Union[exp.StarMap, exp.Struct]: 66 expression = parser.parse_var_map(args) 67 68 if isinstance(expression, exp.StarMap): 69 return expression 70 71 return exp.Struct( 72 expressions=[ 73 t.cast(exp.Condition, k).eq(v) for k, v in zip(expression.keys, expression.values) 74 ] 75 ) 76 77 78def _parse_datediff(args: t.List) -> exp.DateDiff: 79 return exp.DateDiff( 80 this=seq_get(args, 2), expression=seq_get(args, 1), unit=_map_date_part(seq_get(args, 0)) 81 ) 82 83 84# https://docs.snowflake.com/en/sql-reference/functions/date_part.html 85# https://docs.snowflake.com/en/sql-reference/functions-date-time.html#label-supported-date-time-parts 86def _parse_date_part(self: Snowflake.Parser) -> t.Optional[exp.Expression]: 87 this = self._parse_var() or self._parse_type() 88 89 if not this: 90 return None 91 92 self._match(TokenType.COMMA) 93 expression = self._parse_bitwise() 94 this = _map_date_part(this) 95 name = this.name.upper() 96 97 if name.startswith("EPOCH"): 98 if name == "EPOCH_MILLISECOND": 99 scale = 10**3 100 elif name == "EPOCH_MICROSECOND": 101 scale = 10**6 102 elif name == "EPOCH_NANOSECOND": 103 scale = 10**9 104 else: 105 scale = None 106 107 ts = self.expression(exp.Cast, this=expression, to=exp.DataType.build("TIMESTAMP")) 108 to_unix: exp.Expression = self.expression(exp.TimeToUnix, this=ts) 109 110 if scale: 111 to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale)) 112 113 return to_unix 114 115 return self.expression(exp.Extract, this=this, expression=expression) 116 117 118# https://docs.snowflake.com/en/sql-reference/functions/div0 119def _div0_to_if(args: t.List) -> exp.If: 120 cond = exp.EQ(this=seq_get(args, 1), expression=exp.Literal.number(0)) 121 true = exp.Literal.number(0) 122 false = exp.Div(this=seq_get(args, 0), expression=seq_get(args, 1)) 123 return exp.If(this=cond, true=true, false=false) 124 125 126# https://docs.snowflake.com/en/sql-reference/functions/zeroifnull 127def _zeroifnull_to_if(args: t.List) -> exp.If: 128 cond = exp.Is(this=seq_get(args, 0), expression=exp.Null()) 129 return exp.If(this=cond, true=exp.Literal.number(0), false=seq_get(args, 0)) 130 131 132# https://docs.snowflake.com/en/sql-reference/functions/zeroifnull 133def _nullifzero_to_if(args: t.List) -> exp.If: 134 cond = exp.EQ(this=seq_get(args, 0), expression=exp.Literal.number(0)) 135 return exp.If(this=cond, true=exp.Null(), false=seq_get(args, 0)) 136 137 138def _datatype_sql(self: Snowflake.Generator, expression: exp.DataType) -> str: 139 if expression.is_type("array"): 140 return "ARRAY" 141 elif expression.is_type("map"): 142 return "OBJECT" 143 return self.datatype_sql(expression) 144 145 146def _regexpilike_sql(self: Snowflake.Generator, expression: exp.RegexpILike) -> str: 147 flag = expression.text("flag") 148 149 if "i" not in flag: 150 flag += "i" 151 152 return self.func( 153 "REGEXP_LIKE", expression.this, expression.expression, exp.Literal.string(flag) 154 ) 155 156 157def _parse_convert_timezone(args: t.List) -> t.Union[exp.Anonymous, exp.AtTimeZone]: 158 if len(args) == 3: 159 return exp.Anonymous(this="CONVERT_TIMEZONE", expressions=args) 160 return exp.AtTimeZone(this=seq_get(args, 1), zone=seq_get(args, 0)) 161 162 163def _parse_regexp_replace(args: t.List) -> exp.RegexpReplace: 164 regexp_replace = exp.RegexpReplace.from_arg_list(args) 165 166 if not regexp_replace.args.get("replacement"): 167 regexp_replace.set("replacement", exp.Literal.string("")) 168 169 return regexp_replace 170 171 172def _show_parser(*args: t.Any, **kwargs: t.Any) -> t.Callable[[Snowflake.Parser], exp.Show]: 173 def _parse(self: Snowflake.Parser) -> exp.Show: 174 return self._parse_show_snowflake(*args, **kwargs) 175 176 return _parse 177 178 179DATE_PART_MAPPING = { 180 "Y": "YEAR", 181 "YY": "YEAR", 182 "YYY": "YEAR", 183 "YYYY": "YEAR", 184 "YR": "YEAR", 185 "YEARS": "YEAR", 186 "YRS": "YEAR", 187 "MM": "MONTH", 188 "MON": "MONTH", 189 "MONS": "MONTH", 190 "MONTHS": "MONTH", 191 "D": "DAY", 192 "DD": "DAY", 193 "DAYS": "DAY", 194 "DAYOFMONTH": "DAY", 195 "WEEKDAY": "DAYOFWEEK", 196 "DOW": "DAYOFWEEK", 197 "DW": "DAYOFWEEK", 198 "WEEKDAY_ISO": "DAYOFWEEKISO", 199 "DOW_ISO": "DAYOFWEEKISO", 200 "DW_ISO": "DAYOFWEEKISO", 201 "YEARDAY": "DAYOFYEAR", 202 "DOY": "DAYOFYEAR", 203 "DY": "DAYOFYEAR", 204 "W": "WEEK", 205 "WK": "WEEK", 206 "WEEKOFYEAR": "WEEK", 207 "WOY": "WEEK", 208 "WY": "WEEK", 209 "WEEK_ISO": "WEEKISO", 210 "WEEKOFYEARISO": "WEEKISO", 211 "WEEKOFYEAR_ISO": "WEEKISO", 212 "Q": "QUARTER", 213 "QTR": "QUARTER", 214 "QTRS": "QUARTER", 215 "QUARTERS": "QUARTER", 216 "H": "HOUR", 217 "HH": "HOUR", 218 "HR": "HOUR", 219 "HOURS": "HOUR", 220 "HRS": "HOUR", 221 "M": "MINUTE", 222 "MI": "MINUTE", 223 "MIN": "MINUTE", 224 "MINUTES": "MINUTE", 225 "MINS": "MINUTE", 226 "S": "SECOND", 227 "SEC": "SECOND", 228 "SECONDS": "SECOND", 229 "SECS": "SECOND", 230 "MS": "MILLISECOND", 231 "MSEC": "MILLISECOND", 232 "MILLISECONDS": "MILLISECOND", 233 "US": "MICROSECOND", 234 "USEC": "MICROSECOND", 235 "MICROSECONDS": "MICROSECOND", 236 "NS": "NANOSECOND", 237 "NSEC": "NANOSECOND", 238 "NANOSEC": "NANOSECOND", 239 "NSECOND": "NANOSECOND", 240 "NSECONDS": "NANOSECOND", 241 "NANOSECS": "NANOSECOND", 242 "NSECONDS": "NANOSECOND", 243 "EPOCH": "EPOCH_SECOND", 244 "EPOCH_SECONDS": "EPOCH_SECOND", 245 "EPOCH_MILLISECONDS": "EPOCH_MILLISECOND", 246 "EPOCH_MICROSECONDS": "EPOCH_MICROSECOND", 247 "EPOCH_NANOSECONDS": "EPOCH_NANOSECOND", 248 "TZH": "TIMEZONE_HOUR", 249 "TZM": "TIMEZONE_MINUTE", 250} 251 252 253@t.overload 254def _map_date_part(part: exp.Expression) -> exp.Var: 255 pass 256 257 258@t.overload 259def _map_date_part(part: t.Optional[exp.Expression]) -> t.Optional[exp.Expression]: 260 pass 261 262 263def _map_date_part(part): 264 mapped = DATE_PART_MAPPING.get(part.name.upper()) if part else None 265 return exp.var(mapped) if mapped else part 266 267 268def _date_trunc_to_time(args: t.List) -> exp.DateTrunc | exp.TimestampTrunc: 269 trunc = date_trunc_to_time(args) 270 trunc.set("unit", _map_date_part(trunc.args["unit"])) 271 return trunc 272 273 274def _parse_colon_get_path( 275 self: parser.Parser, this: t.Optional[exp.Expression] 276) -> t.Optional[exp.Expression]: 277 while True: 278 path = self._parse_bitwise() 279 280 # The cast :: operator has a lower precedence than the extraction operator :, so 281 # we rearrange the AST appropriately to avoid casting the 2nd argument of GET_PATH 282 if isinstance(path, exp.Cast): 283 target_type = path.to 284 path = path.this 285 else: 286 target_type = None 287 288 if isinstance(path, exp.Expression): 289 path = exp.Literal.string(path.sql(dialect="snowflake")) 290 291 # The extraction operator : is left-associative 292 this = self.expression(exp.GetPath, this=this, expression=path) 293 294 if target_type: 295 this = exp.cast(this, target_type) 296 297 if not self._match(TokenType.COLON): 298 break 299 300 if self._match_set(self.RANGE_PARSERS): 301 this = self.RANGE_PARSERS[self._prev.token_type](self, this) or this 302 303 return this 304 305 306def _parse_timestamp_from_parts(args: t.List) -> exp.Func: 307 if len(args) == 2: 308 # Other dialects don't have the TIMESTAMP_FROM_PARTS(date, time) concept, 309 # so we parse this into Anonymous for now instead of introducing complexity 310 return exp.Anonymous(this="TIMESTAMP_FROM_PARTS", expressions=args) 311 312 return exp.TimestampFromParts.from_arg_list(args) 313 314 315def _unqualify_unpivot_columns(expression: exp.Expression) -> exp.Expression: 316 """ 317 Snowflake doesn't allow columns referenced in UNPIVOT to be qualified, 318 so we need to unqualify them. 319 320 Example: 321 >>> from sqlglot import parse_one 322 >>> expr = parse_one("SELECT * FROM m_sales UNPIVOT(sales FOR month IN (m_sales.jan, feb, mar, april))") 323 >>> print(_unqualify_unpivot_columns(expr).sql(dialect="snowflake")) 324 SELECT * FROM m_sales UNPIVOT(sales FOR month IN (jan, feb, mar, april)) 325 """ 326 if isinstance(expression, exp.Pivot) and expression.unpivot: 327 expression = transforms.unqualify_columns(expression) 328 329 return expression 330 331 332class Snowflake(Dialect): 333 # https://docs.snowflake.com/en/sql-reference/identifiers-syntax 334 NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE 335 NULL_ORDERING = "nulls_are_large" 336 TIME_FORMAT = "'YYYY-MM-DD HH24:MI:SS'" 337 SUPPORTS_USER_DEFINED_TYPES = False 338 SUPPORTS_SEMI_ANTI_JOIN = False 339 PREFER_CTE_ALIAS_COLUMN = True 340 TABLESAMPLE_SIZE_IS_PERCENT = True 341 342 TIME_MAPPING = { 343 "YYYY": "%Y", 344 "yyyy": "%Y", 345 "YY": "%y", 346 "yy": "%y", 347 "MMMM": "%B", 348 "mmmm": "%B", 349 "MON": "%b", 350 "mon": "%b", 351 "MM": "%m", 352 "mm": "%m", 353 "DD": "%d", 354 "dd": "%-d", 355 "DY": "%a", 356 "dy": "%w", 357 "HH24": "%H", 358 "hh24": "%H", 359 "HH12": "%I", 360 "hh12": "%I", 361 "MI": "%M", 362 "mi": "%M", 363 "SS": "%S", 364 "ss": "%S", 365 "FF": "%f", 366 "ff": "%f", 367 "FF6": "%f", 368 "ff6": "%f", 369 } 370 371 def quote_identifier(self, expression: E, identify: bool = True) -> E: 372 # This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an 373 # unquoted DUAL keyword in a special way and does not map it to a user-defined table 374 if ( 375 isinstance(expression, exp.Identifier) 376 and isinstance(expression.parent, exp.Table) 377 and expression.name.lower() == "dual" 378 ): 379 return t.cast(E, expression) 380 381 return super().quote_identifier(expression, identify=identify) 382 383 class Parser(parser.Parser): 384 IDENTIFY_PIVOT_STRINGS = True 385 386 TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.WINDOW} 387 388 FUNCTIONS = { 389 **parser.Parser.FUNCTIONS, 390 "ARRAYAGG": exp.ArrayAgg.from_arg_list, 391 "ARRAY_CONSTRUCT": exp.Array.from_arg_list, 392 "ARRAY_CONTAINS": lambda args: exp.ArrayContains( 393 this=seq_get(args, 1), expression=seq_get(args, 0) 394 ), 395 "ARRAY_GENERATE_RANGE": lambda args: exp.GenerateSeries( 396 # ARRAY_GENERATE_RANGE has an exlusive end; we normalize it to be inclusive 397 start=seq_get(args, 0), 398 end=exp.Sub(this=seq_get(args, 1), expression=exp.Literal.number(1)), 399 step=seq_get(args, 2), 400 ), 401 "ARRAY_TO_STRING": exp.ArrayJoin.from_arg_list, 402 "BITXOR": binary_from_function(exp.BitwiseXor), 403 "BIT_XOR": binary_from_function(exp.BitwiseXor), 404 "BOOLXOR": binary_from_function(exp.Xor), 405 "CONVERT_TIMEZONE": _parse_convert_timezone, 406 "DATE_TRUNC": _date_trunc_to_time, 407 "DATEADD": lambda args: exp.DateAdd( 408 this=seq_get(args, 2), 409 expression=seq_get(args, 1), 410 unit=_map_date_part(seq_get(args, 0)), 411 ), 412 "DATEDIFF": _parse_datediff, 413 "DIV0": _div0_to_if, 414 "FLATTEN": exp.Explode.from_arg_list, 415 "IFF": exp.If.from_arg_list, 416 "LAST_DAY": lambda args: exp.LastDay( 417 this=seq_get(args, 0), unit=_map_date_part(seq_get(args, 1)) 418 ), 419 "LISTAGG": exp.GroupConcat.from_arg_list, 420 "NULLIFZERO": _nullifzero_to_if, 421 "OBJECT_CONSTRUCT": _parse_object_construct, 422 "REGEXP_REPLACE": _parse_regexp_replace, 423 "REGEXP_SUBSTR": exp.RegexpExtract.from_arg_list, 424 "RLIKE": exp.RegexpLike.from_arg_list, 425 "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)), 426 "TIMEDIFF": _parse_datediff, 427 "TIMESTAMPDIFF": _parse_datediff, 428 "TIMESTAMPFROMPARTS": _parse_timestamp_from_parts, 429 "TIMESTAMP_FROM_PARTS": _parse_timestamp_from_parts, 430 "TO_TIMESTAMP": _parse_to_timestamp, 431 "TO_VARCHAR": exp.ToChar.from_arg_list, 432 "ZEROIFNULL": _zeroifnull_to_if, 433 } 434 435 FUNCTION_PARSERS = { 436 **parser.Parser.FUNCTION_PARSERS, 437 "DATE_PART": _parse_date_part, 438 "OBJECT_CONSTRUCT_KEEP_NULL": lambda self: self._parse_json_object(), 439 } 440 FUNCTION_PARSERS.pop("TRIM") 441 442 TIMESTAMPS = parser.Parser.TIMESTAMPS - {TokenType.TIME} 443 444 RANGE_PARSERS = { 445 **parser.Parser.RANGE_PARSERS, 446 TokenType.LIKE_ANY: parser.binary_range_parser(exp.LikeAny), 447 TokenType.ILIKE_ANY: parser.binary_range_parser(exp.ILikeAny), 448 TokenType.COLON: _parse_colon_get_path, 449 } 450 451 ALTER_PARSERS = { 452 **parser.Parser.ALTER_PARSERS, 453 "SET": lambda self: self._parse_set(tag=self._match_text_seq("TAG")), 454 "UNSET": lambda self: self.expression( 455 exp.Set, 456 tag=self._match_text_seq("TAG"), 457 expressions=self._parse_csv(self._parse_id_var), 458 unset=True, 459 ), 460 "SWAP": lambda self: self._parse_alter_table_swap(), 461 } 462 463 STATEMENT_PARSERS = { 464 **parser.Parser.STATEMENT_PARSERS, 465 TokenType.SHOW: lambda self: self._parse_show(), 466 } 467 468 PROPERTY_PARSERS = { 469 **parser.Parser.PROPERTY_PARSERS, 470 "LOCATION": lambda self: self._parse_location(), 471 } 472 473 SHOW_PARSERS = { 474 "PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 475 "TERSE PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 476 "COLUMNS": _show_parser("COLUMNS"), 477 } 478 479 STAGED_FILE_SINGLE_TOKENS = { 480 TokenType.DOT, 481 TokenType.MOD, 482 TokenType.SLASH, 483 } 484 485 FLATTEN_COLUMNS = ["SEQ", "KEY", "PATH", "INDEX", "VALUE", "THIS"] 486 487 def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expression]: 488 if is_map: 489 # Keys are strings in Snowflake's objects, see also: 490 # - https://docs.snowflake.com/en/sql-reference/data-types-semistructured 491 # - https://docs.snowflake.com/en/sql-reference/functions/object_construct 492 return self._parse_slice(self._parse_string()) 493 494 return self._parse_slice(self._parse_alias(self._parse_conjunction(), explicit=True)) 495 496 def _parse_lateral(self) -> t.Optional[exp.Lateral]: 497 lateral = super()._parse_lateral() 498 if not lateral: 499 return lateral 500 501 if isinstance(lateral.this, exp.Explode): 502 table_alias = lateral.args.get("alias") 503 columns = [exp.to_identifier(col) for col in self.FLATTEN_COLUMNS] 504 if table_alias and not table_alias.args.get("columns"): 505 table_alias.set("columns", columns) 506 elif not table_alias: 507 exp.alias_(lateral, "_flattened", table=columns, copy=False) 508 509 return lateral 510 511 def _parse_at_before(self, table: exp.Table) -> exp.Table: 512 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 513 index = self._index 514 if self._match_texts(("AT", "BEFORE")): 515 this = self._prev.text.upper() 516 kind = ( 517 self._match(TokenType.L_PAREN) 518 and self._match_texts(self.HISTORICAL_DATA_KIND) 519 and self._prev.text.upper() 520 ) 521 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 522 523 if expression: 524 self._match_r_paren() 525 when = self.expression( 526 exp.HistoricalData, this=this, kind=kind, expression=expression 527 ) 528 table.set("when", when) 529 else: 530 self._retreat(index) 531 532 return table 533 534 def _parse_table_parts(self, schema: bool = False) -> exp.Table: 535 # https://docs.snowflake.com/en/user-guide/querying-stage 536 if self._match(TokenType.STRING, advance=False): 537 table = self._parse_string() 538 elif self._match_text_seq("@", advance=False): 539 table = self._parse_location_path() 540 else: 541 table = None 542 543 if table: 544 file_format = None 545 pattern = None 546 547 self._match(TokenType.L_PAREN) 548 while self._curr and not self._match(TokenType.R_PAREN): 549 if self._match_text_seq("FILE_FORMAT", "=>"): 550 file_format = self._parse_string() or super()._parse_table_parts() 551 elif self._match_text_seq("PATTERN", "=>"): 552 pattern = self._parse_string() 553 else: 554 break 555 556 self._match(TokenType.COMMA) 557 558 table = self.expression(exp.Table, this=table, format=file_format, pattern=pattern) 559 else: 560 table = super()._parse_table_parts(schema=schema) 561 562 return self._parse_at_before(table) 563 564 def _parse_id_var( 565 self, 566 any_token: bool = True, 567 tokens: t.Optional[t.Collection[TokenType]] = None, 568 ) -> t.Optional[exp.Expression]: 569 if self._match_text_seq("IDENTIFIER", "("): 570 identifier = ( 571 super()._parse_id_var(any_token=any_token, tokens=tokens) 572 or self._parse_string() 573 ) 574 self._match_r_paren() 575 return self.expression(exp.Anonymous, this="IDENTIFIER", expressions=[identifier]) 576 577 return super()._parse_id_var(any_token=any_token, tokens=tokens) 578 579 def _parse_show_snowflake(self, this: str) -> exp.Show: 580 scope = None 581 scope_kind = None 582 583 like = self._parse_string() if self._match(TokenType.LIKE) else None 584 585 if self._match(TokenType.IN): 586 if self._match_text_seq("ACCOUNT"): 587 scope_kind = "ACCOUNT" 588 elif self._match_set(self.DB_CREATABLES): 589 scope_kind = self._prev.text 590 if self._curr: 591 scope = self._parse_table() 592 elif self._curr: 593 scope_kind = "TABLE" 594 scope = self._parse_table() 595 596 return self.expression( 597 exp.Show, this=this, like=like, scope=scope, scope_kind=scope_kind 598 ) 599 600 def _parse_alter_table_swap(self) -> exp.SwapTable: 601 self._match_text_seq("WITH") 602 return self.expression(exp.SwapTable, this=self._parse_table(schema=True)) 603 604 def _parse_location(self) -> exp.LocationProperty: 605 self._match(TokenType.EQ) 606 return self.expression(exp.LocationProperty, this=self._parse_location_path()) 607 608 def _parse_location_path(self) -> exp.Var: 609 parts = [self._advance_any(ignore_reserved=True)] 610 611 # We avoid consuming a comma token because external tables like @foo and @bar 612 # can be joined in a query with a comma separator. 613 while self._is_connected() and not self._match(TokenType.COMMA, advance=False): 614 parts.append(self._advance_any(ignore_reserved=True)) 615 616 return exp.var("".join(part.text for part in parts if part)) 617 618 class Tokenizer(tokens.Tokenizer): 619 STRING_ESCAPES = ["\\", "'"] 620 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 621 RAW_STRINGS = ["$$"] 622 COMMENTS = ["--", "//", ("/*", "*/")] 623 624 KEYWORDS = { 625 **tokens.Tokenizer.KEYWORDS, 626 "BYTEINT": TokenType.INT, 627 "CHAR VARYING": TokenType.VARCHAR, 628 "CHARACTER VARYING": TokenType.VARCHAR, 629 "EXCLUDE": TokenType.EXCEPT, 630 "ILIKE ANY": TokenType.ILIKE_ANY, 631 "LIKE ANY": TokenType.LIKE_ANY, 632 "MATCH_RECOGNIZE": TokenType.MATCH_RECOGNIZE, 633 "MINUS": TokenType.EXCEPT, 634 "NCHAR VARYING": TokenType.VARCHAR, 635 "PUT": TokenType.COMMAND, 636 "REMOVE": TokenType.COMMAND, 637 "RENAME": TokenType.REPLACE, 638 "RM": TokenType.COMMAND, 639 "SAMPLE": TokenType.TABLE_SAMPLE, 640 "SQL_DOUBLE": TokenType.DOUBLE, 641 "SQL_VARCHAR": TokenType.VARCHAR, 642 "TIMESTAMP_LTZ": TokenType.TIMESTAMPLTZ, 643 "TIMESTAMP_NTZ": TokenType.TIMESTAMP, 644 "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ, 645 "TIMESTAMPNTZ": TokenType.TIMESTAMP, 646 "TOP": TokenType.TOP, 647 } 648 649 SINGLE_TOKENS = { 650 **tokens.Tokenizer.SINGLE_TOKENS, 651 "$": TokenType.PARAMETER, 652 } 653 654 VAR_SINGLE_TOKENS = {"$"} 655 656 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW} 657 658 class Generator(generator.Generator): 659 PARAMETER_TOKEN = "$" 660 MATCHED_BY_SOURCE = False 661 SINGLE_STRING_INTERVAL = True 662 JOIN_HINTS = False 663 TABLE_HINTS = False 664 QUERY_HINTS = False 665 AGGREGATE_FILTER_SUPPORTED = False 666 SUPPORTS_TABLE_COPY = False 667 COLLATE_IS_FUNC = True 668 LIMIT_ONLY_LITERALS = True 669 JSON_KEY_VALUE_PAIR_SEP = "," 670 INSERT_OVERWRITE = " OVERWRITE INTO" 671 672 TRANSFORMS = { 673 **generator.Generator.TRANSFORMS, 674 exp.ArgMax: rename_func("MAX_BY"), 675 exp.ArgMin: rename_func("MIN_BY"), 676 exp.Array: inline_array_sql, 677 exp.ArrayConcat: rename_func("ARRAY_CAT"), 678 exp.ArrayContains: lambda self, e: self.func("ARRAY_CONTAINS", e.expression, e.this), 679 exp.ArrayJoin: rename_func("ARRAY_TO_STRING"), 680 exp.AtTimeZone: lambda self, e: self.func( 681 "CONVERT_TIMEZONE", e.args.get("zone"), e.this 682 ), 683 exp.BitwiseXor: rename_func("BITXOR"), 684 exp.DateAdd: date_delta_sql("DATEADD"), 685 exp.DateDiff: date_delta_sql("DATEDIFF"), 686 exp.DateStrToDate: datestrtodate_sql, 687 exp.DataType: _datatype_sql, 688 exp.DayOfMonth: rename_func("DAYOFMONTH"), 689 exp.DayOfWeek: rename_func("DAYOFWEEK"), 690 exp.DayOfYear: rename_func("DAYOFYEAR"), 691 exp.Explode: rename_func("FLATTEN"), 692 exp.Extract: rename_func("DATE_PART"), 693 exp.GenerateSeries: lambda self, e: self.func( 694 "ARRAY_GENERATE_RANGE", e.args["start"], e.args["end"] + 1, e.args.get("step") 695 ), 696 exp.GroupConcat: rename_func("LISTAGG"), 697 exp.If: if_sql(name="IFF", false_value="NULL"), 698 exp.JSONExtract: lambda self, e: f"{self.sql(e, 'this')}[{self.sql(e, 'expression')}]", 699 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 700 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 701 exp.LogicalOr: rename_func("BOOLOR_AGG"), 702 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 703 exp.Max: max_or_greatest, 704 exp.Min: min_or_least, 705 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 706 exp.PercentileCont: transforms.preprocess( 707 [transforms.add_within_group_for_percentiles] 708 ), 709 exp.PercentileDisc: transforms.preprocess( 710 [transforms.add_within_group_for_percentiles] 711 ), 712 exp.Pivot: transforms.preprocess([_unqualify_unpivot_columns]), 713 exp.RegexpILike: _regexpilike_sql, 714 exp.Rand: rename_func("RANDOM"), 715 exp.Select: transforms.preprocess( 716 [ 717 transforms.eliminate_distinct_on, 718 transforms.explode_to_unnest(), 719 transforms.eliminate_semi_and_anti_joins, 720 ] 721 ), 722 exp.SHA: rename_func("SHA1"), 723 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 724 exp.StartsWith: rename_func("STARTSWITH"), 725 exp.StrPosition: lambda self, e: self.func( 726 "POSITION", e.args.get("substr"), e.this, e.args.get("position") 727 ), 728 exp.StrToTime: lambda self, e: f"TO_TIMESTAMP({self.sql(e, 'this')}, {self.format_time(e)})", 729 exp.Struct: lambda self, e: self.func( 730 "OBJECT_CONSTRUCT", 731 *(arg for expression in e.expressions for arg in expression.flatten()), 732 ), 733 exp.Stuff: rename_func("INSERT"), 734 exp.TimestampDiff: lambda self, e: self.func( 735 "TIMESTAMPDIFF", e.unit, e.expression, e.this 736 ), 737 exp.TimestampTrunc: timestamptrunc_sql, 738 exp.TimeStrToTime: timestrtotime_sql, 739 exp.TimeToStr: lambda self, e: self.func( 740 "TO_CHAR", exp.cast(e.this, "timestamp"), self.format_time(e) 741 ), 742 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 743 exp.ToArray: rename_func("TO_ARRAY"), 744 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 745 exp.Trim: lambda self, e: self.func("TRIM", e.this, e.expression), 746 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 747 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 748 exp.UnixToTime: rename_func("TO_TIMESTAMP"), 749 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 750 exp.WeekOfYear: rename_func("WEEKOFYEAR"), 751 exp.Xor: rename_func("BOOLXOR"), 752 } 753 754 TYPE_MAPPING = { 755 **generator.Generator.TYPE_MAPPING, 756 exp.DataType.Type.TIMESTAMP: "TIMESTAMPNTZ", 757 } 758 759 STAR_MAPPING = { 760 "except": "EXCLUDE", 761 "replace": "RENAME", 762 } 763 764 PROPERTIES_LOCATION = { 765 **generator.Generator.PROPERTIES_LOCATION, 766 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 767 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 768 } 769 770 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 771 milli = expression.args.get("milli") 772 if milli is not None: 773 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 774 expression.set("nano", milli_to_nano) 775 776 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 777 778 def trycast_sql(self, expression: exp.TryCast) -> str: 779 value = expression.this 780 781 if value.type is None: 782 from sqlglot.optimizer.annotate_types import annotate_types 783 784 value = annotate_types(value) 785 786 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 787 return super().trycast_sql(expression) 788 789 # TRY_CAST only works for string values in Snowflake 790 return self.cast_sql(expression) 791 792 def log_sql(self, expression: exp.Log) -> str: 793 if not expression.expression: 794 return self.func("LN", expression.this) 795 796 return super().log_sql(expression) 797 798 def unnest_sql(self, expression: exp.Unnest) -> str: 799 unnest_alias = expression.args.get("alias") 800 offset = expression.args.get("offset") 801 802 columns = [ 803 exp.to_identifier("seq"), 804 exp.to_identifier("key"), 805 exp.to_identifier("path"), 806 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 807 seq_get(unnest_alias.columns if unnest_alias else [], 0) 808 or exp.to_identifier("value"), 809 exp.to_identifier("this"), 810 ] 811 812 if unnest_alias: 813 unnest_alias.set("columns", columns) 814 else: 815 unnest_alias = exp.TableAlias(this="_u", columns=columns) 816 817 explode = f"TABLE(FLATTEN(INPUT => {self.sql(expression.expressions[0])}))" 818 alias = self.sql(unnest_alias) 819 alias = f" AS {alias}" if alias else "" 820 return f"{explode}{alias}" 821 822 def show_sql(self, expression: exp.Show) -> str: 823 like = self.sql(expression, "like") 824 like = f" LIKE {like}" if like else "" 825 826 scope = self.sql(expression, "scope") 827 scope = f" {scope}" if scope else "" 828 829 scope_kind = self.sql(expression, "scope_kind") 830 if scope_kind: 831 scope_kind = f" IN {scope_kind}" 832 833 return f"SHOW {expression.name}{like}{scope_kind}{scope}" 834 835 def regexpextract_sql(self, expression: exp.RegexpExtract) -> str: 836 # Other dialects don't support all of the following parameters, so we need to 837 # generate default values as necessary to ensure the transpilation is correct 838 group = expression.args.get("group") 839 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 840 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 841 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 842 843 return self.func( 844 "REGEXP_SUBSTR", 845 expression.this, 846 expression.expression, 847 position, 848 occurrence, 849 parameters, 850 group, 851 ) 852 853 def except_op(self, expression: exp.Except) -> str: 854 if not expression.args.get("distinct", False): 855 self.unsupported("EXCEPT with All is not supported in Snowflake") 856 return super().except_op(expression) 857 858 def intersect_op(self, expression: exp.Intersect) -> str: 859 if not expression.args.get("distinct", False): 860 self.unsupported("INTERSECT with All is not supported in Snowflake") 861 return super().intersect_op(expression) 862 863 def describe_sql(self, expression: exp.Describe) -> str: 864 # Default to table if kind is unknown 865 kind_value = expression.args.get("kind") or "TABLE" 866 kind = f" {kind_value}" if kind_value else "" 867 this = f" {self.sql(expression, 'this')}" 868 expressions = self.expressions(expression, flat=True) 869 expressions = f" {expressions}" if expressions else "" 870 return f"DESCRIBE{kind}{this}{expressions}" 871 872 def generatedasidentitycolumnconstraint_sql( 873 self, expression: exp.GeneratedAsIdentityColumnConstraint 874 ) -> str: 875 start = expression.args.get("start") 876 start = f" START {start}" if start else "" 877 increment = expression.args.get("increment") 878 increment = f" INCREMENT {increment}" if increment else "" 879 return f"AUTOINCREMENT{start}{increment}" 880 881 def swaptable_sql(self, expression: exp.SwapTable) -> str: 882 this = self.sql(expression, "this") 883 return f"SWAP WITH {this}" 884 885 def with_properties(self, properties: exp.Properties) -> str: 886 return self.properties(properties, wrapped=False, prefix=self.seg(""), sep=" ")
333class Snowflake(Dialect): 334 # https://docs.snowflake.com/en/sql-reference/identifiers-syntax 335 NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE 336 NULL_ORDERING = "nulls_are_large" 337 TIME_FORMAT = "'YYYY-MM-DD HH24:MI:SS'" 338 SUPPORTS_USER_DEFINED_TYPES = False 339 SUPPORTS_SEMI_ANTI_JOIN = False 340 PREFER_CTE_ALIAS_COLUMN = True 341 TABLESAMPLE_SIZE_IS_PERCENT = True 342 343 TIME_MAPPING = { 344 "YYYY": "%Y", 345 "yyyy": "%Y", 346 "YY": "%y", 347 "yy": "%y", 348 "MMMM": "%B", 349 "mmmm": "%B", 350 "MON": "%b", 351 "mon": "%b", 352 "MM": "%m", 353 "mm": "%m", 354 "DD": "%d", 355 "dd": "%-d", 356 "DY": "%a", 357 "dy": "%w", 358 "HH24": "%H", 359 "hh24": "%H", 360 "HH12": "%I", 361 "hh12": "%I", 362 "MI": "%M", 363 "mi": "%M", 364 "SS": "%S", 365 "ss": "%S", 366 "FF": "%f", 367 "ff": "%f", 368 "FF6": "%f", 369 "ff6": "%f", 370 } 371 372 def quote_identifier(self, expression: E, identify: bool = True) -> E: 373 # This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an 374 # unquoted DUAL keyword in a special way and does not map it to a user-defined table 375 if ( 376 isinstance(expression, exp.Identifier) 377 and isinstance(expression.parent, exp.Table) 378 and expression.name.lower() == "dual" 379 ): 380 return t.cast(E, expression) 381 382 return super().quote_identifier(expression, identify=identify) 383 384 class Parser(parser.Parser): 385 IDENTIFY_PIVOT_STRINGS = True 386 387 TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.WINDOW} 388 389 FUNCTIONS = { 390 **parser.Parser.FUNCTIONS, 391 "ARRAYAGG": exp.ArrayAgg.from_arg_list, 392 "ARRAY_CONSTRUCT": exp.Array.from_arg_list, 393 "ARRAY_CONTAINS": lambda args: exp.ArrayContains( 394 this=seq_get(args, 1), expression=seq_get(args, 0) 395 ), 396 "ARRAY_GENERATE_RANGE": lambda args: exp.GenerateSeries( 397 # ARRAY_GENERATE_RANGE has an exlusive end; we normalize it to be inclusive 398 start=seq_get(args, 0), 399 end=exp.Sub(this=seq_get(args, 1), expression=exp.Literal.number(1)), 400 step=seq_get(args, 2), 401 ), 402 "ARRAY_TO_STRING": exp.ArrayJoin.from_arg_list, 403 "BITXOR": binary_from_function(exp.BitwiseXor), 404 "BIT_XOR": binary_from_function(exp.BitwiseXor), 405 "BOOLXOR": binary_from_function(exp.Xor), 406 "CONVERT_TIMEZONE": _parse_convert_timezone, 407 "DATE_TRUNC": _date_trunc_to_time, 408 "DATEADD": lambda args: exp.DateAdd( 409 this=seq_get(args, 2), 410 expression=seq_get(args, 1), 411 unit=_map_date_part(seq_get(args, 0)), 412 ), 413 "DATEDIFF": _parse_datediff, 414 "DIV0": _div0_to_if, 415 "FLATTEN": exp.Explode.from_arg_list, 416 "IFF": exp.If.from_arg_list, 417 "LAST_DAY": lambda args: exp.LastDay( 418 this=seq_get(args, 0), unit=_map_date_part(seq_get(args, 1)) 419 ), 420 "LISTAGG": exp.GroupConcat.from_arg_list, 421 "NULLIFZERO": _nullifzero_to_if, 422 "OBJECT_CONSTRUCT": _parse_object_construct, 423 "REGEXP_REPLACE": _parse_regexp_replace, 424 "REGEXP_SUBSTR": exp.RegexpExtract.from_arg_list, 425 "RLIKE": exp.RegexpLike.from_arg_list, 426 "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)), 427 "TIMEDIFF": _parse_datediff, 428 "TIMESTAMPDIFF": _parse_datediff, 429 "TIMESTAMPFROMPARTS": _parse_timestamp_from_parts, 430 "TIMESTAMP_FROM_PARTS": _parse_timestamp_from_parts, 431 "TO_TIMESTAMP": _parse_to_timestamp, 432 "TO_VARCHAR": exp.ToChar.from_arg_list, 433 "ZEROIFNULL": _zeroifnull_to_if, 434 } 435 436 FUNCTION_PARSERS = { 437 **parser.Parser.FUNCTION_PARSERS, 438 "DATE_PART": _parse_date_part, 439 "OBJECT_CONSTRUCT_KEEP_NULL": lambda self: self._parse_json_object(), 440 } 441 FUNCTION_PARSERS.pop("TRIM") 442 443 TIMESTAMPS = parser.Parser.TIMESTAMPS - {TokenType.TIME} 444 445 RANGE_PARSERS = { 446 **parser.Parser.RANGE_PARSERS, 447 TokenType.LIKE_ANY: parser.binary_range_parser(exp.LikeAny), 448 TokenType.ILIKE_ANY: parser.binary_range_parser(exp.ILikeAny), 449 TokenType.COLON: _parse_colon_get_path, 450 } 451 452 ALTER_PARSERS = { 453 **parser.Parser.ALTER_PARSERS, 454 "SET": lambda self: self._parse_set(tag=self._match_text_seq("TAG")), 455 "UNSET": lambda self: self.expression( 456 exp.Set, 457 tag=self._match_text_seq("TAG"), 458 expressions=self._parse_csv(self._parse_id_var), 459 unset=True, 460 ), 461 "SWAP": lambda self: self._parse_alter_table_swap(), 462 } 463 464 STATEMENT_PARSERS = { 465 **parser.Parser.STATEMENT_PARSERS, 466 TokenType.SHOW: lambda self: self._parse_show(), 467 } 468 469 PROPERTY_PARSERS = { 470 **parser.Parser.PROPERTY_PARSERS, 471 "LOCATION": lambda self: self._parse_location(), 472 } 473 474 SHOW_PARSERS = { 475 "PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 476 "TERSE PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 477 "COLUMNS": _show_parser("COLUMNS"), 478 } 479 480 STAGED_FILE_SINGLE_TOKENS = { 481 TokenType.DOT, 482 TokenType.MOD, 483 TokenType.SLASH, 484 } 485 486 FLATTEN_COLUMNS = ["SEQ", "KEY", "PATH", "INDEX", "VALUE", "THIS"] 487 488 def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expression]: 489 if is_map: 490 # Keys are strings in Snowflake's objects, see also: 491 # - https://docs.snowflake.com/en/sql-reference/data-types-semistructured 492 # - https://docs.snowflake.com/en/sql-reference/functions/object_construct 493 return self._parse_slice(self._parse_string()) 494 495 return self._parse_slice(self._parse_alias(self._parse_conjunction(), explicit=True)) 496 497 def _parse_lateral(self) -> t.Optional[exp.Lateral]: 498 lateral = super()._parse_lateral() 499 if not lateral: 500 return lateral 501 502 if isinstance(lateral.this, exp.Explode): 503 table_alias = lateral.args.get("alias") 504 columns = [exp.to_identifier(col) for col in self.FLATTEN_COLUMNS] 505 if table_alias and not table_alias.args.get("columns"): 506 table_alias.set("columns", columns) 507 elif not table_alias: 508 exp.alias_(lateral, "_flattened", table=columns, copy=False) 509 510 return lateral 511 512 def _parse_at_before(self, table: exp.Table) -> exp.Table: 513 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 514 index = self._index 515 if self._match_texts(("AT", "BEFORE")): 516 this = self._prev.text.upper() 517 kind = ( 518 self._match(TokenType.L_PAREN) 519 and self._match_texts(self.HISTORICAL_DATA_KIND) 520 and self._prev.text.upper() 521 ) 522 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 523 524 if expression: 525 self._match_r_paren() 526 when = self.expression( 527 exp.HistoricalData, this=this, kind=kind, expression=expression 528 ) 529 table.set("when", when) 530 else: 531 self._retreat(index) 532 533 return table 534 535 def _parse_table_parts(self, schema: bool = False) -> exp.Table: 536 # https://docs.snowflake.com/en/user-guide/querying-stage 537 if self._match(TokenType.STRING, advance=False): 538 table = self._parse_string() 539 elif self._match_text_seq("@", advance=False): 540 table = self._parse_location_path() 541 else: 542 table = None 543 544 if table: 545 file_format = None 546 pattern = None 547 548 self._match(TokenType.L_PAREN) 549 while self._curr and not self._match(TokenType.R_PAREN): 550 if self._match_text_seq("FILE_FORMAT", "=>"): 551 file_format = self._parse_string() or super()._parse_table_parts() 552 elif self._match_text_seq("PATTERN", "=>"): 553 pattern = self._parse_string() 554 else: 555 break 556 557 self._match(TokenType.COMMA) 558 559 table = self.expression(exp.Table, this=table, format=file_format, pattern=pattern) 560 else: 561 table = super()._parse_table_parts(schema=schema) 562 563 return self._parse_at_before(table) 564 565 def _parse_id_var( 566 self, 567 any_token: bool = True, 568 tokens: t.Optional[t.Collection[TokenType]] = None, 569 ) -> t.Optional[exp.Expression]: 570 if self._match_text_seq("IDENTIFIER", "("): 571 identifier = ( 572 super()._parse_id_var(any_token=any_token, tokens=tokens) 573 or self._parse_string() 574 ) 575 self._match_r_paren() 576 return self.expression(exp.Anonymous, this="IDENTIFIER", expressions=[identifier]) 577 578 return super()._parse_id_var(any_token=any_token, tokens=tokens) 579 580 def _parse_show_snowflake(self, this: str) -> exp.Show: 581 scope = None 582 scope_kind = None 583 584 like = self._parse_string() if self._match(TokenType.LIKE) else None 585 586 if self._match(TokenType.IN): 587 if self._match_text_seq("ACCOUNT"): 588 scope_kind = "ACCOUNT" 589 elif self._match_set(self.DB_CREATABLES): 590 scope_kind = self._prev.text 591 if self._curr: 592 scope = self._parse_table() 593 elif self._curr: 594 scope_kind = "TABLE" 595 scope = self._parse_table() 596 597 return self.expression( 598 exp.Show, this=this, like=like, scope=scope, scope_kind=scope_kind 599 ) 600 601 def _parse_alter_table_swap(self) -> exp.SwapTable: 602 self._match_text_seq("WITH") 603 return self.expression(exp.SwapTable, this=self._parse_table(schema=True)) 604 605 def _parse_location(self) -> exp.LocationProperty: 606 self._match(TokenType.EQ) 607 return self.expression(exp.LocationProperty, this=self._parse_location_path()) 608 609 def _parse_location_path(self) -> exp.Var: 610 parts = [self._advance_any(ignore_reserved=True)] 611 612 # We avoid consuming a comma token because external tables like @foo and @bar 613 # can be joined in a query with a comma separator. 614 while self._is_connected() and not self._match(TokenType.COMMA, advance=False): 615 parts.append(self._advance_any(ignore_reserved=True)) 616 617 return exp.var("".join(part.text for part in parts if part)) 618 619 class Tokenizer(tokens.Tokenizer): 620 STRING_ESCAPES = ["\\", "'"] 621 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 622 RAW_STRINGS = ["$$"] 623 COMMENTS = ["--", "//", ("/*", "*/")] 624 625 KEYWORDS = { 626 **tokens.Tokenizer.KEYWORDS, 627 "BYTEINT": TokenType.INT, 628 "CHAR VARYING": TokenType.VARCHAR, 629 "CHARACTER VARYING": TokenType.VARCHAR, 630 "EXCLUDE": TokenType.EXCEPT, 631 "ILIKE ANY": TokenType.ILIKE_ANY, 632 "LIKE ANY": TokenType.LIKE_ANY, 633 "MATCH_RECOGNIZE": TokenType.MATCH_RECOGNIZE, 634 "MINUS": TokenType.EXCEPT, 635 "NCHAR VARYING": TokenType.VARCHAR, 636 "PUT": TokenType.COMMAND, 637 "REMOVE": TokenType.COMMAND, 638 "RENAME": TokenType.REPLACE, 639 "RM": TokenType.COMMAND, 640 "SAMPLE": TokenType.TABLE_SAMPLE, 641 "SQL_DOUBLE": TokenType.DOUBLE, 642 "SQL_VARCHAR": TokenType.VARCHAR, 643 "TIMESTAMP_LTZ": TokenType.TIMESTAMPLTZ, 644 "TIMESTAMP_NTZ": TokenType.TIMESTAMP, 645 "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ, 646 "TIMESTAMPNTZ": TokenType.TIMESTAMP, 647 "TOP": TokenType.TOP, 648 } 649 650 SINGLE_TOKENS = { 651 **tokens.Tokenizer.SINGLE_TOKENS, 652 "$": TokenType.PARAMETER, 653 } 654 655 VAR_SINGLE_TOKENS = {"$"} 656 657 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW} 658 659 class Generator(generator.Generator): 660 PARAMETER_TOKEN = "$" 661 MATCHED_BY_SOURCE = False 662 SINGLE_STRING_INTERVAL = True 663 JOIN_HINTS = False 664 TABLE_HINTS = False 665 QUERY_HINTS = False 666 AGGREGATE_FILTER_SUPPORTED = False 667 SUPPORTS_TABLE_COPY = False 668 COLLATE_IS_FUNC = True 669 LIMIT_ONLY_LITERALS = True 670 JSON_KEY_VALUE_PAIR_SEP = "," 671 INSERT_OVERWRITE = " OVERWRITE INTO" 672 673 TRANSFORMS = { 674 **generator.Generator.TRANSFORMS, 675 exp.ArgMax: rename_func("MAX_BY"), 676 exp.ArgMin: rename_func("MIN_BY"), 677 exp.Array: inline_array_sql, 678 exp.ArrayConcat: rename_func("ARRAY_CAT"), 679 exp.ArrayContains: lambda self, e: self.func("ARRAY_CONTAINS", e.expression, e.this), 680 exp.ArrayJoin: rename_func("ARRAY_TO_STRING"), 681 exp.AtTimeZone: lambda self, e: self.func( 682 "CONVERT_TIMEZONE", e.args.get("zone"), e.this 683 ), 684 exp.BitwiseXor: rename_func("BITXOR"), 685 exp.DateAdd: date_delta_sql("DATEADD"), 686 exp.DateDiff: date_delta_sql("DATEDIFF"), 687 exp.DateStrToDate: datestrtodate_sql, 688 exp.DataType: _datatype_sql, 689 exp.DayOfMonth: rename_func("DAYOFMONTH"), 690 exp.DayOfWeek: rename_func("DAYOFWEEK"), 691 exp.DayOfYear: rename_func("DAYOFYEAR"), 692 exp.Explode: rename_func("FLATTEN"), 693 exp.Extract: rename_func("DATE_PART"), 694 exp.GenerateSeries: lambda self, e: self.func( 695 "ARRAY_GENERATE_RANGE", e.args["start"], e.args["end"] + 1, e.args.get("step") 696 ), 697 exp.GroupConcat: rename_func("LISTAGG"), 698 exp.If: if_sql(name="IFF", false_value="NULL"), 699 exp.JSONExtract: lambda self, e: f"{self.sql(e, 'this')}[{self.sql(e, 'expression')}]", 700 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 701 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 702 exp.LogicalOr: rename_func("BOOLOR_AGG"), 703 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 704 exp.Max: max_or_greatest, 705 exp.Min: min_or_least, 706 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 707 exp.PercentileCont: transforms.preprocess( 708 [transforms.add_within_group_for_percentiles] 709 ), 710 exp.PercentileDisc: transforms.preprocess( 711 [transforms.add_within_group_for_percentiles] 712 ), 713 exp.Pivot: transforms.preprocess([_unqualify_unpivot_columns]), 714 exp.RegexpILike: _regexpilike_sql, 715 exp.Rand: rename_func("RANDOM"), 716 exp.Select: transforms.preprocess( 717 [ 718 transforms.eliminate_distinct_on, 719 transforms.explode_to_unnest(), 720 transforms.eliminate_semi_and_anti_joins, 721 ] 722 ), 723 exp.SHA: rename_func("SHA1"), 724 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 725 exp.StartsWith: rename_func("STARTSWITH"), 726 exp.StrPosition: lambda self, e: self.func( 727 "POSITION", e.args.get("substr"), e.this, e.args.get("position") 728 ), 729 exp.StrToTime: lambda self, e: f"TO_TIMESTAMP({self.sql(e, 'this')}, {self.format_time(e)})", 730 exp.Struct: lambda self, e: self.func( 731 "OBJECT_CONSTRUCT", 732 *(arg for expression in e.expressions for arg in expression.flatten()), 733 ), 734 exp.Stuff: rename_func("INSERT"), 735 exp.TimestampDiff: lambda self, e: self.func( 736 "TIMESTAMPDIFF", e.unit, e.expression, e.this 737 ), 738 exp.TimestampTrunc: timestamptrunc_sql, 739 exp.TimeStrToTime: timestrtotime_sql, 740 exp.TimeToStr: lambda self, e: self.func( 741 "TO_CHAR", exp.cast(e.this, "timestamp"), self.format_time(e) 742 ), 743 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 744 exp.ToArray: rename_func("TO_ARRAY"), 745 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 746 exp.Trim: lambda self, e: self.func("TRIM", e.this, e.expression), 747 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 748 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 749 exp.UnixToTime: rename_func("TO_TIMESTAMP"), 750 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 751 exp.WeekOfYear: rename_func("WEEKOFYEAR"), 752 exp.Xor: rename_func("BOOLXOR"), 753 } 754 755 TYPE_MAPPING = { 756 **generator.Generator.TYPE_MAPPING, 757 exp.DataType.Type.TIMESTAMP: "TIMESTAMPNTZ", 758 } 759 760 STAR_MAPPING = { 761 "except": "EXCLUDE", 762 "replace": "RENAME", 763 } 764 765 PROPERTIES_LOCATION = { 766 **generator.Generator.PROPERTIES_LOCATION, 767 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 768 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 769 } 770 771 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 772 milli = expression.args.get("milli") 773 if milli is not None: 774 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 775 expression.set("nano", milli_to_nano) 776 777 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 778 779 def trycast_sql(self, expression: exp.TryCast) -> str: 780 value = expression.this 781 782 if value.type is None: 783 from sqlglot.optimizer.annotate_types import annotate_types 784 785 value = annotate_types(value) 786 787 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 788 return super().trycast_sql(expression) 789 790 # TRY_CAST only works for string values in Snowflake 791 return self.cast_sql(expression) 792 793 def log_sql(self, expression: exp.Log) -> str: 794 if not expression.expression: 795 return self.func("LN", expression.this) 796 797 return super().log_sql(expression) 798 799 def unnest_sql(self, expression: exp.Unnest) -> str: 800 unnest_alias = expression.args.get("alias") 801 offset = expression.args.get("offset") 802 803 columns = [ 804 exp.to_identifier("seq"), 805 exp.to_identifier("key"), 806 exp.to_identifier("path"), 807 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 808 seq_get(unnest_alias.columns if unnest_alias else [], 0) 809 or exp.to_identifier("value"), 810 exp.to_identifier("this"), 811 ] 812 813 if unnest_alias: 814 unnest_alias.set("columns", columns) 815 else: 816 unnest_alias = exp.TableAlias(this="_u", columns=columns) 817 818 explode = f"TABLE(FLATTEN(INPUT => {self.sql(expression.expressions[0])}))" 819 alias = self.sql(unnest_alias) 820 alias = f" AS {alias}" if alias else "" 821 return f"{explode}{alias}" 822 823 def show_sql(self, expression: exp.Show) -> str: 824 like = self.sql(expression, "like") 825 like = f" LIKE {like}" if like else "" 826 827 scope = self.sql(expression, "scope") 828 scope = f" {scope}" if scope else "" 829 830 scope_kind = self.sql(expression, "scope_kind") 831 if scope_kind: 832 scope_kind = f" IN {scope_kind}" 833 834 return f"SHOW {expression.name}{like}{scope_kind}{scope}" 835 836 def regexpextract_sql(self, expression: exp.RegexpExtract) -> str: 837 # Other dialects don't support all of the following parameters, so we need to 838 # generate default values as necessary to ensure the transpilation is correct 839 group = expression.args.get("group") 840 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 841 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 842 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 843 844 return self.func( 845 "REGEXP_SUBSTR", 846 expression.this, 847 expression.expression, 848 position, 849 occurrence, 850 parameters, 851 group, 852 ) 853 854 def except_op(self, expression: exp.Except) -> str: 855 if not expression.args.get("distinct", False): 856 self.unsupported("EXCEPT with All is not supported in Snowflake") 857 return super().except_op(expression) 858 859 def intersect_op(self, expression: exp.Intersect) -> str: 860 if not expression.args.get("distinct", False): 861 self.unsupported("INTERSECT with All is not supported in Snowflake") 862 return super().intersect_op(expression) 863 864 def describe_sql(self, expression: exp.Describe) -> str: 865 # Default to table if kind is unknown 866 kind_value = expression.args.get("kind") or "TABLE" 867 kind = f" {kind_value}" if kind_value else "" 868 this = f" {self.sql(expression, 'this')}" 869 expressions = self.expressions(expression, flat=True) 870 expressions = f" {expressions}" if expressions else "" 871 return f"DESCRIBE{kind}{this}{expressions}" 872 873 def generatedasidentitycolumnconstraint_sql( 874 self, expression: exp.GeneratedAsIdentityColumnConstraint 875 ) -> str: 876 start = expression.args.get("start") 877 start = f" START {start}" if start else "" 878 increment = expression.args.get("increment") 879 increment = f" INCREMENT {increment}" if increment else "" 880 return f"AUTOINCREMENT{start}{increment}" 881 882 def swaptable_sql(self, expression: exp.SwapTable) -> str: 883 this = self.sql(expression, "this") 884 return f"SWAP WITH {this}" 885 886 def with_properties(self, properties: exp.Properties) -> str: 887 return self.properties(properties, wrapped=False, prefix=self.seg(""), sep=" ")
Specifies the strategy according to which identifiers should be normalized.
Indicates the default NULL
ordering method to use if not explicitly set.
Possible values: "nulls_are_small"
, "nulls_are_large"
, "nulls_are_last"
Determines whether or not user-defined data types are supported.
Some dialects, such as Snowflake, allow you to reference a CTE column alias in the HAVING clause of the CTE. This flag will cause the CTE alias columns to override any projection aliases in the subquery.
For example, WITH y(c) AS ( SELECT SUM(a) FROM (SELECT 1 a) AS x HAVING c > 0 ) SELECT c FROM y;
will be rewritten as
WITH y(c) AS (
SELECT SUM(a) AS c FROM (SELECT 1 AS a) AS x HAVING c > 0
) SELECT c FROM y;
Determines whether or not a size in the table sample clause represents percentage.
Associates this dialect's time formats with their equivalent Python strftime
format.
372 def quote_identifier(self, expression: E, identify: bool = True) -> E: 373 # This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an 374 # unquoted DUAL keyword in a special way and does not map it to a user-defined table 375 if ( 376 isinstance(expression, exp.Identifier) 377 and isinstance(expression.parent, exp.Table) 378 and expression.name.lower() == "dual" 379 ): 380 return t.cast(E, expression) 381 382 return super().quote_identifier(expression, identify=identify)
Adds quotes to a given identifier.
Arguments:
- expression: The expression of interest. If it's not an
Identifier
, this method is a no-op. - identify: If set to
False
, the quotes will only be added if the identifier is deemed "unsafe", with respect to its characters and this dialect's normalization strategy.
Inherited Members
- sqlglot.dialects.dialect.Dialect
- Dialect
- INDEX_OFFSET
- WEEK_OFFSET
- UNNEST_COLUMN_ONLY
- ALIAS_POST_TABLESAMPLE
- IDENTIFIERS_CAN_START_WITH_DIGIT
- DPIPE_IS_STRING_CONCAT
- STRICT_STRING_CONCAT
- NORMALIZE_FUNCTIONS
- LOG_BASE_FIRST
- TYPED_DIVISION
- SAFE_DIVISION
- CONCAT_COALESCE
- DATE_FORMAT
- DATEINT_FORMAT
- FORMAT_MAPPING
- ESCAPE_SEQUENCES
- PSEUDOCOLUMNS
- get_or_raise
- format_time
- normalize_identifier
- case_sensitive
- can_identify
- parse
- parse_into
- generate
- transpile
- tokenize
- tokenizer
- parser
- generator
384 class Parser(parser.Parser): 385 IDENTIFY_PIVOT_STRINGS = True 386 387 TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.WINDOW} 388 389 FUNCTIONS = { 390 **parser.Parser.FUNCTIONS, 391 "ARRAYAGG": exp.ArrayAgg.from_arg_list, 392 "ARRAY_CONSTRUCT": exp.Array.from_arg_list, 393 "ARRAY_CONTAINS": lambda args: exp.ArrayContains( 394 this=seq_get(args, 1), expression=seq_get(args, 0) 395 ), 396 "ARRAY_GENERATE_RANGE": lambda args: exp.GenerateSeries( 397 # ARRAY_GENERATE_RANGE has an exlusive end; we normalize it to be inclusive 398 start=seq_get(args, 0), 399 end=exp.Sub(this=seq_get(args, 1), expression=exp.Literal.number(1)), 400 step=seq_get(args, 2), 401 ), 402 "ARRAY_TO_STRING": exp.ArrayJoin.from_arg_list, 403 "BITXOR": binary_from_function(exp.BitwiseXor), 404 "BIT_XOR": binary_from_function(exp.BitwiseXor), 405 "BOOLXOR": binary_from_function(exp.Xor), 406 "CONVERT_TIMEZONE": _parse_convert_timezone, 407 "DATE_TRUNC": _date_trunc_to_time, 408 "DATEADD": lambda args: exp.DateAdd( 409 this=seq_get(args, 2), 410 expression=seq_get(args, 1), 411 unit=_map_date_part(seq_get(args, 0)), 412 ), 413 "DATEDIFF": _parse_datediff, 414 "DIV0": _div0_to_if, 415 "FLATTEN": exp.Explode.from_arg_list, 416 "IFF": exp.If.from_arg_list, 417 "LAST_DAY": lambda args: exp.LastDay( 418 this=seq_get(args, 0), unit=_map_date_part(seq_get(args, 1)) 419 ), 420 "LISTAGG": exp.GroupConcat.from_arg_list, 421 "NULLIFZERO": _nullifzero_to_if, 422 "OBJECT_CONSTRUCT": _parse_object_construct, 423 "REGEXP_REPLACE": _parse_regexp_replace, 424 "REGEXP_SUBSTR": exp.RegexpExtract.from_arg_list, 425 "RLIKE": exp.RegexpLike.from_arg_list, 426 "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)), 427 "TIMEDIFF": _parse_datediff, 428 "TIMESTAMPDIFF": _parse_datediff, 429 "TIMESTAMPFROMPARTS": _parse_timestamp_from_parts, 430 "TIMESTAMP_FROM_PARTS": _parse_timestamp_from_parts, 431 "TO_TIMESTAMP": _parse_to_timestamp, 432 "TO_VARCHAR": exp.ToChar.from_arg_list, 433 "ZEROIFNULL": _zeroifnull_to_if, 434 } 435 436 FUNCTION_PARSERS = { 437 **parser.Parser.FUNCTION_PARSERS, 438 "DATE_PART": _parse_date_part, 439 "OBJECT_CONSTRUCT_KEEP_NULL": lambda self: self._parse_json_object(), 440 } 441 FUNCTION_PARSERS.pop("TRIM") 442 443 TIMESTAMPS = parser.Parser.TIMESTAMPS - {TokenType.TIME} 444 445 RANGE_PARSERS = { 446 **parser.Parser.RANGE_PARSERS, 447 TokenType.LIKE_ANY: parser.binary_range_parser(exp.LikeAny), 448 TokenType.ILIKE_ANY: parser.binary_range_parser(exp.ILikeAny), 449 TokenType.COLON: _parse_colon_get_path, 450 } 451 452 ALTER_PARSERS = { 453 **parser.Parser.ALTER_PARSERS, 454 "SET": lambda self: self._parse_set(tag=self._match_text_seq("TAG")), 455 "UNSET": lambda self: self.expression( 456 exp.Set, 457 tag=self._match_text_seq("TAG"), 458 expressions=self._parse_csv(self._parse_id_var), 459 unset=True, 460 ), 461 "SWAP": lambda self: self._parse_alter_table_swap(), 462 } 463 464 STATEMENT_PARSERS = { 465 **parser.Parser.STATEMENT_PARSERS, 466 TokenType.SHOW: lambda self: self._parse_show(), 467 } 468 469 PROPERTY_PARSERS = { 470 **parser.Parser.PROPERTY_PARSERS, 471 "LOCATION": lambda self: self._parse_location(), 472 } 473 474 SHOW_PARSERS = { 475 "PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 476 "TERSE PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 477 "COLUMNS": _show_parser("COLUMNS"), 478 } 479 480 STAGED_FILE_SINGLE_TOKENS = { 481 TokenType.DOT, 482 TokenType.MOD, 483 TokenType.SLASH, 484 } 485 486 FLATTEN_COLUMNS = ["SEQ", "KEY", "PATH", "INDEX", "VALUE", "THIS"] 487 488 def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expression]: 489 if is_map: 490 # Keys are strings in Snowflake's objects, see also: 491 # - https://docs.snowflake.com/en/sql-reference/data-types-semistructured 492 # - https://docs.snowflake.com/en/sql-reference/functions/object_construct 493 return self._parse_slice(self._parse_string()) 494 495 return self._parse_slice(self._parse_alias(self._parse_conjunction(), explicit=True)) 496 497 def _parse_lateral(self) -> t.Optional[exp.Lateral]: 498 lateral = super()._parse_lateral() 499 if not lateral: 500 return lateral 501 502 if isinstance(lateral.this, exp.Explode): 503 table_alias = lateral.args.get("alias") 504 columns = [exp.to_identifier(col) for col in self.FLATTEN_COLUMNS] 505 if table_alias and not table_alias.args.get("columns"): 506 table_alias.set("columns", columns) 507 elif not table_alias: 508 exp.alias_(lateral, "_flattened", table=columns, copy=False) 509 510 return lateral 511 512 def _parse_at_before(self, table: exp.Table) -> exp.Table: 513 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 514 index = self._index 515 if self._match_texts(("AT", "BEFORE")): 516 this = self._prev.text.upper() 517 kind = ( 518 self._match(TokenType.L_PAREN) 519 and self._match_texts(self.HISTORICAL_DATA_KIND) 520 and self._prev.text.upper() 521 ) 522 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 523 524 if expression: 525 self._match_r_paren() 526 when = self.expression( 527 exp.HistoricalData, this=this, kind=kind, expression=expression 528 ) 529 table.set("when", when) 530 else: 531 self._retreat(index) 532 533 return table 534 535 def _parse_table_parts(self, schema: bool = False) -> exp.Table: 536 # https://docs.snowflake.com/en/user-guide/querying-stage 537 if self._match(TokenType.STRING, advance=False): 538 table = self._parse_string() 539 elif self._match_text_seq("@", advance=False): 540 table = self._parse_location_path() 541 else: 542 table = None 543 544 if table: 545 file_format = None 546 pattern = None 547 548 self._match(TokenType.L_PAREN) 549 while self._curr and not self._match(TokenType.R_PAREN): 550 if self._match_text_seq("FILE_FORMAT", "=>"): 551 file_format = self._parse_string() or super()._parse_table_parts() 552 elif self._match_text_seq("PATTERN", "=>"): 553 pattern = self._parse_string() 554 else: 555 break 556 557 self._match(TokenType.COMMA) 558 559 table = self.expression(exp.Table, this=table, format=file_format, pattern=pattern) 560 else: 561 table = super()._parse_table_parts(schema=schema) 562 563 return self._parse_at_before(table) 564 565 def _parse_id_var( 566 self, 567 any_token: bool = True, 568 tokens: t.Optional[t.Collection[TokenType]] = None, 569 ) -> t.Optional[exp.Expression]: 570 if self._match_text_seq("IDENTIFIER", "("): 571 identifier = ( 572 super()._parse_id_var(any_token=any_token, tokens=tokens) 573 or self._parse_string() 574 ) 575 self._match_r_paren() 576 return self.expression(exp.Anonymous, this="IDENTIFIER", expressions=[identifier]) 577 578 return super()._parse_id_var(any_token=any_token, tokens=tokens) 579 580 def _parse_show_snowflake(self, this: str) -> exp.Show: 581 scope = None 582 scope_kind = None 583 584 like = self._parse_string() if self._match(TokenType.LIKE) else None 585 586 if self._match(TokenType.IN): 587 if self._match_text_seq("ACCOUNT"): 588 scope_kind = "ACCOUNT" 589 elif self._match_set(self.DB_CREATABLES): 590 scope_kind = self._prev.text 591 if self._curr: 592 scope = self._parse_table() 593 elif self._curr: 594 scope_kind = "TABLE" 595 scope = self._parse_table() 596 597 return self.expression( 598 exp.Show, this=this, like=like, scope=scope, scope_kind=scope_kind 599 ) 600 601 def _parse_alter_table_swap(self) -> exp.SwapTable: 602 self._match_text_seq("WITH") 603 return self.expression(exp.SwapTable, this=self._parse_table(schema=True)) 604 605 def _parse_location(self) -> exp.LocationProperty: 606 self._match(TokenType.EQ) 607 return self.expression(exp.LocationProperty, this=self._parse_location_path()) 608 609 def _parse_location_path(self) -> exp.Var: 610 parts = [self._advance_any(ignore_reserved=True)] 611 612 # We avoid consuming a comma token because external tables like @foo and @bar 613 # can be joined in a query with a comma separator. 614 while self._is_connected() and not self._match(TokenType.COMMA, advance=False): 615 parts.append(self._advance_any(ignore_reserved=True)) 616 617 return exp.var("".join(part.text for part in parts if part))
Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree.
Arguments:
- error_level: The desired error level. Default: ErrorLevel.IMMEDIATE
- error_message_context: Determines the amount of context to capture from a query string when displaying the error message (in number of characters). Default: 100
- max_errors: Maximum number of error messages to include in a raised ParseError. This is only relevant if error_level is ErrorLevel.RAISE. Default: 3
Inherited Members
- sqlglot.parser.Parser
- Parser
- NO_PAREN_FUNCTIONS
- STRUCT_TYPE_TOKENS
- NESTED_TYPE_TOKENS
- ENUM_TYPE_TOKENS
- TYPE_TOKENS
- SIGNED_TO_UNSIGNED_TYPE_TOKEN
- SUBQUERY_PREDICATES
- RESERVED_TOKENS
- DB_CREATABLES
- CREATABLES
- ID_VAR_TOKENS
- INTERVAL_VARS
- COMMENT_TABLE_ALIAS_TOKENS
- UPDATE_ALIAS_TOKENS
- TRIM_TYPES
- FUNC_TOKENS
- CONJUNCTION
- EQUALITY
- COMPARISON
- BITWISE
- TERM
- FACTOR
- EXPONENT
- TIMES
- SET_OPERATIONS
- JOIN_METHODS
- JOIN_SIDES
- JOIN_KINDS
- JOIN_HINTS
- LAMBDAS
- COLUMN_OPERATORS
- EXPRESSION_PARSERS
- UNARY_PARSERS
- PRIMARY_PARSERS
- PLACEHOLDER_PARSERS
- CONSTRAINT_PARSERS
- SCHEMA_UNNAMED_CONSTRAINTS
- NO_PAREN_FUNCTION_PARSERS
- INVALID_FUNC_NAME_TOKENS
- FUNCTIONS_WITH_ALIASED_ARGS
- QUERY_MODIFIER_PARSERS
- SET_PARSERS
- TYPE_LITERAL_PARSERS
- MODIFIABLES
- DDL_SELECT_TOKENS
- PRE_VOLATILE_TOKENS
- TRANSACTION_KIND
- TRANSACTION_CHARACTERISTICS
- INSERT_ALTERNATIVES
- CLONE_KEYWORDS
- HISTORICAL_DATA_KIND
- OPCLASS_FOLLOW_KEYWORDS
- OPTYPE_FOLLOW_TOKENS
- TABLE_INDEX_HINT_TOKENS
- WINDOW_ALIAS_TOKENS
- WINDOW_BEFORE_PAREN_TOKENS
- WINDOW_SIDES
- JSON_KEY_VALUE_SEPARATOR_TOKENS
- FETCH_TOKENS
- ADD_CONSTRAINT_TOKENS
- DISTINCT_TOKENS
- NULL_TOKENS
- UNNEST_OFFSET_ALIAS_TOKENS
- STRICT_CAST
- PREFIXED_PIVOT_COLUMNS
- LOG_DEFAULTS_TO_LN
- ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
- TABLESAMPLE_CSV
- SET_REQUIRES_ASSIGNMENT_DELIMITER
- TRIM_PATTERN_FIRST
- STRING_ALIASES
- MODIFIERS_ATTACHED_TO_UNION
- UNION_MODIFIERS
- error_level
- error_message_context
- max_errors
- dialect
- reset
- parse
- parse_into
- check_errors
- raise_error
- expression
- validate_expression
- errors
- sql
619 class Tokenizer(tokens.Tokenizer): 620 STRING_ESCAPES = ["\\", "'"] 621 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 622 RAW_STRINGS = ["$$"] 623 COMMENTS = ["--", "//", ("/*", "*/")] 624 625 KEYWORDS = { 626 **tokens.Tokenizer.KEYWORDS, 627 "BYTEINT": TokenType.INT, 628 "CHAR VARYING": TokenType.VARCHAR, 629 "CHARACTER VARYING": TokenType.VARCHAR, 630 "EXCLUDE": TokenType.EXCEPT, 631 "ILIKE ANY": TokenType.ILIKE_ANY, 632 "LIKE ANY": TokenType.LIKE_ANY, 633 "MATCH_RECOGNIZE": TokenType.MATCH_RECOGNIZE, 634 "MINUS": TokenType.EXCEPT, 635 "NCHAR VARYING": TokenType.VARCHAR, 636 "PUT": TokenType.COMMAND, 637 "REMOVE": TokenType.COMMAND, 638 "RENAME": TokenType.REPLACE, 639 "RM": TokenType.COMMAND, 640 "SAMPLE": TokenType.TABLE_SAMPLE, 641 "SQL_DOUBLE": TokenType.DOUBLE, 642 "SQL_VARCHAR": TokenType.VARCHAR, 643 "TIMESTAMP_LTZ": TokenType.TIMESTAMPLTZ, 644 "TIMESTAMP_NTZ": TokenType.TIMESTAMP, 645 "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ, 646 "TIMESTAMPNTZ": TokenType.TIMESTAMP, 647 "TOP": TokenType.TOP, 648 } 649 650 SINGLE_TOKENS = { 651 **tokens.Tokenizer.SINGLE_TOKENS, 652 "$": TokenType.PARAMETER, 653 } 654 655 VAR_SINGLE_TOKENS = {"$"} 656 657 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW}
659 class Generator(generator.Generator): 660 PARAMETER_TOKEN = "$" 661 MATCHED_BY_SOURCE = False 662 SINGLE_STRING_INTERVAL = True 663 JOIN_HINTS = False 664 TABLE_HINTS = False 665 QUERY_HINTS = False 666 AGGREGATE_FILTER_SUPPORTED = False 667 SUPPORTS_TABLE_COPY = False 668 COLLATE_IS_FUNC = True 669 LIMIT_ONLY_LITERALS = True 670 JSON_KEY_VALUE_PAIR_SEP = "," 671 INSERT_OVERWRITE = " OVERWRITE INTO" 672 673 TRANSFORMS = { 674 **generator.Generator.TRANSFORMS, 675 exp.ArgMax: rename_func("MAX_BY"), 676 exp.ArgMin: rename_func("MIN_BY"), 677 exp.Array: inline_array_sql, 678 exp.ArrayConcat: rename_func("ARRAY_CAT"), 679 exp.ArrayContains: lambda self, e: self.func("ARRAY_CONTAINS", e.expression, e.this), 680 exp.ArrayJoin: rename_func("ARRAY_TO_STRING"), 681 exp.AtTimeZone: lambda self, e: self.func( 682 "CONVERT_TIMEZONE", e.args.get("zone"), e.this 683 ), 684 exp.BitwiseXor: rename_func("BITXOR"), 685 exp.DateAdd: date_delta_sql("DATEADD"), 686 exp.DateDiff: date_delta_sql("DATEDIFF"), 687 exp.DateStrToDate: datestrtodate_sql, 688 exp.DataType: _datatype_sql, 689 exp.DayOfMonth: rename_func("DAYOFMONTH"), 690 exp.DayOfWeek: rename_func("DAYOFWEEK"), 691 exp.DayOfYear: rename_func("DAYOFYEAR"), 692 exp.Explode: rename_func("FLATTEN"), 693 exp.Extract: rename_func("DATE_PART"), 694 exp.GenerateSeries: lambda self, e: self.func( 695 "ARRAY_GENERATE_RANGE", e.args["start"], e.args["end"] + 1, e.args.get("step") 696 ), 697 exp.GroupConcat: rename_func("LISTAGG"), 698 exp.If: if_sql(name="IFF", false_value="NULL"), 699 exp.JSONExtract: lambda self, e: f"{self.sql(e, 'this')}[{self.sql(e, 'expression')}]", 700 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 701 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 702 exp.LogicalOr: rename_func("BOOLOR_AGG"), 703 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 704 exp.Max: max_or_greatest, 705 exp.Min: min_or_least, 706 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 707 exp.PercentileCont: transforms.preprocess( 708 [transforms.add_within_group_for_percentiles] 709 ), 710 exp.PercentileDisc: transforms.preprocess( 711 [transforms.add_within_group_for_percentiles] 712 ), 713 exp.Pivot: transforms.preprocess([_unqualify_unpivot_columns]), 714 exp.RegexpILike: _regexpilike_sql, 715 exp.Rand: rename_func("RANDOM"), 716 exp.Select: transforms.preprocess( 717 [ 718 transforms.eliminate_distinct_on, 719 transforms.explode_to_unnest(), 720 transforms.eliminate_semi_and_anti_joins, 721 ] 722 ), 723 exp.SHA: rename_func("SHA1"), 724 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 725 exp.StartsWith: rename_func("STARTSWITH"), 726 exp.StrPosition: lambda self, e: self.func( 727 "POSITION", e.args.get("substr"), e.this, e.args.get("position") 728 ), 729 exp.StrToTime: lambda self, e: f"TO_TIMESTAMP({self.sql(e, 'this')}, {self.format_time(e)})", 730 exp.Struct: lambda self, e: self.func( 731 "OBJECT_CONSTRUCT", 732 *(arg for expression in e.expressions for arg in expression.flatten()), 733 ), 734 exp.Stuff: rename_func("INSERT"), 735 exp.TimestampDiff: lambda self, e: self.func( 736 "TIMESTAMPDIFF", e.unit, e.expression, e.this 737 ), 738 exp.TimestampTrunc: timestamptrunc_sql, 739 exp.TimeStrToTime: timestrtotime_sql, 740 exp.TimeToStr: lambda self, e: self.func( 741 "TO_CHAR", exp.cast(e.this, "timestamp"), self.format_time(e) 742 ), 743 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 744 exp.ToArray: rename_func("TO_ARRAY"), 745 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 746 exp.Trim: lambda self, e: self.func("TRIM", e.this, e.expression), 747 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 748 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 749 exp.UnixToTime: rename_func("TO_TIMESTAMP"), 750 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 751 exp.WeekOfYear: rename_func("WEEKOFYEAR"), 752 exp.Xor: rename_func("BOOLXOR"), 753 } 754 755 TYPE_MAPPING = { 756 **generator.Generator.TYPE_MAPPING, 757 exp.DataType.Type.TIMESTAMP: "TIMESTAMPNTZ", 758 } 759 760 STAR_MAPPING = { 761 "except": "EXCLUDE", 762 "replace": "RENAME", 763 } 764 765 PROPERTIES_LOCATION = { 766 **generator.Generator.PROPERTIES_LOCATION, 767 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 768 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 769 } 770 771 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 772 milli = expression.args.get("milli") 773 if milli is not None: 774 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 775 expression.set("nano", milli_to_nano) 776 777 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 778 779 def trycast_sql(self, expression: exp.TryCast) -> str: 780 value = expression.this 781 782 if value.type is None: 783 from sqlglot.optimizer.annotate_types import annotate_types 784 785 value = annotate_types(value) 786 787 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 788 return super().trycast_sql(expression) 789 790 # TRY_CAST only works for string values in Snowflake 791 return self.cast_sql(expression) 792 793 def log_sql(self, expression: exp.Log) -> str: 794 if not expression.expression: 795 return self.func("LN", expression.this) 796 797 return super().log_sql(expression) 798 799 def unnest_sql(self, expression: exp.Unnest) -> str: 800 unnest_alias = expression.args.get("alias") 801 offset = expression.args.get("offset") 802 803 columns = [ 804 exp.to_identifier("seq"), 805 exp.to_identifier("key"), 806 exp.to_identifier("path"), 807 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 808 seq_get(unnest_alias.columns if unnest_alias else [], 0) 809 or exp.to_identifier("value"), 810 exp.to_identifier("this"), 811 ] 812 813 if unnest_alias: 814 unnest_alias.set("columns", columns) 815 else: 816 unnest_alias = exp.TableAlias(this="_u", columns=columns) 817 818 explode = f"TABLE(FLATTEN(INPUT => {self.sql(expression.expressions[0])}))" 819 alias = self.sql(unnest_alias) 820 alias = f" AS {alias}" if alias else "" 821 return f"{explode}{alias}" 822 823 def show_sql(self, expression: exp.Show) -> str: 824 like = self.sql(expression, "like") 825 like = f" LIKE {like}" if like else "" 826 827 scope = self.sql(expression, "scope") 828 scope = f" {scope}" if scope else "" 829 830 scope_kind = self.sql(expression, "scope_kind") 831 if scope_kind: 832 scope_kind = f" IN {scope_kind}" 833 834 return f"SHOW {expression.name}{like}{scope_kind}{scope}" 835 836 def regexpextract_sql(self, expression: exp.RegexpExtract) -> str: 837 # Other dialects don't support all of the following parameters, so we need to 838 # generate default values as necessary to ensure the transpilation is correct 839 group = expression.args.get("group") 840 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 841 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 842 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 843 844 return self.func( 845 "REGEXP_SUBSTR", 846 expression.this, 847 expression.expression, 848 position, 849 occurrence, 850 parameters, 851 group, 852 ) 853 854 def except_op(self, expression: exp.Except) -> str: 855 if not expression.args.get("distinct", False): 856 self.unsupported("EXCEPT with All is not supported in Snowflake") 857 return super().except_op(expression) 858 859 def intersect_op(self, expression: exp.Intersect) -> str: 860 if not expression.args.get("distinct", False): 861 self.unsupported("INTERSECT with All is not supported in Snowflake") 862 return super().intersect_op(expression) 863 864 def describe_sql(self, expression: exp.Describe) -> str: 865 # Default to table if kind is unknown 866 kind_value = expression.args.get("kind") or "TABLE" 867 kind = f" {kind_value}" if kind_value else "" 868 this = f" {self.sql(expression, 'this')}" 869 expressions = self.expressions(expression, flat=True) 870 expressions = f" {expressions}" if expressions else "" 871 return f"DESCRIBE{kind}{this}{expressions}" 872 873 def generatedasidentitycolumnconstraint_sql( 874 self, expression: exp.GeneratedAsIdentityColumnConstraint 875 ) -> str: 876 start = expression.args.get("start") 877 start = f" START {start}" if start else "" 878 increment = expression.args.get("increment") 879 increment = f" INCREMENT {increment}" if increment else "" 880 return f"AUTOINCREMENT{start}{increment}" 881 882 def swaptable_sql(self, expression: exp.SwapTable) -> str: 883 this = self.sql(expression, "this") 884 return f"SWAP WITH {this}" 885 886 def with_properties(self, properties: exp.Properties) -> str: 887 return self.properties(properties, wrapped=False, prefix=self.seg(""), sep=" ")
Generator converts a given syntax tree to the corresponding SQL string.
Arguments:
- pretty: Whether or not 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 or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether or not to normalize identifiers to lowercase. Default: False.
- pad: Determines the pad size in a formatted string. Default: 2.
- indent: Determines the indentation size in a formatted string. Default: 2.
- normalize_functions: Whether or not to normalize all 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: Determines whether or not 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 or not to preserve comments in the output SQL code. Default: True
771 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 772 milli = expression.args.get("milli") 773 if milli is not None: 774 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 775 expression.set("nano", milli_to_nano) 776 777 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression)
779 def trycast_sql(self, expression: exp.TryCast) -> str: 780 value = expression.this 781 782 if value.type is None: 783 from sqlglot.optimizer.annotate_types import annotate_types 784 785 value = annotate_types(value) 786 787 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 788 return super().trycast_sql(expression) 789 790 # TRY_CAST only works for string values in Snowflake 791 return self.cast_sql(expression)
799 def unnest_sql(self, expression: exp.Unnest) -> str: 800 unnest_alias = expression.args.get("alias") 801 offset = expression.args.get("offset") 802 803 columns = [ 804 exp.to_identifier("seq"), 805 exp.to_identifier("key"), 806 exp.to_identifier("path"), 807 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 808 seq_get(unnest_alias.columns if unnest_alias else [], 0) 809 or exp.to_identifier("value"), 810 exp.to_identifier("this"), 811 ] 812 813 if unnest_alias: 814 unnest_alias.set("columns", columns) 815 else: 816 unnest_alias = exp.TableAlias(this="_u", columns=columns) 817 818 explode = f"TABLE(FLATTEN(INPUT => {self.sql(expression.expressions[0])}))" 819 alias = self.sql(unnest_alias) 820 alias = f" AS {alias}" if alias else "" 821 return f"{explode}{alias}"
823 def show_sql(self, expression: exp.Show) -> str: 824 like = self.sql(expression, "like") 825 like = f" LIKE {like}" if like else "" 826 827 scope = self.sql(expression, "scope") 828 scope = f" {scope}" if scope else "" 829 830 scope_kind = self.sql(expression, "scope_kind") 831 if scope_kind: 832 scope_kind = f" IN {scope_kind}" 833 834 return f"SHOW {expression.name}{like}{scope_kind}{scope}"
836 def regexpextract_sql(self, expression: exp.RegexpExtract) -> str: 837 # Other dialects don't support all of the following parameters, so we need to 838 # generate default values as necessary to ensure the transpilation is correct 839 group = expression.args.get("group") 840 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 841 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 842 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 843 844 return self.func( 845 "REGEXP_SUBSTR", 846 expression.this, 847 expression.expression, 848 position, 849 occurrence, 850 parameters, 851 group, 852 )
864 def describe_sql(self, expression: exp.Describe) -> str: 865 # Default to table if kind is unknown 866 kind_value = expression.args.get("kind") or "TABLE" 867 kind = f" {kind_value}" if kind_value else "" 868 this = f" {self.sql(expression, 'this')}" 869 expressions = self.expressions(expression, flat=True) 870 expressions = f" {expressions}" if expressions else "" 871 return f"DESCRIBE{kind}{this}{expressions}"
873 def generatedasidentitycolumnconstraint_sql( 874 self, expression: exp.GeneratedAsIdentityColumnConstraint 875 ) -> str: 876 start = expression.args.get("start") 877 start = f" START {start}" if start else "" 878 increment = expression.args.get("increment") 879 increment = f" INCREMENT {increment}" if increment else "" 880 return f"AUTOINCREMENT{start}{increment}"
Inherited Members
- sqlglot.generator.Generator
- Generator
- NULL_ORDERING_SUPPORTED
- LOCKING_READS_SUPPORTED
- EXPLICIT_UNION
- WRAP_DERIVED_VALUES
- CREATE_FUNCTION_RETURN_AS
- INTERVAL_ALLOWS_PLURAL_FORM
- LIMIT_FETCH
- RENAME_TABLE_WITH_DB
- GROUPINGS_SEP
- INDEX_ON
- QUERY_HINT_SEP
- IS_BOOL_ALLOWED
- DUPLICATE_KEY_UPDATE_WITH_SET
- LIMIT_IS_TOP
- RETURNING_END
- COLUMN_JOIN_MARKS_SUPPORTED
- EXTRACT_ALLOWS_QUOTES
- TZ_TO_WITH_TIME_ZONE
- NVL2_SUPPORTED
- VALUES_AS_TABLE
- ALTER_TABLE_INCLUDE_COLUMN_KEYWORD
- UNNEST_WITH_ORDINALITY
- SEMI_ANTI_JOIN_WITH_SIDE
- COMPUTED_COLUMN_WITH_TYPE
- TABLESAMPLE_REQUIRES_PARENS
- TABLESAMPLE_SIZE_IS_ROWS
- TABLESAMPLE_KEYWORDS
- TABLESAMPLE_WITH_METHOD
- TABLESAMPLE_SEED_KEYWORD
- DATA_TYPE_SPECIFIERS_ALLOWED
- ENSURE_BOOLS
- CTE_RECURSIVE_KEYWORD_REQUIRED
- SUPPORTS_SINGLE_ARG_CONCAT
- LAST_DAY_SUPPORTS_DATE_PART
- SUPPORTS_TABLE_ALIAS_COLUMNS
- UNPIVOT_ALIASES_ARE_IDENTIFIERS
- SUPPORTS_SELECT_INTO
- SUPPORTS_UNLOGGED_TABLES
- TIME_PART_SINGULARS
- TOKEN_MAPPING
- STRUCT_DELIMITER
- RESERVED_KEYWORDS
- WITH_SEPARATED_COMMENTS
- EXCLUDE_COMMENTS
- UNWRAPPED_INTERVAL_VALUES
- EXPRESSIONS_WITHOUT_NESTED_CTES
- KEY_VALUE_DEFINITIONS
- 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
- pad_comment
- maybe_comment
- wrap
- no_identify
- normalize_func
- indent
- sql
- uncache_sql
- cache_sql
- characterset_sql
- column_sql
- columnposition_sql
- columndef_sql
- columnconstraint_sql
- computedcolumnconstraint_sql
- autoincrementcolumnconstraint_sql
- compresscolumnconstraint_sql
- generatedasrowcolumnconstraint_sql
- periodforsystemtimeconstraint_sql
- notnullcolumnconstraint_sql
- transformcolumnconstraint_sql
- primarykeycolumnconstraint_sql
- uniquecolumnconstraint_sql
- createable_sql
- create_sql
- clone_sql
- heredoc_sql
- prepend_ctes
- with_sql
- cte_sql
- tablealias_sql
- bitstring_sql
- hexstring_sql
- bytestring_sql
- unicodestring_sql
- rawstring_sql
- datatypeparam_sql
- datatype_sql
- directory_sql
- delete_sql
- drop_sql
- except_sql
- fetch_sql
- filter_sql
- hint_sql
- index_sql
- identifier_sql
- inputoutputformat_sql
- national_sql
- partition_sql
- properties_sql
- root_properties
- properties
- locate_properties
- property_name
- property_sql
- likeproperty_sql
- fallbackproperty_sql
- journalproperty_sql
- freespaceproperty_sql
- checksumproperty_sql
- mergeblockratioproperty_sql
- datablocksizeproperty_sql
- blockcompressionproperty_sql
- isolatedloadingproperty_sql
- partitionboundspec_sql
- partitionedofproperty_sql
- lockingproperty_sql
- withdataproperty_sql
- withsystemversioningproperty_sql
- insert_sql
- intersect_sql
- introducer_sql
- kill_sql
- pseudotype_sql
- objectidentifier_sql
- onconflict_sql
- returning_sql
- rowformatdelimitedproperty_sql
- withtablehint_sql
- indextablehint_sql
- historicaldata_sql
- table_sql
- tablesample_sql
- pivot_sql
- version_sql
- tuple_sql
- update_sql
- values_sql
- var_sql
- into_sql
- from_sql
- group_sql
- having_sql
- connect_sql
- prior_sql
- join_sql
- lambda_sql
- lateral_op
- lateral_sql
- limit_sql
- offset_sql
- setitem_sql
- set_sql
- pragma_sql
- lock_sql
- literal_sql
- escape_str
- loaddata_sql
- null_sql
- boolean_sql
- order_sql
- withfill_sql
- cluster_sql
- distribute_sql
- sort_sql
- ordered_sql
- matchrecognize_sql
- query_modifiers
- offset_limit_modifiers
- after_having_modifiers
- after_limit_modifiers
- select_sql
- schema_sql
- schema_columns_sql
- star_sql
- parameter_sql
- sessionparameter_sql
- placeholder_sql
- subquery_sql
- qualify_sql
- union_sql
- union_op
- where_sql
- window_sql
- partition_by_sql
- windowspec_sql
- withingroup_sql
- between_sql
- bracket_sql
- all_sql
- any_sql
- exists_sql
- case_sql
- constraint_sql
- nextvaluefor_sql
- extract_sql
- trim_sql
- convert_concat_args
- concat_sql
- concatws_sql
- check_sql
- foreignkey_sql
- primarykey_sql
- if_sql
- matchagainst_sql
- jsonkeyvalue_sql
- formatjson_sql
- jsonobject_sql
- jsonobjectagg_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
- add_sql
- and_sql
- xor_sql
- connector_sql
- bitwiseand_sql
- bitwiseleftshift_sql
- bitwisenot_sql
- bitwiseor_sql
- bitwiserightshift_sql
- bitwisexor_sql
- cast_sql
- currentdate_sql
- collate_sql
- command_sql
- comment_sql
- mergetreettlaction_sql
- mergetreettl_sql
- transaction_sql
- commit_sql
- rollback_sql
- altercolumn_sql
- renametable_sql
- altertable_sql
- add_column_sql
- droppartition_sql
- addconstraint_sql
- distinct_sql
- ignorenulls_sql
- respectnulls_sql
- intdiv_sql
- dpipe_sql
- div_sql
- overlaps_sql
- distance_sql
- dot_sql
- eq_sql
- propertyeq_sql
- escape_sql
- glob_sql
- gt_sql
- gte_sql
- ilike_sql
- ilikeany_sql
- is_sql
- like_sql
- likeany_sql
- similarto_sql
- lt_sql
- lte_sql
- mod_sql
- mul_sql
- neq_sql
- nullsafeeq_sql
- nullsafeneq_sql
- or_sql
- slice_sql
- sub_sql
- use_sql
- binary
- function_fallback_sql
- func
- format_args
- text_width
- format_time
- expressions
- op_expressions
- naked_property
- set_operation
- tag_sql
- token_sql
- userdefinedfunction_sql
- joinhint_sql
- kwarg_sql
- when_sql
- merge_sql
- tochar_sql
- dictproperty_sql
- dictrange_sql
- dictsubproperty_sql
- oncluster_sql
- clusteredbyproperty_sql
- anyvalue_sql
- querytransform_sql
- indexconstraintoption_sql
- indexcolumnconstraint_sql
- nvl2_sql
- comprehension_sql
- columnprefix_sql
- opclass_sql
- predict_sql
- forin_sql
- refresh_sql
- operator_sql
- toarray_sql
- tsordstotime_sql
- tsordstodate_sql
- unixdate_sql
- lastday_sql