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 json_keyvalue_comma_sql, 18 max_or_greatest, 19 min_or_least, 20 rename_func, 21 timestamptrunc_sql, 22 timestrtotime_sql, 23 var_map_sql, 24) 25from sqlglot.expressions import Literal 26from sqlglot.helper import seq_get 27from sqlglot.tokens import TokenType 28 29 30def _check_int(s: str) -> bool: 31 if s[0] in ("-", "+"): 32 return s[1:].isdigit() 33 return s.isdigit() 34 35 36# from https://docs.snowflake.com/en/sql-reference/functions/to_timestamp.html 37def _parse_to_timestamp(args: t.List) -> t.Union[exp.StrToTime, exp.UnixToTime, exp.TimeStrToTime]: 38 if len(args) == 2: 39 first_arg, second_arg = args 40 if second_arg.is_string: 41 # case: <string_expr> [ , <format> ] 42 return format_time_lambda(exp.StrToTime, "snowflake")(args) 43 44 # case: <numeric_expr> [ , <scale> ] 45 if second_arg.name not in ["0", "3", "9"]: 46 raise ValueError( 47 f"Scale for snowflake numeric timestamp is {second_arg}, but should be 0, 3, or 9" 48 ) 49 50 if second_arg.name == "0": 51 timescale = exp.UnixToTime.SECONDS 52 elif second_arg.name == "3": 53 timescale = exp.UnixToTime.MILLIS 54 elif second_arg.name == "9": 55 timescale = exp.UnixToTime.NANOS 56 57 return exp.UnixToTime(this=first_arg, scale=timescale) 58 59 from sqlglot.optimizer.simplify import simplify_literals 60 61 # The first argument might be an expression like 40 * 365 * 86400, so we try to 62 # reduce it using `simplify_literals` first and then check if it's a Literal. 63 first_arg = seq_get(args, 0) 64 if not isinstance(simplify_literals(first_arg, root=True), Literal): 65 # case: <variant_expr> or other expressions such as columns 66 return exp.TimeStrToTime.from_arg_list(args) 67 68 if first_arg.is_string: 69 if _check_int(first_arg.this): 70 # case: <integer> 71 return exp.UnixToTime.from_arg_list(args) 72 73 # case: <date_expr> 74 return format_time_lambda(exp.StrToTime, "snowflake", default=True)(args) 75 76 # case: <numeric_expr> 77 return exp.UnixToTime.from_arg_list(args) 78 79 80def _parse_object_construct(args: t.List) -> t.Union[exp.StarMap, exp.Struct]: 81 expression = parser.parse_var_map(args) 82 83 if isinstance(expression, exp.StarMap): 84 return expression 85 86 return exp.Struct( 87 expressions=[ 88 t.cast(exp.Condition, k).eq(v) for k, v in zip(expression.keys, expression.values) 89 ] 90 ) 91 92 93def _parse_datediff(args: t.List) -> exp.DateDiff: 94 return exp.DateDiff( 95 this=seq_get(args, 2), expression=seq_get(args, 1), unit=_map_date_part(seq_get(args, 0)) 96 ) 97 98 99def _unix_to_time_sql(self: Snowflake.Generator, expression: exp.UnixToTime) -> str: 100 scale = expression.args.get("scale") 101 timestamp = self.sql(expression, "this") 102 if scale in (None, exp.UnixToTime.SECONDS): 103 return f"TO_TIMESTAMP({timestamp})" 104 if scale == exp.UnixToTime.MILLIS: 105 return f"TO_TIMESTAMP({timestamp}, 3)" 106 if scale == exp.UnixToTime.MICROS: 107 return f"TO_TIMESTAMP({timestamp} / 1000, 3)" 108 if scale == exp.UnixToTime.NANOS: 109 return f"TO_TIMESTAMP({timestamp}, 9)" 110 111 self.unsupported(f"Unsupported scale for timestamp: {scale}.") 112 return "" 113 114 115# https://docs.snowflake.com/en/sql-reference/functions/date_part.html 116# https://docs.snowflake.com/en/sql-reference/functions-date-time.html#label-supported-date-time-parts 117def _parse_date_part(self: Snowflake.Parser) -> t.Optional[exp.Expression]: 118 this = self._parse_var() or self._parse_type() 119 120 if not this: 121 return None 122 123 self._match(TokenType.COMMA) 124 expression = self._parse_bitwise() 125 this = _map_date_part(this) 126 name = this.name.upper() 127 128 if name.startswith("EPOCH"): 129 if name == "EPOCH_MILLISECOND": 130 scale = 10**3 131 elif name == "EPOCH_MICROSECOND": 132 scale = 10**6 133 elif name == "EPOCH_NANOSECOND": 134 scale = 10**9 135 else: 136 scale = None 137 138 ts = self.expression(exp.Cast, this=expression, to=exp.DataType.build("TIMESTAMP")) 139 to_unix: exp.Expression = self.expression(exp.TimeToUnix, this=ts) 140 141 if scale: 142 to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale)) 143 144 return to_unix 145 146 return self.expression(exp.Extract, this=this, expression=expression) 147 148 149# https://docs.snowflake.com/en/sql-reference/functions/div0 150def _div0_to_if(args: t.List) -> exp.If: 151 cond = exp.EQ(this=seq_get(args, 1), expression=exp.Literal.number(0)) 152 true = exp.Literal.number(0) 153 false = exp.Div(this=seq_get(args, 0), expression=seq_get(args, 1)) 154 return exp.If(this=cond, true=true, false=false) 155 156 157# https://docs.snowflake.com/en/sql-reference/functions/zeroifnull 158def _zeroifnull_to_if(args: t.List) -> exp.If: 159 cond = exp.Is(this=seq_get(args, 0), expression=exp.Null()) 160 return exp.If(this=cond, true=exp.Literal.number(0), false=seq_get(args, 0)) 161 162 163# https://docs.snowflake.com/en/sql-reference/functions/zeroifnull 164def _nullifzero_to_if(args: t.List) -> exp.If: 165 cond = exp.EQ(this=seq_get(args, 0), expression=exp.Literal.number(0)) 166 return exp.If(this=cond, true=exp.Null(), false=seq_get(args, 0)) 167 168 169def _datatype_sql(self: Snowflake.Generator, expression: exp.DataType) -> str: 170 if expression.is_type("array"): 171 return "ARRAY" 172 elif expression.is_type("map"): 173 return "OBJECT" 174 return self.datatype_sql(expression) 175 176 177def _regexpilike_sql(self: Snowflake.Generator, expression: exp.RegexpILike) -> str: 178 flag = expression.text("flag") 179 180 if "i" not in flag: 181 flag += "i" 182 183 return self.func( 184 "REGEXP_LIKE", expression.this, expression.expression, exp.Literal.string(flag) 185 ) 186 187 188def _parse_convert_timezone(args: t.List) -> t.Union[exp.Anonymous, exp.AtTimeZone]: 189 if len(args) == 3: 190 return exp.Anonymous(this="CONVERT_TIMEZONE", expressions=args) 191 return exp.AtTimeZone(this=seq_get(args, 1), zone=seq_get(args, 0)) 192 193 194def _parse_regexp_replace(args: t.List) -> exp.RegexpReplace: 195 regexp_replace = exp.RegexpReplace.from_arg_list(args) 196 197 if not regexp_replace.args.get("replacement"): 198 regexp_replace.set("replacement", exp.Literal.string("")) 199 200 return regexp_replace 201 202 203def _show_parser(*args: t.Any, **kwargs: t.Any) -> t.Callable[[Snowflake.Parser], exp.Show]: 204 def _parse(self: Snowflake.Parser) -> exp.Show: 205 return self._parse_show_snowflake(*args, **kwargs) 206 207 return _parse 208 209 210DATE_PART_MAPPING = { 211 "Y": "YEAR", 212 "YY": "YEAR", 213 "YYY": "YEAR", 214 "YYYY": "YEAR", 215 "YR": "YEAR", 216 "YEARS": "YEAR", 217 "YRS": "YEAR", 218 "MM": "MONTH", 219 "MON": "MONTH", 220 "MONS": "MONTH", 221 "MONTHS": "MONTH", 222 "D": "DAY", 223 "DD": "DAY", 224 "DAYS": "DAY", 225 "DAYOFMONTH": "DAY", 226 "WEEKDAY": "DAYOFWEEK", 227 "DOW": "DAYOFWEEK", 228 "DW": "DAYOFWEEK", 229 "WEEKDAY_ISO": "DAYOFWEEKISO", 230 "DOW_ISO": "DAYOFWEEKISO", 231 "DW_ISO": "DAYOFWEEKISO", 232 "YEARDAY": "DAYOFYEAR", 233 "DOY": "DAYOFYEAR", 234 "DY": "DAYOFYEAR", 235 "W": "WEEK", 236 "WK": "WEEK", 237 "WEEKOFYEAR": "WEEK", 238 "WOY": "WEEK", 239 "WY": "WEEK", 240 "WEEK_ISO": "WEEKISO", 241 "WEEKOFYEARISO": "WEEKISO", 242 "WEEKOFYEAR_ISO": "WEEKISO", 243 "Q": "QUARTER", 244 "QTR": "QUARTER", 245 "QTRS": "QUARTER", 246 "QUARTERS": "QUARTER", 247 "H": "HOUR", 248 "HH": "HOUR", 249 "HR": "HOUR", 250 "HOURS": "HOUR", 251 "HRS": "HOUR", 252 "M": "MINUTE", 253 "MI": "MINUTE", 254 "MIN": "MINUTE", 255 "MINUTES": "MINUTE", 256 "MINS": "MINUTE", 257 "S": "SECOND", 258 "SEC": "SECOND", 259 "SECONDS": "SECOND", 260 "SECS": "SECOND", 261 "MS": "MILLISECOND", 262 "MSEC": "MILLISECOND", 263 "MILLISECONDS": "MILLISECOND", 264 "US": "MICROSECOND", 265 "USEC": "MICROSECOND", 266 "MICROSECONDS": "MICROSECOND", 267 "NS": "NANOSECOND", 268 "NSEC": "NANOSECOND", 269 "NANOSEC": "NANOSECOND", 270 "NSECOND": "NANOSECOND", 271 "NSECONDS": "NANOSECOND", 272 "NANOSECS": "NANOSECOND", 273 "NSECONDS": "NANOSECOND", 274 "EPOCH": "EPOCH_SECOND", 275 "EPOCH_SECONDS": "EPOCH_SECOND", 276 "EPOCH_MILLISECONDS": "EPOCH_MILLISECOND", 277 "EPOCH_MICROSECONDS": "EPOCH_MICROSECOND", 278 "EPOCH_NANOSECONDS": "EPOCH_NANOSECOND", 279 "TZH": "TIMEZONE_HOUR", 280 "TZM": "TIMEZONE_MINUTE", 281} 282 283 284@t.overload 285def _map_date_part(part: exp.Expression) -> exp.Var: 286 pass 287 288 289@t.overload 290def _map_date_part(part: t.Optional[exp.Expression]) -> t.Optional[exp.Expression]: 291 pass 292 293 294def _map_date_part(part): 295 mapped = DATE_PART_MAPPING.get(part.name.upper()) if part else None 296 return exp.var(mapped) if mapped else part 297 298 299def _date_trunc_to_time(args: t.List) -> exp.DateTrunc | exp.TimestampTrunc: 300 trunc = date_trunc_to_time(args) 301 trunc.set("unit", _map_date_part(trunc.args["unit"])) 302 return trunc 303 304 305def _parse_colon_get_path( 306 self: parser.Parser, this: t.Optional[exp.Expression] 307) -> t.Optional[exp.Expression]: 308 while True: 309 path = self._parse_bitwise() 310 311 # The cast :: operator has a lower precedence than the extraction operator :, so 312 # we rearrange the AST appropriately to avoid casting the 2nd argument of GET_PATH 313 if isinstance(path, exp.Cast): 314 target_type = path.to 315 path = path.this 316 else: 317 target_type = None 318 319 if isinstance(path, exp.Expression): 320 path = exp.Literal.string(path.sql(dialect="snowflake")) 321 322 # The extraction operator : is left-associative 323 this = self.expression(exp.GetPath, this=this, expression=path) 324 325 if target_type: 326 this = exp.cast(this, target_type) 327 328 if not self._match(TokenType.COLON): 329 break 330 331 return this 332 333 334def _parse_timestamp_from_parts(args: t.List) -> exp.Func: 335 if len(args) == 2: 336 # Other dialects don't have the TIMESTAMP_FROM_PARTS(date, time) concept, 337 # so we parse this into Anonymous for now instead of introducing complexity 338 return exp.Anonymous(this="TIMESTAMP_FROM_PARTS", expressions=args) 339 340 return exp.TimestampFromParts.from_arg_list(args) 341 342 343def _unqualify_unpivot_columns(expression: exp.Expression) -> exp.Expression: 344 """ 345 Snowflake doesn't allow columns referenced in UNPIVOT to be qualified, 346 so we need to unqualify them. 347 348 Example: 349 >>> from sqlglot import parse_one 350 >>> expr = parse_one("SELECT * FROM m_sales UNPIVOT(sales FOR month IN (m_sales.jan, feb, mar, april))") 351 >>> print(_unqualify_unpivot_columns(expr).sql(dialect="snowflake")) 352 SELECT * FROM m_sales UNPIVOT(sales FOR month IN (jan, feb, mar, april)) 353 """ 354 if isinstance(expression, exp.Pivot) and expression.unpivot: 355 expression = transforms.unqualify_columns(expression) 356 357 return expression 358 359 360class Snowflake(Dialect): 361 # https://docs.snowflake.com/en/sql-reference/identifiers-syntax 362 NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE 363 NULL_ORDERING = "nulls_are_large" 364 TIME_FORMAT = "'YYYY-MM-DD HH24:MI:SS'" 365 SUPPORTS_USER_DEFINED_TYPES = False 366 SUPPORTS_SEMI_ANTI_JOIN = False 367 PREFER_CTE_ALIAS_COLUMN = True 368 TABLESAMPLE_SIZE_IS_PERCENT = True 369 370 TIME_MAPPING = { 371 "YYYY": "%Y", 372 "yyyy": "%Y", 373 "YY": "%y", 374 "yy": "%y", 375 "MMMM": "%B", 376 "mmmm": "%B", 377 "MON": "%b", 378 "mon": "%b", 379 "MM": "%m", 380 "mm": "%m", 381 "DD": "%d", 382 "dd": "%-d", 383 "DY": "%a", 384 "dy": "%w", 385 "HH24": "%H", 386 "hh24": "%H", 387 "HH12": "%I", 388 "hh12": "%I", 389 "MI": "%M", 390 "mi": "%M", 391 "SS": "%S", 392 "ss": "%S", 393 "FF": "%f", 394 "ff": "%f", 395 "FF6": "%f", 396 "ff6": "%f", 397 } 398 399 def quote_identifier(self, expression: E, identify: bool = True) -> E: 400 # This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an 401 # unquoted DUAL keyword in a special way and does not map it to a user-defined table 402 if ( 403 isinstance(expression, exp.Identifier) 404 and isinstance(expression.parent, exp.Table) 405 and expression.name.lower() == "dual" 406 ): 407 return t.cast(E, expression) 408 409 return super().quote_identifier(expression, identify=identify) 410 411 class Parser(parser.Parser): 412 IDENTIFY_PIVOT_STRINGS = True 413 414 TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.WINDOW} 415 416 FUNCTIONS = { 417 **parser.Parser.FUNCTIONS, 418 "ARRAYAGG": exp.ArrayAgg.from_arg_list, 419 "ARRAY_CONSTRUCT": exp.Array.from_arg_list, 420 "ARRAY_CONTAINS": lambda args: exp.ArrayContains( 421 this=seq_get(args, 1), expression=seq_get(args, 0) 422 ), 423 "ARRAY_GENERATE_RANGE": lambda args: exp.GenerateSeries( 424 # ARRAY_GENERATE_RANGE has an exlusive end; we normalize it to be inclusive 425 start=seq_get(args, 0), 426 end=exp.Sub(this=seq_get(args, 1), expression=exp.Literal.number(1)), 427 step=seq_get(args, 2), 428 ), 429 "ARRAY_TO_STRING": exp.ArrayJoin.from_arg_list, 430 "BITXOR": binary_from_function(exp.BitwiseXor), 431 "BIT_XOR": binary_from_function(exp.BitwiseXor), 432 "BOOLXOR": binary_from_function(exp.Xor), 433 "CONVERT_TIMEZONE": _parse_convert_timezone, 434 "DATE_TRUNC": _date_trunc_to_time, 435 "DATEADD": lambda args: exp.DateAdd( 436 this=seq_get(args, 2), 437 expression=seq_get(args, 1), 438 unit=_map_date_part(seq_get(args, 0)), 439 ), 440 "DATEDIFF": _parse_datediff, 441 "DIV0": _div0_to_if, 442 "FLATTEN": exp.Explode.from_arg_list, 443 "IFF": exp.If.from_arg_list, 444 "LAST_DAY": lambda args: exp.LastDay( 445 this=seq_get(args, 0), unit=_map_date_part(seq_get(args, 1)) 446 ), 447 "LISTAGG": exp.GroupConcat.from_arg_list, 448 "NULLIFZERO": _nullifzero_to_if, 449 "OBJECT_CONSTRUCT": _parse_object_construct, 450 "REGEXP_REPLACE": _parse_regexp_replace, 451 "REGEXP_SUBSTR": exp.RegexpExtract.from_arg_list, 452 "RLIKE": exp.RegexpLike.from_arg_list, 453 "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)), 454 "TIMEDIFF": _parse_datediff, 455 "TIMESTAMPDIFF": _parse_datediff, 456 "TIMESTAMPFROMPARTS": _parse_timestamp_from_parts, 457 "TIMESTAMP_FROM_PARTS": _parse_timestamp_from_parts, 458 "TO_TIMESTAMP": _parse_to_timestamp, 459 "TO_VARCHAR": exp.ToChar.from_arg_list, 460 "ZEROIFNULL": _zeroifnull_to_if, 461 } 462 463 FUNCTION_PARSERS = { 464 **parser.Parser.FUNCTION_PARSERS, 465 "DATE_PART": _parse_date_part, 466 "OBJECT_CONSTRUCT_KEEP_NULL": lambda self: self._parse_json_object(), 467 } 468 FUNCTION_PARSERS.pop("TRIM") 469 470 TIMESTAMPS = parser.Parser.TIMESTAMPS - {TokenType.TIME} 471 472 RANGE_PARSERS = { 473 **parser.Parser.RANGE_PARSERS, 474 TokenType.LIKE_ANY: parser.binary_range_parser(exp.LikeAny), 475 TokenType.ILIKE_ANY: parser.binary_range_parser(exp.ILikeAny), 476 TokenType.COLON: _parse_colon_get_path, 477 } 478 479 ALTER_PARSERS = { 480 **parser.Parser.ALTER_PARSERS, 481 "SET": lambda self: self._parse_set(tag=self._match_text_seq("TAG")), 482 "UNSET": lambda self: self.expression( 483 exp.Set, 484 tag=self._match_text_seq("TAG"), 485 expressions=self._parse_csv(self._parse_id_var), 486 unset=True, 487 ), 488 "SWAP": lambda self: self._parse_alter_table_swap(), 489 } 490 491 STATEMENT_PARSERS = { 492 **parser.Parser.STATEMENT_PARSERS, 493 TokenType.SHOW: lambda self: self._parse_show(), 494 } 495 496 PROPERTY_PARSERS = { 497 **parser.Parser.PROPERTY_PARSERS, 498 "LOCATION": lambda self: self._parse_location(), 499 } 500 501 SHOW_PARSERS = { 502 "PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 503 "TERSE PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 504 "COLUMNS": _show_parser("COLUMNS"), 505 } 506 507 STAGED_FILE_SINGLE_TOKENS = { 508 TokenType.DOT, 509 TokenType.MOD, 510 TokenType.SLASH, 511 } 512 513 FLATTEN_COLUMNS = ["SEQ", "KEY", "PATH", "INDEX", "VALUE", "THIS"] 514 515 def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expression]: 516 if is_map: 517 # Keys are strings in Snowflake's objects, see also: 518 # - https://docs.snowflake.com/en/sql-reference/data-types-semistructured 519 # - https://docs.snowflake.com/en/sql-reference/functions/object_construct 520 return self._parse_slice(self._parse_string()) 521 522 return self._parse_slice(self._parse_alias(self._parse_conjunction(), explicit=True)) 523 524 def _parse_lateral(self) -> t.Optional[exp.Lateral]: 525 lateral = super()._parse_lateral() 526 if not lateral: 527 return lateral 528 529 if isinstance(lateral.this, exp.Explode): 530 table_alias = lateral.args.get("alias") 531 columns = [exp.to_identifier(col) for col in self.FLATTEN_COLUMNS] 532 if table_alias and not table_alias.args.get("columns"): 533 table_alias.set("columns", columns) 534 elif not table_alias: 535 exp.alias_(lateral, "_flattened", table=columns, copy=False) 536 537 return lateral 538 539 def _parse_at_before(self, table: exp.Table) -> exp.Table: 540 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 541 index = self._index 542 if self._match_texts(("AT", "BEFORE")): 543 this = self._prev.text.upper() 544 kind = ( 545 self._match(TokenType.L_PAREN) 546 and self._match_texts(self.HISTORICAL_DATA_KIND) 547 and self._prev.text.upper() 548 ) 549 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 550 551 if expression: 552 self._match_r_paren() 553 when = self.expression( 554 exp.HistoricalData, this=this, kind=kind, expression=expression 555 ) 556 table.set("when", when) 557 else: 558 self._retreat(index) 559 560 return table 561 562 def _parse_table_parts(self, schema: bool = False) -> exp.Table: 563 # https://docs.snowflake.com/en/user-guide/querying-stage 564 if self._match(TokenType.STRING, advance=False): 565 table = self._parse_string() 566 elif self._match_text_seq("@", advance=False): 567 table = self._parse_location_path() 568 else: 569 table = None 570 571 if table: 572 file_format = None 573 pattern = None 574 575 self._match(TokenType.L_PAREN) 576 while self._curr and not self._match(TokenType.R_PAREN): 577 if self._match_text_seq("FILE_FORMAT", "=>"): 578 file_format = self._parse_string() or super()._parse_table_parts() 579 elif self._match_text_seq("PATTERN", "=>"): 580 pattern = self._parse_string() 581 else: 582 break 583 584 self._match(TokenType.COMMA) 585 586 table = self.expression(exp.Table, this=table, format=file_format, pattern=pattern) 587 else: 588 table = super()._parse_table_parts(schema=schema) 589 590 return self._parse_at_before(table) 591 592 def _parse_id_var( 593 self, 594 any_token: bool = True, 595 tokens: t.Optional[t.Collection[TokenType]] = None, 596 ) -> t.Optional[exp.Expression]: 597 if self._match_text_seq("IDENTIFIER", "("): 598 identifier = ( 599 super()._parse_id_var(any_token=any_token, tokens=tokens) 600 or self._parse_string() 601 ) 602 self._match_r_paren() 603 return self.expression(exp.Anonymous, this="IDENTIFIER", expressions=[identifier]) 604 605 return super()._parse_id_var(any_token=any_token, tokens=tokens) 606 607 def _parse_show_snowflake(self, this: str) -> exp.Show: 608 scope = None 609 scope_kind = None 610 611 like = self._parse_string() if self._match(TokenType.LIKE) else None 612 613 if self._match(TokenType.IN): 614 if self._match_text_seq("ACCOUNT"): 615 scope_kind = "ACCOUNT" 616 elif self._match_set(self.DB_CREATABLES): 617 scope_kind = self._prev.text 618 if self._curr: 619 scope = self._parse_table() 620 elif self._curr: 621 scope_kind = "TABLE" 622 scope = self._parse_table() 623 624 return self.expression( 625 exp.Show, this=this, like=like, scope=scope, scope_kind=scope_kind 626 ) 627 628 def _parse_alter_table_swap(self) -> exp.SwapTable: 629 self._match_text_seq("WITH") 630 return self.expression(exp.SwapTable, this=self._parse_table(schema=True)) 631 632 def _parse_location(self) -> exp.LocationProperty: 633 self._match(TokenType.EQ) 634 return self.expression(exp.LocationProperty, this=self._parse_location_path()) 635 636 def _parse_location_path(self) -> exp.Var: 637 parts = [self._advance_any(ignore_reserved=True)] 638 639 # We avoid consuming a comma token because external tables like @foo and @bar 640 # can be joined in a query with a comma separator. 641 while self._is_connected() and not self._match(TokenType.COMMA, advance=False): 642 parts.append(self._advance_any(ignore_reserved=True)) 643 644 return exp.var("".join(part.text for part in parts if part)) 645 646 class Tokenizer(tokens.Tokenizer): 647 STRING_ESCAPES = ["\\", "'"] 648 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 649 RAW_STRINGS = ["$$"] 650 COMMENTS = ["--", "//", ("/*", "*/")] 651 652 KEYWORDS = { 653 **tokens.Tokenizer.KEYWORDS, 654 "BYTEINT": TokenType.INT, 655 "CHAR VARYING": TokenType.VARCHAR, 656 "CHARACTER VARYING": TokenType.VARCHAR, 657 "EXCLUDE": TokenType.EXCEPT, 658 "ILIKE ANY": TokenType.ILIKE_ANY, 659 "LIKE ANY": TokenType.LIKE_ANY, 660 "MATCH_RECOGNIZE": TokenType.MATCH_RECOGNIZE, 661 "MINUS": TokenType.EXCEPT, 662 "NCHAR VARYING": TokenType.VARCHAR, 663 "PUT": TokenType.COMMAND, 664 "RENAME": TokenType.REPLACE, 665 "SAMPLE": TokenType.TABLE_SAMPLE, 666 "SQL_DOUBLE": TokenType.DOUBLE, 667 "SQL_VARCHAR": TokenType.VARCHAR, 668 "TIMESTAMP_LTZ": TokenType.TIMESTAMPLTZ, 669 "TIMESTAMP_NTZ": TokenType.TIMESTAMP, 670 "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ, 671 "TIMESTAMPNTZ": TokenType.TIMESTAMP, 672 "TOP": TokenType.TOP, 673 } 674 675 SINGLE_TOKENS = { 676 **tokens.Tokenizer.SINGLE_TOKENS, 677 "$": TokenType.PARAMETER, 678 } 679 680 VAR_SINGLE_TOKENS = {"$"} 681 682 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW} 683 684 class Generator(generator.Generator): 685 PARAMETER_TOKEN = "$" 686 MATCHED_BY_SOURCE = False 687 SINGLE_STRING_INTERVAL = True 688 JOIN_HINTS = False 689 TABLE_HINTS = False 690 QUERY_HINTS = False 691 AGGREGATE_FILTER_SUPPORTED = False 692 SUPPORTS_TABLE_COPY = False 693 COLLATE_IS_FUNC = True 694 LIMIT_ONLY_LITERALS = True 695 696 TRANSFORMS = { 697 **generator.Generator.TRANSFORMS, 698 exp.ArgMax: rename_func("MAX_BY"), 699 exp.ArgMin: rename_func("MIN_BY"), 700 exp.Array: inline_array_sql, 701 exp.ArrayConcat: rename_func("ARRAY_CAT"), 702 exp.ArrayContains: lambda self, e: self.func("ARRAY_CONTAINS", e.expression, e.this), 703 exp.ArrayJoin: rename_func("ARRAY_TO_STRING"), 704 exp.AtTimeZone: lambda self, e: self.func( 705 "CONVERT_TIMEZONE", e.args.get("zone"), e.this 706 ), 707 exp.BitwiseXor: rename_func("BITXOR"), 708 exp.DateAdd: date_delta_sql("DATEADD"), 709 exp.DateDiff: date_delta_sql("DATEDIFF"), 710 exp.DateStrToDate: datestrtodate_sql, 711 exp.DataType: _datatype_sql, 712 exp.DayOfMonth: rename_func("DAYOFMONTH"), 713 exp.DayOfWeek: rename_func("DAYOFWEEK"), 714 exp.DayOfYear: rename_func("DAYOFYEAR"), 715 exp.Explode: rename_func("FLATTEN"), 716 exp.Extract: rename_func("DATE_PART"), 717 exp.GenerateSeries: lambda self, e: self.func( 718 "ARRAY_GENERATE_RANGE", e.args["start"], e.args["end"] + 1, e.args.get("step") 719 ), 720 exp.GroupConcat: rename_func("LISTAGG"), 721 exp.If: if_sql(name="IFF", false_value="NULL"), 722 exp.JSONExtract: lambda self, e: f"{self.sql(e, 'this')}[{self.sql(e, 'expression')}]", 723 exp.JSONKeyValue: json_keyvalue_comma_sql, 724 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 725 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 726 exp.LogicalOr: rename_func("BOOLOR_AGG"), 727 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 728 exp.Max: max_or_greatest, 729 exp.Min: min_or_least, 730 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 731 exp.PercentileCont: transforms.preprocess( 732 [transforms.add_within_group_for_percentiles] 733 ), 734 exp.PercentileDisc: transforms.preprocess( 735 [transforms.add_within_group_for_percentiles] 736 ), 737 exp.Pivot: transforms.preprocess([_unqualify_unpivot_columns]), 738 exp.RegexpILike: _regexpilike_sql, 739 exp.Rand: rename_func("RANDOM"), 740 exp.Select: transforms.preprocess( 741 [ 742 transforms.eliminate_distinct_on, 743 transforms.explode_to_unnest(), 744 transforms.eliminate_semi_and_anti_joins, 745 ] 746 ), 747 exp.SHA: rename_func("SHA1"), 748 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 749 exp.StartsWith: rename_func("STARTSWITH"), 750 exp.StrPosition: lambda self, e: self.func( 751 "POSITION", e.args.get("substr"), e.this, e.args.get("position") 752 ), 753 exp.StrToTime: lambda self, e: f"TO_TIMESTAMP({self.sql(e, 'this')}, {self.format_time(e)})", 754 exp.Struct: lambda self, e: self.func( 755 "OBJECT_CONSTRUCT", 756 *(arg for expression in e.expressions for arg in expression.flatten()), 757 ), 758 exp.Stuff: rename_func("INSERT"), 759 exp.TimestampDiff: lambda self, e: self.func( 760 "TIMESTAMPDIFF", e.unit, e.expression, e.this 761 ), 762 exp.TimestampTrunc: timestamptrunc_sql, 763 exp.TimeStrToTime: timestrtotime_sql, 764 exp.TimeToStr: lambda self, e: self.func( 765 "TO_CHAR", exp.cast(e.this, "timestamp"), self.format_time(e) 766 ), 767 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 768 exp.ToArray: rename_func("TO_ARRAY"), 769 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 770 exp.Trim: lambda self, e: self.func("TRIM", e.this, e.expression), 771 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 772 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 773 exp.UnixToTime: _unix_to_time_sql, 774 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 775 exp.WeekOfYear: rename_func("WEEKOFYEAR"), 776 exp.Xor: rename_func("BOOLXOR"), 777 } 778 779 TYPE_MAPPING = { 780 **generator.Generator.TYPE_MAPPING, 781 exp.DataType.Type.TIMESTAMP: "TIMESTAMPNTZ", 782 } 783 784 STAR_MAPPING = { 785 "except": "EXCLUDE", 786 "replace": "RENAME", 787 } 788 789 PROPERTIES_LOCATION = { 790 **generator.Generator.PROPERTIES_LOCATION, 791 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 792 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 793 } 794 795 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 796 milli = expression.args.get("milli") 797 if milli is not None: 798 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 799 expression.set("nano", milli_to_nano) 800 801 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 802 803 def trycast_sql(self, expression: exp.TryCast) -> str: 804 value = expression.this 805 806 if value.type is None: 807 from sqlglot.optimizer.annotate_types import annotate_types 808 809 value = annotate_types(value) 810 811 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 812 return super().trycast_sql(expression) 813 814 # TRY_CAST only works for string values in Snowflake 815 return self.cast_sql(expression) 816 817 def log_sql(self, expression: exp.Log) -> str: 818 if not expression.expression: 819 return self.func("LN", expression.this) 820 821 return super().log_sql(expression) 822 823 def unnest_sql(self, expression: exp.Unnest) -> str: 824 unnest_alias = expression.args.get("alias") 825 offset = expression.args.get("offset") 826 827 columns = [ 828 exp.to_identifier("seq"), 829 exp.to_identifier("key"), 830 exp.to_identifier("path"), 831 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 832 seq_get(unnest_alias.columns if unnest_alias else [], 0) 833 or exp.to_identifier("value"), 834 exp.to_identifier("this"), 835 ] 836 837 if unnest_alias: 838 unnest_alias.set("columns", columns) 839 else: 840 unnest_alias = exp.TableAlias(this="_u", columns=columns) 841 842 explode = f"TABLE(FLATTEN(INPUT => {self.sql(expression.expressions[0])}))" 843 alias = self.sql(unnest_alias) 844 alias = f" AS {alias}" if alias else "" 845 return f"{explode}{alias}" 846 847 def show_sql(self, expression: exp.Show) -> str: 848 like = self.sql(expression, "like") 849 like = f" LIKE {like}" if like else "" 850 851 scope = self.sql(expression, "scope") 852 scope = f" {scope}" if scope else "" 853 854 scope_kind = self.sql(expression, "scope_kind") 855 if scope_kind: 856 scope_kind = f" IN {scope_kind}" 857 858 return f"SHOW {expression.name}{like}{scope_kind}{scope}" 859 860 def regexpextract_sql(self, expression: exp.RegexpExtract) -> str: 861 # Other dialects don't support all of the following parameters, so we need to 862 # generate default values as necessary to ensure the transpilation is correct 863 group = expression.args.get("group") 864 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 865 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 866 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 867 868 return self.func( 869 "REGEXP_SUBSTR", 870 expression.this, 871 expression.expression, 872 position, 873 occurrence, 874 parameters, 875 group, 876 ) 877 878 def except_op(self, expression: exp.Except) -> str: 879 if not expression.args.get("distinct", False): 880 self.unsupported("EXCEPT with All is not supported in Snowflake") 881 return super().except_op(expression) 882 883 def intersect_op(self, expression: exp.Intersect) -> str: 884 if not expression.args.get("distinct", False): 885 self.unsupported("INTERSECT with All is not supported in Snowflake") 886 return super().intersect_op(expression) 887 888 def describe_sql(self, expression: exp.Describe) -> str: 889 # Default to table if kind is unknown 890 kind_value = expression.args.get("kind") or "TABLE" 891 kind = f" {kind_value}" if kind_value else "" 892 this = f" {self.sql(expression, 'this')}" 893 expressions = self.expressions(expression, flat=True) 894 expressions = f" {expressions}" if expressions else "" 895 return f"DESCRIBE{kind}{this}{expressions}" 896 897 def generatedasidentitycolumnconstraint_sql( 898 self, expression: exp.GeneratedAsIdentityColumnConstraint 899 ) -> str: 900 start = expression.args.get("start") 901 start = f" START {start}" if start else "" 902 increment = expression.args.get("increment") 903 increment = f" INCREMENT {increment}" if increment else "" 904 return f"AUTOINCREMENT{start}{increment}" 905 906 def swaptable_sql(self, expression: exp.SwapTable) -> str: 907 this = self.sql(expression, "this") 908 return f"SWAP WITH {this}" 909 910 def with_properties(self, properties: exp.Properties) -> str: 911 return self.properties(properties, wrapped=False, prefix=self.seg(""), sep=" ")
361class Snowflake(Dialect): 362 # https://docs.snowflake.com/en/sql-reference/identifiers-syntax 363 NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE 364 NULL_ORDERING = "nulls_are_large" 365 TIME_FORMAT = "'YYYY-MM-DD HH24:MI:SS'" 366 SUPPORTS_USER_DEFINED_TYPES = False 367 SUPPORTS_SEMI_ANTI_JOIN = False 368 PREFER_CTE_ALIAS_COLUMN = True 369 TABLESAMPLE_SIZE_IS_PERCENT = True 370 371 TIME_MAPPING = { 372 "YYYY": "%Y", 373 "yyyy": "%Y", 374 "YY": "%y", 375 "yy": "%y", 376 "MMMM": "%B", 377 "mmmm": "%B", 378 "MON": "%b", 379 "mon": "%b", 380 "MM": "%m", 381 "mm": "%m", 382 "DD": "%d", 383 "dd": "%-d", 384 "DY": "%a", 385 "dy": "%w", 386 "HH24": "%H", 387 "hh24": "%H", 388 "HH12": "%I", 389 "hh12": "%I", 390 "MI": "%M", 391 "mi": "%M", 392 "SS": "%S", 393 "ss": "%S", 394 "FF": "%f", 395 "ff": "%f", 396 "FF6": "%f", 397 "ff6": "%f", 398 } 399 400 def quote_identifier(self, expression: E, identify: bool = True) -> E: 401 # This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an 402 # unquoted DUAL keyword in a special way and does not map it to a user-defined table 403 if ( 404 isinstance(expression, exp.Identifier) 405 and isinstance(expression.parent, exp.Table) 406 and expression.name.lower() == "dual" 407 ): 408 return t.cast(E, expression) 409 410 return super().quote_identifier(expression, identify=identify) 411 412 class Parser(parser.Parser): 413 IDENTIFY_PIVOT_STRINGS = True 414 415 TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.WINDOW} 416 417 FUNCTIONS = { 418 **parser.Parser.FUNCTIONS, 419 "ARRAYAGG": exp.ArrayAgg.from_arg_list, 420 "ARRAY_CONSTRUCT": exp.Array.from_arg_list, 421 "ARRAY_CONTAINS": lambda args: exp.ArrayContains( 422 this=seq_get(args, 1), expression=seq_get(args, 0) 423 ), 424 "ARRAY_GENERATE_RANGE": lambda args: exp.GenerateSeries( 425 # ARRAY_GENERATE_RANGE has an exlusive end; we normalize it to be inclusive 426 start=seq_get(args, 0), 427 end=exp.Sub(this=seq_get(args, 1), expression=exp.Literal.number(1)), 428 step=seq_get(args, 2), 429 ), 430 "ARRAY_TO_STRING": exp.ArrayJoin.from_arg_list, 431 "BITXOR": binary_from_function(exp.BitwiseXor), 432 "BIT_XOR": binary_from_function(exp.BitwiseXor), 433 "BOOLXOR": binary_from_function(exp.Xor), 434 "CONVERT_TIMEZONE": _parse_convert_timezone, 435 "DATE_TRUNC": _date_trunc_to_time, 436 "DATEADD": lambda args: exp.DateAdd( 437 this=seq_get(args, 2), 438 expression=seq_get(args, 1), 439 unit=_map_date_part(seq_get(args, 0)), 440 ), 441 "DATEDIFF": _parse_datediff, 442 "DIV0": _div0_to_if, 443 "FLATTEN": exp.Explode.from_arg_list, 444 "IFF": exp.If.from_arg_list, 445 "LAST_DAY": lambda args: exp.LastDay( 446 this=seq_get(args, 0), unit=_map_date_part(seq_get(args, 1)) 447 ), 448 "LISTAGG": exp.GroupConcat.from_arg_list, 449 "NULLIFZERO": _nullifzero_to_if, 450 "OBJECT_CONSTRUCT": _parse_object_construct, 451 "REGEXP_REPLACE": _parse_regexp_replace, 452 "REGEXP_SUBSTR": exp.RegexpExtract.from_arg_list, 453 "RLIKE": exp.RegexpLike.from_arg_list, 454 "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)), 455 "TIMEDIFF": _parse_datediff, 456 "TIMESTAMPDIFF": _parse_datediff, 457 "TIMESTAMPFROMPARTS": _parse_timestamp_from_parts, 458 "TIMESTAMP_FROM_PARTS": _parse_timestamp_from_parts, 459 "TO_TIMESTAMP": _parse_to_timestamp, 460 "TO_VARCHAR": exp.ToChar.from_arg_list, 461 "ZEROIFNULL": _zeroifnull_to_if, 462 } 463 464 FUNCTION_PARSERS = { 465 **parser.Parser.FUNCTION_PARSERS, 466 "DATE_PART": _parse_date_part, 467 "OBJECT_CONSTRUCT_KEEP_NULL": lambda self: self._parse_json_object(), 468 } 469 FUNCTION_PARSERS.pop("TRIM") 470 471 TIMESTAMPS = parser.Parser.TIMESTAMPS - {TokenType.TIME} 472 473 RANGE_PARSERS = { 474 **parser.Parser.RANGE_PARSERS, 475 TokenType.LIKE_ANY: parser.binary_range_parser(exp.LikeAny), 476 TokenType.ILIKE_ANY: parser.binary_range_parser(exp.ILikeAny), 477 TokenType.COLON: _parse_colon_get_path, 478 } 479 480 ALTER_PARSERS = { 481 **parser.Parser.ALTER_PARSERS, 482 "SET": lambda self: self._parse_set(tag=self._match_text_seq("TAG")), 483 "UNSET": lambda self: self.expression( 484 exp.Set, 485 tag=self._match_text_seq("TAG"), 486 expressions=self._parse_csv(self._parse_id_var), 487 unset=True, 488 ), 489 "SWAP": lambda self: self._parse_alter_table_swap(), 490 } 491 492 STATEMENT_PARSERS = { 493 **parser.Parser.STATEMENT_PARSERS, 494 TokenType.SHOW: lambda self: self._parse_show(), 495 } 496 497 PROPERTY_PARSERS = { 498 **parser.Parser.PROPERTY_PARSERS, 499 "LOCATION": lambda self: self._parse_location(), 500 } 501 502 SHOW_PARSERS = { 503 "PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 504 "TERSE PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 505 "COLUMNS": _show_parser("COLUMNS"), 506 } 507 508 STAGED_FILE_SINGLE_TOKENS = { 509 TokenType.DOT, 510 TokenType.MOD, 511 TokenType.SLASH, 512 } 513 514 FLATTEN_COLUMNS = ["SEQ", "KEY", "PATH", "INDEX", "VALUE", "THIS"] 515 516 def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expression]: 517 if is_map: 518 # Keys are strings in Snowflake's objects, see also: 519 # - https://docs.snowflake.com/en/sql-reference/data-types-semistructured 520 # - https://docs.snowflake.com/en/sql-reference/functions/object_construct 521 return self._parse_slice(self._parse_string()) 522 523 return self._parse_slice(self._parse_alias(self._parse_conjunction(), explicit=True)) 524 525 def _parse_lateral(self) -> t.Optional[exp.Lateral]: 526 lateral = super()._parse_lateral() 527 if not lateral: 528 return lateral 529 530 if isinstance(lateral.this, exp.Explode): 531 table_alias = lateral.args.get("alias") 532 columns = [exp.to_identifier(col) for col in self.FLATTEN_COLUMNS] 533 if table_alias and not table_alias.args.get("columns"): 534 table_alias.set("columns", columns) 535 elif not table_alias: 536 exp.alias_(lateral, "_flattened", table=columns, copy=False) 537 538 return lateral 539 540 def _parse_at_before(self, table: exp.Table) -> exp.Table: 541 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 542 index = self._index 543 if self._match_texts(("AT", "BEFORE")): 544 this = self._prev.text.upper() 545 kind = ( 546 self._match(TokenType.L_PAREN) 547 and self._match_texts(self.HISTORICAL_DATA_KIND) 548 and self._prev.text.upper() 549 ) 550 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 551 552 if expression: 553 self._match_r_paren() 554 when = self.expression( 555 exp.HistoricalData, this=this, kind=kind, expression=expression 556 ) 557 table.set("when", when) 558 else: 559 self._retreat(index) 560 561 return table 562 563 def _parse_table_parts(self, schema: bool = False) -> exp.Table: 564 # https://docs.snowflake.com/en/user-guide/querying-stage 565 if self._match(TokenType.STRING, advance=False): 566 table = self._parse_string() 567 elif self._match_text_seq("@", advance=False): 568 table = self._parse_location_path() 569 else: 570 table = None 571 572 if table: 573 file_format = None 574 pattern = None 575 576 self._match(TokenType.L_PAREN) 577 while self._curr and not self._match(TokenType.R_PAREN): 578 if self._match_text_seq("FILE_FORMAT", "=>"): 579 file_format = self._parse_string() or super()._parse_table_parts() 580 elif self._match_text_seq("PATTERN", "=>"): 581 pattern = self._parse_string() 582 else: 583 break 584 585 self._match(TokenType.COMMA) 586 587 table = self.expression(exp.Table, this=table, format=file_format, pattern=pattern) 588 else: 589 table = super()._parse_table_parts(schema=schema) 590 591 return self._parse_at_before(table) 592 593 def _parse_id_var( 594 self, 595 any_token: bool = True, 596 tokens: t.Optional[t.Collection[TokenType]] = None, 597 ) -> t.Optional[exp.Expression]: 598 if self._match_text_seq("IDENTIFIER", "("): 599 identifier = ( 600 super()._parse_id_var(any_token=any_token, tokens=tokens) 601 or self._parse_string() 602 ) 603 self._match_r_paren() 604 return self.expression(exp.Anonymous, this="IDENTIFIER", expressions=[identifier]) 605 606 return super()._parse_id_var(any_token=any_token, tokens=tokens) 607 608 def _parse_show_snowflake(self, this: str) -> exp.Show: 609 scope = None 610 scope_kind = None 611 612 like = self._parse_string() if self._match(TokenType.LIKE) else None 613 614 if self._match(TokenType.IN): 615 if self._match_text_seq("ACCOUNT"): 616 scope_kind = "ACCOUNT" 617 elif self._match_set(self.DB_CREATABLES): 618 scope_kind = self._prev.text 619 if self._curr: 620 scope = self._parse_table() 621 elif self._curr: 622 scope_kind = "TABLE" 623 scope = self._parse_table() 624 625 return self.expression( 626 exp.Show, this=this, like=like, scope=scope, scope_kind=scope_kind 627 ) 628 629 def _parse_alter_table_swap(self) -> exp.SwapTable: 630 self._match_text_seq("WITH") 631 return self.expression(exp.SwapTable, this=self._parse_table(schema=True)) 632 633 def _parse_location(self) -> exp.LocationProperty: 634 self._match(TokenType.EQ) 635 return self.expression(exp.LocationProperty, this=self._parse_location_path()) 636 637 def _parse_location_path(self) -> exp.Var: 638 parts = [self._advance_any(ignore_reserved=True)] 639 640 # We avoid consuming a comma token because external tables like @foo and @bar 641 # can be joined in a query with a comma separator. 642 while self._is_connected() and not self._match(TokenType.COMMA, advance=False): 643 parts.append(self._advance_any(ignore_reserved=True)) 644 645 return exp.var("".join(part.text for part in parts if part)) 646 647 class Tokenizer(tokens.Tokenizer): 648 STRING_ESCAPES = ["\\", "'"] 649 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 650 RAW_STRINGS = ["$$"] 651 COMMENTS = ["--", "//", ("/*", "*/")] 652 653 KEYWORDS = { 654 **tokens.Tokenizer.KEYWORDS, 655 "BYTEINT": TokenType.INT, 656 "CHAR VARYING": TokenType.VARCHAR, 657 "CHARACTER VARYING": TokenType.VARCHAR, 658 "EXCLUDE": TokenType.EXCEPT, 659 "ILIKE ANY": TokenType.ILIKE_ANY, 660 "LIKE ANY": TokenType.LIKE_ANY, 661 "MATCH_RECOGNIZE": TokenType.MATCH_RECOGNIZE, 662 "MINUS": TokenType.EXCEPT, 663 "NCHAR VARYING": TokenType.VARCHAR, 664 "PUT": TokenType.COMMAND, 665 "RENAME": TokenType.REPLACE, 666 "SAMPLE": TokenType.TABLE_SAMPLE, 667 "SQL_DOUBLE": TokenType.DOUBLE, 668 "SQL_VARCHAR": TokenType.VARCHAR, 669 "TIMESTAMP_LTZ": TokenType.TIMESTAMPLTZ, 670 "TIMESTAMP_NTZ": TokenType.TIMESTAMP, 671 "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ, 672 "TIMESTAMPNTZ": TokenType.TIMESTAMP, 673 "TOP": TokenType.TOP, 674 } 675 676 SINGLE_TOKENS = { 677 **tokens.Tokenizer.SINGLE_TOKENS, 678 "$": TokenType.PARAMETER, 679 } 680 681 VAR_SINGLE_TOKENS = {"$"} 682 683 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW} 684 685 class Generator(generator.Generator): 686 PARAMETER_TOKEN = "$" 687 MATCHED_BY_SOURCE = False 688 SINGLE_STRING_INTERVAL = True 689 JOIN_HINTS = False 690 TABLE_HINTS = False 691 QUERY_HINTS = False 692 AGGREGATE_FILTER_SUPPORTED = False 693 SUPPORTS_TABLE_COPY = False 694 COLLATE_IS_FUNC = True 695 LIMIT_ONLY_LITERALS = True 696 697 TRANSFORMS = { 698 **generator.Generator.TRANSFORMS, 699 exp.ArgMax: rename_func("MAX_BY"), 700 exp.ArgMin: rename_func("MIN_BY"), 701 exp.Array: inline_array_sql, 702 exp.ArrayConcat: rename_func("ARRAY_CAT"), 703 exp.ArrayContains: lambda self, e: self.func("ARRAY_CONTAINS", e.expression, e.this), 704 exp.ArrayJoin: rename_func("ARRAY_TO_STRING"), 705 exp.AtTimeZone: lambda self, e: self.func( 706 "CONVERT_TIMEZONE", e.args.get("zone"), e.this 707 ), 708 exp.BitwiseXor: rename_func("BITXOR"), 709 exp.DateAdd: date_delta_sql("DATEADD"), 710 exp.DateDiff: date_delta_sql("DATEDIFF"), 711 exp.DateStrToDate: datestrtodate_sql, 712 exp.DataType: _datatype_sql, 713 exp.DayOfMonth: rename_func("DAYOFMONTH"), 714 exp.DayOfWeek: rename_func("DAYOFWEEK"), 715 exp.DayOfYear: rename_func("DAYOFYEAR"), 716 exp.Explode: rename_func("FLATTEN"), 717 exp.Extract: rename_func("DATE_PART"), 718 exp.GenerateSeries: lambda self, e: self.func( 719 "ARRAY_GENERATE_RANGE", e.args["start"], e.args["end"] + 1, e.args.get("step") 720 ), 721 exp.GroupConcat: rename_func("LISTAGG"), 722 exp.If: if_sql(name="IFF", false_value="NULL"), 723 exp.JSONExtract: lambda self, e: f"{self.sql(e, 'this')}[{self.sql(e, 'expression')}]", 724 exp.JSONKeyValue: json_keyvalue_comma_sql, 725 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 726 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 727 exp.LogicalOr: rename_func("BOOLOR_AGG"), 728 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 729 exp.Max: max_or_greatest, 730 exp.Min: min_or_least, 731 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 732 exp.PercentileCont: transforms.preprocess( 733 [transforms.add_within_group_for_percentiles] 734 ), 735 exp.PercentileDisc: transforms.preprocess( 736 [transforms.add_within_group_for_percentiles] 737 ), 738 exp.Pivot: transforms.preprocess([_unqualify_unpivot_columns]), 739 exp.RegexpILike: _regexpilike_sql, 740 exp.Rand: rename_func("RANDOM"), 741 exp.Select: transforms.preprocess( 742 [ 743 transforms.eliminate_distinct_on, 744 transforms.explode_to_unnest(), 745 transforms.eliminate_semi_and_anti_joins, 746 ] 747 ), 748 exp.SHA: rename_func("SHA1"), 749 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 750 exp.StartsWith: rename_func("STARTSWITH"), 751 exp.StrPosition: lambda self, e: self.func( 752 "POSITION", e.args.get("substr"), e.this, e.args.get("position") 753 ), 754 exp.StrToTime: lambda self, e: f"TO_TIMESTAMP({self.sql(e, 'this')}, {self.format_time(e)})", 755 exp.Struct: lambda self, e: self.func( 756 "OBJECT_CONSTRUCT", 757 *(arg for expression in e.expressions for arg in expression.flatten()), 758 ), 759 exp.Stuff: rename_func("INSERT"), 760 exp.TimestampDiff: lambda self, e: self.func( 761 "TIMESTAMPDIFF", e.unit, e.expression, e.this 762 ), 763 exp.TimestampTrunc: timestamptrunc_sql, 764 exp.TimeStrToTime: timestrtotime_sql, 765 exp.TimeToStr: lambda self, e: self.func( 766 "TO_CHAR", exp.cast(e.this, "timestamp"), self.format_time(e) 767 ), 768 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 769 exp.ToArray: rename_func("TO_ARRAY"), 770 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 771 exp.Trim: lambda self, e: self.func("TRIM", e.this, e.expression), 772 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 773 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 774 exp.UnixToTime: _unix_to_time_sql, 775 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 776 exp.WeekOfYear: rename_func("WEEKOFYEAR"), 777 exp.Xor: rename_func("BOOLXOR"), 778 } 779 780 TYPE_MAPPING = { 781 **generator.Generator.TYPE_MAPPING, 782 exp.DataType.Type.TIMESTAMP: "TIMESTAMPNTZ", 783 } 784 785 STAR_MAPPING = { 786 "except": "EXCLUDE", 787 "replace": "RENAME", 788 } 789 790 PROPERTIES_LOCATION = { 791 **generator.Generator.PROPERTIES_LOCATION, 792 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 793 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 794 } 795 796 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 797 milli = expression.args.get("milli") 798 if milli is not None: 799 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 800 expression.set("nano", milli_to_nano) 801 802 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 803 804 def trycast_sql(self, expression: exp.TryCast) -> str: 805 value = expression.this 806 807 if value.type is None: 808 from sqlglot.optimizer.annotate_types import annotate_types 809 810 value = annotate_types(value) 811 812 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 813 return super().trycast_sql(expression) 814 815 # TRY_CAST only works for string values in Snowflake 816 return self.cast_sql(expression) 817 818 def log_sql(self, expression: exp.Log) -> str: 819 if not expression.expression: 820 return self.func("LN", expression.this) 821 822 return super().log_sql(expression) 823 824 def unnest_sql(self, expression: exp.Unnest) -> str: 825 unnest_alias = expression.args.get("alias") 826 offset = expression.args.get("offset") 827 828 columns = [ 829 exp.to_identifier("seq"), 830 exp.to_identifier("key"), 831 exp.to_identifier("path"), 832 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 833 seq_get(unnest_alias.columns if unnest_alias else [], 0) 834 or exp.to_identifier("value"), 835 exp.to_identifier("this"), 836 ] 837 838 if unnest_alias: 839 unnest_alias.set("columns", columns) 840 else: 841 unnest_alias = exp.TableAlias(this="_u", columns=columns) 842 843 explode = f"TABLE(FLATTEN(INPUT => {self.sql(expression.expressions[0])}))" 844 alias = self.sql(unnest_alias) 845 alias = f" AS {alias}" if alias else "" 846 return f"{explode}{alias}" 847 848 def show_sql(self, expression: exp.Show) -> str: 849 like = self.sql(expression, "like") 850 like = f" LIKE {like}" if like else "" 851 852 scope = self.sql(expression, "scope") 853 scope = f" {scope}" if scope else "" 854 855 scope_kind = self.sql(expression, "scope_kind") 856 if scope_kind: 857 scope_kind = f" IN {scope_kind}" 858 859 return f"SHOW {expression.name}{like}{scope_kind}{scope}" 860 861 def regexpextract_sql(self, expression: exp.RegexpExtract) -> str: 862 # Other dialects don't support all of the following parameters, so we need to 863 # generate default values as necessary to ensure the transpilation is correct 864 group = expression.args.get("group") 865 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 866 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 867 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 868 869 return self.func( 870 "REGEXP_SUBSTR", 871 expression.this, 872 expression.expression, 873 position, 874 occurrence, 875 parameters, 876 group, 877 ) 878 879 def except_op(self, expression: exp.Except) -> str: 880 if not expression.args.get("distinct", False): 881 self.unsupported("EXCEPT with All is not supported in Snowflake") 882 return super().except_op(expression) 883 884 def intersect_op(self, expression: exp.Intersect) -> str: 885 if not expression.args.get("distinct", False): 886 self.unsupported("INTERSECT with All is not supported in Snowflake") 887 return super().intersect_op(expression) 888 889 def describe_sql(self, expression: exp.Describe) -> str: 890 # Default to table if kind is unknown 891 kind_value = expression.args.get("kind") or "TABLE" 892 kind = f" {kind_value}" if kind_value else "" 893 this = f" {self.sql(expression, 'this')}" 894 expressions = self.expressions(expression, flat=True) 895 expressions = f" {expressions}" if expressions else "" 896 return f"DESCRIBE{kind}{this}{expressions}" 897 898 def generatedasidentitycolumnconstraint_sql( 899 self, expression: exp.GeneratedAsIdentityColumnConstraint 900 ) -> str: 901 start = expression.args.get("start") 902 start = f" START {start}" if start else "" 903 increment = expression.args.get("increment") 904 increment = f" INCREMENT {increment}" if increment else "" 905 return f"AUTOINCREMENT{start}{increment}" 906 907 def swaptable_sql(self, expression: exp.SwapTable) -> str: 908 this = self.sql(expression, "this") 909 return f"SWAP WITH {this}" 910 911 def with_properties(self, properties: exp.Properties) -> str: 912 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.
400 def quote_identifier(self, expression: E, identify: bool = True) -> E: 401 # This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an 402 # unquoted DUAL keyword in a special way and does not map it to a user-defined table 403 if ( 404 isinstance(expression, exp.Identifier) 405 and isinstance(expression.parent, exp.Table) 406 and expression.name.lower() == "dual" 407 ): 408 return t.cast(E, expression) 409 410 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
412 class Parser(parser.Parser): 413 IDENTIFY_PIVOT_STRINGS = True 414 415 TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.WINDOW} 416 417 FUNCTIONS = { 418 **parser.Parser.FUNCTIONS, 419 "ARRAYAGG": exp.ArrayAgg.from_arg_list, 420 "ARRAY_CONSTRUCT": exp.Array.from_arg_list, 421 "ARRAY_CONTAINS": lambda args: exp.ArrayContains( 422 this=seq_get(args, 1), expression=seq_get(args, 0) 423 ), 424 "ARRAY_GENERATE_RANGE": lambda args: exp.GenerateSeries( 425 # ARRAY_GENERATE_RANGE has an exlusive end; we normalize it to be inclusive 426 start=seq_get(args, 0), 427 end=exp.Sub(this=seq_get(args, 1), expression=exp.Literal.number(1)), 428 step=seq_get(args, 2), 429 ), 430 "ARRAY_TO_STRING": exp.ArrayJoin.from_arg_list, 431 "BITXOR": binary_from_function(exp.BitwiseXor), 432 "BIT_XOR": binary_from_function(exp.BitwiseXor), 433 "BOOLXOR": binary_from_function(exp.Xor), 434 "CONVERT_TIMEZONE": _parse_convert_timezone, 435 "DATE_TRUNC": _date_trunc_to_time, 436 "DATEADD": lambda args: exp.DateAdd( 437 this=seq_get(args, 2), 438 expression=seq_get(args, 1), 439 unit=_map_date_part(seq_get(args, 0)), 440 ), 441 "DATEDIFF": _parse_datediff, 442 "DIV0": _div0_to_if, 443 "FLATTEN": exp.Explode.from_arg_list, 444 "IFF": exp.If.from_arg_list, 445 "LAST_DAY": lambda args: exp.LastDay( 446 this=seq_get(args, 0), unit=_map_date_part(seq_get(args, 1)) 447 ), 448 "LISTAGG": exp.GroupConcat.from_arg_list, 449 "NULLIFZERO": _nullifzero_to_if, 450 "OBJECT_CONSTRUCT": _parse_object_construct, 451 "REGEXP_REPLACE": _parse_regexp_replace, 452 "REGEXP_SUBSTR": exp.RegexpExtract.from_arg_list, 453 "RLIKE": exp.RegexpLike.from_arg_list, 454 "SQUARE": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)), 455 "TIMEDIFF": _parse_datediff, 456 "TIMESTAMPDIFF": _parse_datediff, 457 "TIMESTAMPFROMPARTS": _parse_timestamp_from_parts, 458 "TIMESTAMP_FROM_PARTS": _parse_timestamp_from_parts, 459 "TO_TIMESTAMP": _parse_to_timestamp, 460 "TO_VARCHAR": exp.ToChar.from_arg_list, 461 "ZEROIFNULL": _zeroifnull_to_if, 462 } 463 464 FUNCTION_PARSERS = { 465 **parser.Parser.FUNCTION_PARSERS, 466 "DATE_PART": _parse_date_part, 467 "OBJECT_CONSTRUCT_KEEP_NULL": lambda self: self._parse_json_object(), 468 } 469 FUNCTION_PARSERS.pop("TRIM") 470 471 TIMESTAMPS = parser.Parser.TIMESTAMPS - {TokenType.TIME} 472 473 RANGE_PARSERS = { 474 **parser.Parser.RANGE_PARSERS, 475 TokenType.LIKE_ANY: parser.binary_range_parser(exp.LikeAny), 476 TokenType.ILIKE_ANY: parser.binary_range_parser(exp.ILikeAny), 477 TokenType.COLON: _parse_colon_get_path, 478 } 479 480 ALTER_PARSERS = { 481 **parser.Parser.ALTER_PARSERS, 482 "SET": lambda self: self._parse_set(tag=self._match_text_seq("TAG")), 483 "UNSET": lambda self: self.expression( 484 exp.Set, 485 tag=self._match_text_seq("TAG"), 486 expressions=self._parse_csv(self._parse_id_var), 487 unset=True, 488 ), 489 "SWAP": lambda self: self._parse_alter_table_swap(), 490 } 491 492 STATEMENT_PARSERS = { 493 **parser.Parser.STATEMENT_PARSERS, 494 TokenType.SHOW: lambda self: self._parse_show(), 495 } 496 497 PROPERTY_PARSERS = { 498 **parser.Parser.PROPERTY_PARSERS, 499 "LOCATION": lambda self: self._parse_location(), 500 } 501 502 SHOW_PARSERS = { 503 "PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 504 "TERSE PRIMARY KEYS": _show_parser("PRIMARY KEYS"), 505 "COLUMNS": _show_parser("COLUMNS"), 506 } 507 508 STAGED_FILE_SINGLE_TOKENS = { 509 TokenType.DOT, 510 TokenType.MOD, 511 TokenType.SLASH, 512 } 513 514 FLATTEN_COLUMNS = ["SEQ", "KEY", "PATH", "INDEX", "VALUE", "THIS"] 515 516 def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expression]: 517 if is_map: 518 # Keys are strings in Snowflake's objects, see also: 519 # - https://docs.snowflake.com/en/sql-reference/data-types-semistructured 520 # - https://docs.snowflake.com/en/sql-reference/functions/object_construct 521 return self._parse_slice(self._parse_string()) 522 523 return self._parse_slice(self._parse_alias(self._parse_conjunction(), explicit=True)) 524 525 def _parse_lateral(self) -> t.Optional[exp.Lateral]: 526 lateral = super()._parse_lateral() 527 if not lateral: 528 return lateral 529 530 if isinstance(lateral.this, exp.Explode): 531 table_alias = lateral.args.get("alias") 532 columns = [exp.to_identifier(col) for col in self.FLATTEN_COLUMNS] 533 if table_alias and not table_alias.args.get("columns"): 534 table_alias.set("columns", columns) 535 elif not table_alias: 536 exp.alias_(lateral, "_flattened", table=columns, copy=False) 537 538 return lateral 539 540 def _parse_at_before(self, table: exp.Table) -> exp.Table: 541 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 542 index = self._index 543 if self._match_texts(("AT", "BEFORE")): 544 this = self._prev.text.upper() 545 kind = ( 546 self._match(TokenType.L_PAREN) 547 and self._match_texts(self.HISTORICAL_DATA_KIND) 548 and self._prev.text.upper() 549 ) 550 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 551 552 if expression: 553 self._match_r_paren() 554 when = self.expression( 555 exp.HistoricalData, this=this, kind=kind, expression=expression 556 ) 557 table.set("when", when) 558 else: 559 self._retreat(index) 560 561 return table 562 563 def _parse_table_parts(self, schema: bool = False) -> exp.Table: 564 # https://docs.snowflake.com/en/user-guide/querying-stage 565 if self._match(TokenType.STRING, advance=False): 566 table = self._parse_string() 567 elif self._match_text_seq("@", advance=False): 568 table = self._parse_location_path() 569 else: 570 table = None 571 572 if table: 573 file_format = None 574 pattern = None 575 576 self._match(TokenType.L_PAREN) 577 while self._curr and not self._match(TokenType.R_PAREN): 578 if self._match_text_seq("FILE_FORMAT", "=>"): 579 file_format = self._parse_string() or super()._parse_table_parts() 580 elif self._match_text_seq("PATTERN", "=>"): 581 pattern = self._parse_string() 582 else: 583 break 584 585 self._match(TokenType.COMMA) 586 587 table = self.expression(exp.Table, this=table, format=file_format, pattern=pattern) 588 else: 589 table = super()._parse_table_parts(schema=schema) 590 591 return self._parse_at_before(table) 592 593 def _parse_id_var( 594 self, 595 any_token: bool = True, 596 tokens: t.Optional[t.Collection[TokenType]] = None, 597 ) -> t.Optional[exp.Expression]: 598 if self._match_text_seq("IDENTIFIER", "("): 599 identifier = ( 600 super()._parse_id_var(any_token=any_token, tokens=tokens) 601 or self._parse_string() 602 ) 603 self._match_r_paren() 604 return self.expression(exp.Anonymous, this="IDENTIFIER", expressions=[identifier]) 605 606 return super()._parse_id_var(any_token=any_token, tokens=tokens) 607 608 def _parse_show_snowflake(self, this: str) -> exp.Show: 609 scope = None 610 scope_kind = None 611 612 like = self._parse_string() if self._match(TokenType.LIKE) else None 613 614 if self._match(TokenType.IN): 615 if self._match_text_seq("ACCOUNT"): 616 scope_kind = "ACCOUNT" 617 elif self._match_set(self.DB_CREATABLES): 618 scope_kind = self._prev.text 619 if self._curr: 620 scope = self._parse_table() 621 elif self._curr: 622 scope_kind = "TABLE" 623 scope = self._parse_table() 624 625 return self.expression( 626 exp.Show, this=this, like=like, scope=scope, scope_kind=scope_kind 627 ) 628 629 def _parse_alter_table_swap(self) -> exp.SwapTable: 630 self._match_text_seq("WITH") 631 return self.expression(exp.SwapTable, this=self._parse_table(schema=True)) 632 633 def _parse_location(self) -> exp.LocationProperty: 634 self._match(TokenType.EQ) 635 return self.expression(exp.LocationProperty, this=self._parse_location_path()) 636 637 def _parse_location_path(self) -> exp.Var: 638 parts = [self._advance_any(ignore_reserved=True)] 639 640 # We avoid consuming a comma token because external tables like @foo and @bar 641 # can be joined in a query with a comma separator. 642 while self._is_connected() and not self._match(TokenType.COMMA, advance=False): 643 parts.append(self._advance_any(ignore_reserved=True)) 644 645 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
- 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
647 class Tokenizer(tokens.Tokenizer): 648 STRING_ESCAPES = ["\\", "'"] 649 HEX_STRINGS = [("x'", "'"), ("X'", "'")] 650 RAW_STRINGS = ["$$"] 651 COMMENTS = ["--", "//", ("/*", "*/")] 652 653 KEYWORDS = { 654 **tokens.Tokenizer.KEYWORDS, 655 "BYTEINT": TokenType.INT, 656 "CHAR VARYING": TokenType.VARCHAR, 657 "CHARACTER VARYING": TokenType.VARCHAR, 658 "EXCLUDE": TokenType.EXCEPT, 659 "ILIKE ANY": TokenType.ILIKE_ANY, 660 "LIKE ANY": TokenType.LIKE_ANY, 661 "MATCH_RECOGNIZE": TokenType.MATCH_RECOGNIZE, 662 "MINUS": TokenType.EXCEPT, 663 "NCHAR VARYING": TokenType.VARCHAR, 664 "PUT": TokenType.COMMAND, 665 "RENAME": TokenType.REPLACE, 666 "SAMPLE": TokenType.TABLE_SAMPLE, 667 "SQL_DOUBLE": TokenType.DOUBLE, 668 "SQL_VARCHAR": TokenType.VARCHAR, 669 "TIMESTAMP_LTZ": TokenType.TIMESTAMPLTZ, 670 "TIMESTAMP_NTZ": TokenType.TIMESTAMP, 671 "TIMESTAMP_TZ": TokenType.TIMESTAMPTZ, 672 "TIMESTAMPNTZ": TokenType.TIMESTAMP, 673 "TOP": TokenType.TOP, 674 } 675 676 SINGLE_TOKENS = { 677 **tokens.Tokenizer.SINGLE_TOKENS, 678 "$": TokenType.PARAMETER, 679 } 680 681 VAR_SINGLE_TOKENS = {"$"} 682 683 COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW}
685 class Generator(generator.Generator): 686 PARAMETER_TOKEN = "$" 687 MATCHED_BY_SOURCE = False 688 SINGLE_STRING_INTERVAL = True 689 JOIN_HINTS = False 690 TABLE_HINTS = False 691 QUERY_HINTS = False 692 AGGREGATE_FILTER_SUPPORTED = False 693 SUPPORTS_TABLE_COPY = False 694 COLLATE_IS_FUNC = True 695 LIMIT_ONLY_LITERALS = True 696 697 TRANSFORMS = { 698 **generator.Generator.TRANSFORMS, 699 exp.ArgMax: rename_func("MAX_BY"), 700 exp.ArgMin: rename_func("MIN_BY"), 701 exp.Array: inline_array_sql, 702 exp.ArrayConcat: rename_func("ARRAY_CAT"), 703 exp.ArrayContains: lambda self, e: self.func("ARRAY_CONTAINS", e.expression, e.this), 704 exp.ArrayJoin: rename_func("ARRAY_TO_STRING"), 705 exp.AtTimeZone: lambda self, e: self.func( 706 "CONVERT_TIMEZONE", e.args.get("zone"), e.this 707 ), 708 exp.BitwiseXor: rename_func("BITXOR"), 709 exp.DateAdd: date_delta_sql("DATEADD"), 710 exp.DateDiff: date_delta_sql("DATEDIFF"), 711 exp.DateStrToDate: datestrtodate_sql, 712 exp.DataType: _datatype_sql, 713 exp.DayOfMonth: rename_func("DAYOFMONTH"), 714 exp.DayOfWeek: rename_func("DAYOFWEEK"), 715 exp.DayOfYear: rename_func("DAYOFYEAR"), 716 exp.Explode: rename_func("FLATTEN"), 717 exp.Extract: rename_func("DATE_PART"), 718 exp.GenerateSeries: lambda self, e: self.func( 719 "ARRAY_GENERATE_RANGE", e.args["start"], e.args["end"] + 1, e.args.get("step") 720 ), 721 exp.GroupConcat: rename_func("LISTAGG"), 722 exp.If: if_sql(name="IFF", false_value="NULL"), 723 exp.JSONExtract: lambda self, e: f"{self.sql(e, 'this')}[{self.sql(e, 'expression')}]", 724 exp.JSONKeyValue: json_keyvalue_comma_sql, 725 exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions), 726 exp.LogicalAnd: rename_func("BOOLAND_AGG"), 727 exp.LogicalOr: rename_func("BOOLOR_AGG"), 728 exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 729 exp.Max: max_or_greatest, 730 exp.Min: min_or_least, 731 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", 732 exp.PercentileCont: transforms.preprocess( 733 [transforms.add_within_group_for_percentiles] 734 ), 735 exp.PercentileDisc: transforms.preprocess( 736 [transforms.add_within_group_for_percentiles] 737 ), 738 exp.Pivot: transforms.preprocess([_unqualify_unpivot_columns]), 739 exp.RegexpILike: _regexpilike_sql, 740 exp.Rand: rename_func("RANDOM"), 741 exp.Select: transforms.preprocess( 742 [ 743 transforms.eliminate_distinct_on, 744 transforms.explode_to_unnest(), 745 transforms.eliminate_semi_and_anti_joins, 746 ] 747 ), 748 exp.SHA: rename_func("SHA1"), 749 exp.StarMap: rename_func("OBJECT_CONSTRUCT"), 750 exp.StartsWith: rename_func("STARTSWITH"), 751 exp.StrPosition: lambda self, e: self.func( 752 "POSITION", e.args.get("substr"), e.this, e.args.get("position") 753 ), 754 exp.StrToTime: lambda self, e: f"TO_TIMESTAMP({self.sql(e, 'this')}, {self.format_time(e)})", 755 exp.Struct: lambda self, e: self.func( 756 "OBJECT_CONSTRUCT", 757 *(arg for expression in e.expressions for arg in expression.flatten()), 758 ), 759 exp.Stuff: rename_func("INSERT"), 760 exp.TimestampDiff: lambda self, e: self.func( 761 "TIMESTAMPDIFF", e.unit, e.expression, e.this 762 ), 763 exp.TimestampTrunc: timestamptrunc_sql, 764 exp.TimeStrToTime: timestrtotime_sql, 765 exp.TimeToStr: lambda self, e: self.func( 766 "TO_CHAR", exp.cast(e.this, "timestamp"), self.format_time(e) 767 ), 768 exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})", 769 exp.ToArray: rename_func("TO_ARRAY"), 770 exp.ToChar: lambda self, e: self.function_fallback_sql(e), 771 exp.Trim: lambda self, e: self.func("TRIM", e.this, e.expression), 772 exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True), 773 exp.TsOrDsDiff: date_delta_sql("DATEDIFF"), 774 exp.UnixToTime: _unix_to_time_sql, 775 exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"), 776 exp.WeekOfYear: rename_func("WEEKOFYEAR"), 777 exp.Xor: rename_func("BOOLXOR"), 778 } 779 780 TYPE_MAPPING = { 781 **generator.Generator.TYPE_MAPPING, 782 exp.DataType.Type.TIMESTAMP: "TIMESTAMPNTZ", 783 } 784 785 STAR_MAPPING = { 786 "except": "EXCLUDE", 787 "replace": "RENAME", 788 } 789 790 PROPERTIES_LOCATION = { 791 **generator.Generator.PROPERTIES_LOCATION, 792 exp.SetProperty: exp.Properties.Location.UNSUPPORTED, 793 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, 794 } 795 796 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 797 milli = expression.args.get("milli") 798 if milli is not None: 799 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 800 expression.set("nano", milli_to_nano) 801 802 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression) 803 804 def trycast_sql(self, expression: exp.TryCast) -> str: 805 value = expression.this 806 807 if value.type is None: 808 from sqlglot.optimizer.annotate_types import annotate_types 809 810 value = annotate_types(value) 811 812 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 813 return super().trycast_sql(expression) 814 815 # TRY_CAST only works for string values in Snowflake 816 return self.cast_sql(expression) 817 818 def log_sql(self, expression: exp.Log) -> str: 819 if not expression.expression: 820 return self.func("LN", expression.this) 821 822 return super().log_sql(expression) 823 824 def unnest_sql(self, expression: exp.Unnest) -> str: 825 unnest_alias = expression.args.get("alias") 826 offset = expression.args.get("offset") 827 828 columns = [ 829 exp.to_identifier("seq"), 830 exp.to_identifier("key"), 831 exp.to_identifier("path"), 832 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 833 seq_get(unnest_alias.columns if unnest_alias else [], 0) 834 or exp.to_identifier("value"), 835 exp.to_identifier("this"), 836 ] 837 838 if unnest_alias: 839 unnest_alias.set("columns", columns) 840 else: 841 unnest_alias = exp.TableAlias(this="_u", columns=columns) 842 843 explode = f"TABLE(FLATTEN(INPUT => {self.sql(expression.expressions[0])}))" 844 alias = self.sql(unnest_alias) 845 alias = f" AS {alias}" if alias else "" 846 return f"{explode}{alias}" 847 848 def show_sql(self, expression: exp.Show) -> str: 849 like = self.sql(expression, "like") 850 like = f" LIKE {like}" if like else "" 851 852 scope = self.sql(expression, "scope") 853 scope = f" {scope}" if scope else "" 854 855 scope_kind = self.sql(expression, "scope_kind") 856 if scope_kind: 857 scope_kind = f" IN {scope_kind}" 858 859 return f"SHOW {expression.name}{like}{scope_kind}{scope}" 860 861 def regexpextract_sql(self, expression: exp.RegexpExtract) -> str: 862 # Other dialects don't support all of the following parameters, so we need to 863 # generate default values as necessary to ensure the transpilation is correct 864 group = expression.args.get("group") 865 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 866 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 867 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 868 869 return self.func( 870 "REGEXP_SUBSTR", 871 expression.this, 872 expression.expression, 873 position, 874 occurrence, 875 parameters, 876 group, 877 ) 878 879 def except_op(self, expression: exp.Except) -> str: 880 if not expression.args.get("distinct", False): 881 self.unsupported("EXCEPT with All is not supported in Snowflake") 882 return super().except_op(expression) 883 884 def intersect_op(self, expression: exp.Intersect) -> str: 885 if not expression.args.get("distinct", False): 886 self.unsupported("INTERSECT with All is not supported in Snowflake") 887 return super().intersect_op(expression) 888 889 def describe_sql(self, expression: exp.Describe) -> str: 890 # Default to table if kind is unknown 891 kind_value = expression.args.get("kind") or "TABLE" 892 kind = f" {kind_value}" if kind_value else "" 893 this = f" {self.sql(expression, 'this')}" 894 expressions = self.expressions(expression, flat=True) 895 expressions = f" {expressions}" if expressions else "" 896 return f"DESCRIBE{kind}{this}{expressions}" 897 898 def generatedasidentitycolumnconstraint_sql( 899 self, expression: exp.GeneratedAsIdentityColumnConstraint 900 ) -> str: 901 start = expression.args.get("start") 902 start = f" START {start}" if start else "" 903 increment = expression.args.get("increment") 904 increment = f" INCREMENT {increment}" if increment else "" 905 return f"AUTOINCREMENT{start}{increment}" 906 907 def swaptable_sql(self, expression: exp.SwapTable) -> str: 908 this = self.sql(expression, "this") 909 return f"SWAP WITH {this}" 910 911 def with_properties(self, properties: exp.Properties) -> str: 912 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
796 def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str: 797 milli = expression.args.get("milli") 798 if milli is not None: 799 milli_to_nano = milli.pop() * exp.Literal.number(1000000) 800 expression.set("nano", milli_to_nano) 801 802 return rename_func("TIMESTAMP_FROM_PARTS")(self, expression)
804 def trycast_sql(self, expression: exp.TryCast) -> str: 805 value = expression.this 806 807 if value.type is None: 808 from sqlglot.optimizer.annotate_types import annotate_types 809 810 value = annotate_types(value) 811 812 if value.is_type(*exp.DataType.TEXT_TYPES, exp.DataType.Type.UNKNOWN): 813 return super().trycast_sql(expression) 814 815 # TRY_CAST only works for string values in Snowflake 816 return self.cast_sql(expression)
824 def unnest_sql(self, expression: exp.Unnest) -> str: 825 unnest_alias = expression.args.get("alias") 826 offset = expression.args.get("offset") 827 828 columns = [ 829 exp.to_identifier("seq"), 830 exp.to_identifier("key"), 831 exp.to_identifier("path"), 832 offset.pop() if isinstance(offset, exp.Expression) else exp.to_identifier("index"), 833 seq_get(unnest_alias.columns if unnest_alias else [], 0) 834 or exp.to_identifier("value"), 835 exp.to_identifier("this"), 836 ] 837 838 if unnest_alias: 839 unnest_alias.set("columns", columns) 840 else: 841 unnest_alias = exp.TableAlias(this="_u", columns=columns) 842 843 explode = f"TABLE(FLATTEN(INPUT => {self.sql(expression.expressions[0])}))" 844 alias = self.sql(unnest_alias) 845 alias = f" AS {alias}" if alias else "" 846 return f"{explode}{alias}"
848 def show_sql(self, expression: exp.Show) -> str: 849 like = self.sql(expression, "like") 850 like = f" LIKE {like}" if like else "" 851 852 scope = self.sql(expression, "scope") 853 scope = f" {scope}" if scope else "" 854 855 scope_kind = self.sql(expression, "scope_kind") 856 if scope_kind: 857 scope_kind = f" IN {scope_kind}" 858 859 return f"SHOW {expression.name}{like}{scope_kind}{scope}"
861 def regexpextract_sql(self, expression: exp.RegexpExtract) -> str: 862 # Other dialects don't support all of the following parameters, so we need to 863 # generate default values as necessary to ensure the transpilation is correct 864 group = expression.args.get("group") 865 parameters = expression.args.get("parameters") or (group and exp.Literal.string("c")) 866 occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1)) 867 position = expression.args.get("position") or (occurrence and exp.Literal.number(1)) 868 869 return self.func( 870 "REGEXP_SUBSTR", 871 expression.this, 872 expression.expression, 873 position, 874 occurrence, 875 parameters, 876 group, 877 )
889 def describe_sql(self, expression: exp.Describe) -> str: 890 # Default to table if kind is unknown 891 kind_value = expression.args.get("kind") or "TABLE" 892 kind = f" {kind_value}" if kind_value else "" 893 this = f" {self.sql(expression, 'this')}" 894 expressions = self.expressions(expression, flat=True) 895 expressions = f" {expressions}" if expressions else "" 896 return f"DESCRIBE{kind}{this}{expressions}"
898 def generatedasidentitycolumnconstraint_sql( 899 self, expression: exp.GeneratedAsIdentityColumnConstraint 900 ) -> str: 901 start = expression.args.get("start") 902 start = f" START {start}" if start else "" 903 increment = expression.args.get("increment") 904 increment = f" INCREMENT {increment}" if increment else "" 905 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
- 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
- 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
- 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