sqlglot.parser
1from __future__ import annotations 2 3import itertools 4import logging 5import re 6import typing as t 7from builtins import type as Type 8from collections import defaultdict 9from collections.abc import Sequence 10 11from sqlglot import exp 12from sqlglot._typing import F 13from sqlglot.errors import ( 14 ErrorLevel, 15 ParseError, 16 TokenError, 17 concat_messages, 18 highlight_sql, 19 merge_errors, 20) 21from sqlglot.expressions import apply_index_offset 22from sqlglot.helper import ensure_list, i64, seq_get 23from sqlglot.optimizer.scope import find_in_scope 24from sqlglot.time import format_time 25from sqlglot.tokens import Token, Tokenizer, TokenType 26from sqlglot.trie import TrieResult, in_trie, new_trie 27 28if t.TYPE_CHECKING: 29 from re import Pattern 30 31 from sqlglot._typing import BuilderArgs, E 32 from sqlglot.dialects.dialect import Dialect, DialectType 33 from sqlglot.expressions import ExpOrStr 34 35 T = t.TypeVar("T") 36 TCeilFloor = t.TypeVar("TCeilFloor", exp.Ceil, exp.Floor) 37 38logger = logging.getLogger("sqlglot") 39 40OPTIONS_TYPE = dict[str, Sequence[t.Union[Sequence[str], str]]] 41 42# Excludes bare strings, which are also collections of strings, so that a single keyword 43# can't accidentally be matched with substring semantics (e.g. _match_texts("FOO")) 44TEXTS_TYPE = t.Union[tuple[str, ...], list[str], t.AbstractSet[str], t.Mapping[str, t.Any]] 45 46# Used to detect alphabetical characters and +/- in timestamp literals 47TIME_ZONE_RE: Pattern[str] = re.compile(r":.*?[a-zA-Z\+\-]") 48 49 50def build_var_map(args: BuilderArgs) -> exp.StarMap | exp.VarMap: 51 if len(args) == 1 and args[0].is_star: 52 return exp.StarMap(this=args[0]) 53 54 keys: list[ExpOrStr] = [] 55 values: list[ExpOrStr] = [] 56 for i in range(0, len(args), 2): 57 keys.append(args[i]) 58 values.append(args[i + 1]) 59 60 return exp.VarMap(keys=exp.array(*keys, copy=False), values=exp.array(*values, copy=False)) 61 62 63def build_like(args: BuilderArgs) -> exp.Escape | exp.Like: 64 like = exp.Like(this=seq_get(args, 1), expression=seq_get(args, 0)) 65 return exp.Escape(this=like, expression=seq_get(args, 2)) if len(args) > 2 else like 66 67 68def binary_range_parser( 69 expr_type: Type[exp.Expr], reverse_args: bool = False 70) -> t.Callable[[Parser, exp.Expr | None], exp.Expr | None]: 71 def _parse_binary_range(self: Parser, this: exp.Expr | None) -> exp.Expr | None: 72 expression = self._parse_bitwise() 73 if reverse_args: 74 this, expression = expression, this 75 return self._parse_escape(self.expression(expr_type(this=this, expression=expression))) 76 77 return _parse_binary_range 78 79 80def build_logarithm(args: BuilderArgs, dialect: Dialect) -> exp.Func: 81 # Default argument order is base, expression 82 this = seq_get(args, 0) 83 expression = seq_get(args, 1) 84 85 if expression: 86 if not dialect.LOG_BASE_FIRST: 87 this, expression = expression, this 88 return exp.Log(this=this, expression=expression) 89 90 return (exp.Ln if dialect.parser_class.LOG_DEFAULTS_TO_LN else exp.Log)(this=this) 91 92 93def build_hex(args: BuilderArgs, dialect: Dialect) -> exp.Hex | exp.LowerHex: 94 arg = seq_get(args, 0) 95 return exp.LowerHex(this=arg) if dialect.HEX_LOWERCASE else exp.Hex(this=arg) 96 97 98def build_lower(args: BuilderArgs) -> exp.Lower | exp.Hex: 99 # LOWER(HEX(..)) can be simplified to LowerHex to simplify its transpilation 100 arg = seq_get(args, 0) 101 return exp.LowerHex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Lower(this=arg) 102 103 104def build_upper(args: BuilderArgs) -> exp.Upper | exp.Hex: 105 # UPPER(HEX(..)) can be simplified to Hex to simplify its transpilation 106 arg = seq_get(args, 0) 107 return exp.Hex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Upper(this=arg) 108 109 110def build_extract_json_with_path( 111 expr_type: Type[E], 112) -> t.Callable[[BuilderArgs, Dialect], E]: 113 def _builder(args: BuilderArgs, dialect: Dialect) -> E: 114 expression = expr_type( 115 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 116 ) 117 if len(args) > 2 and expr_type is exp.JSONExtract: 118 expression.set("expressions", args[2:]) 119 if expr_type is exp.JSONExtractScalar: 120 expression.set("scalar_only", dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY) 121 122 return expression 123 124 return _builder 125 126 127def build_mod(args: BuilderArgs) -> exp.Mod: 128 this = seq_get(args, 0) 129 expression = seq_get(args, 1) 130 131 # Wrap the operands if they are binary nodes, e.g. MOD(a + 1, 7) -> (a + 1) % 7 132 this = exp.Paren(this=this) if isinstance(this, exp.Binary) else this 133 expression = exp.Paren(this=expression) if isinstance(expression, exp.Binary) else expression 134 135 return exp.Mod(this=this, expression=expression) 136 137 138def build_pad(args: BuilderArgs, is_left: bool = True): 139 return exp.Pad( 140 this=seq_get(args, 0), 141 expression=seq_get(args, 1), 142 fill_pattern=seq_get(args, 2), 143 is_left=is_left, 144 ) 145 146 147def build_array_constructor( 148 exp_class: Type[E], args: list[t.Any], bracket_kind: TokenType, dialect: Dialect 149) -> exp.Expr: 150 array_exp = exp_class(expressions=args) 151 152 if exp_class == exp.Array and dialect.HAS_DISTINCT_ARRAY_CONSTRUCTORS: 153 array_exp.set("bracket_notation", bracket_kind == TokenType.L_BRACKET) 154 155 return array_exp 156 157 158def build_convert_timezone( 159 args: BuilderArgs, default_source_tz: str | None = None 160) -> exp.ConvertTimezone | exp.Anonymous: 161 if len(args) == 2: 162 source_tz = exp.Literal.string(default_source_tz) if default_source_tz else None 163 return exp.ConvertTimezone( 164 source_tz=source_tz, target_tz=seq_get(args, 0), timestamp=seq_get(args, 1) 165 ) 166 167 return exp.ConvertTimezone.from_arg_list(args) 168 169 170def build_trim(args: BuilderArgs, is_left: bool = True, reverse_args: bool = False) -> exp.Trim: 171 this, expression = seq_get(args, 0), seq_get(args, 1) 172 173 if expression and reverse_args: 174 this, expression = expression, this 175 176 return exp.Trim(this=this, expression=expression, position="LEADING" if is_left else "TRAILING") 177 178 179def build_coalesce( 180 args: BuilderArgs, is_nvl: bool | None = None, is_null: bool | None = None 181) -> exp.Coalesce: 182 return exp.Coalesce(this=seq_get(args, 0), expressions=args[1:], is_nvl=is_nvl, is_null=is_null) 183 184 185def build_locate_strposition(args: BuilderArgs) -> exp.StrPosition: 186 return exp.StrPosition( 187 this=seq_get(args, 1), 188 substr=seq_get(args, 0), 189 position=seq_get(args, 2), 190 ) 191 192 193def build_array_append(args: BuilderArgs, dialect: Dialect) -> exp.ArrayAppend: 194 """ 195 Builds ArrayAppend with NULL propagation semantics based on the dialect configuration. 196 197 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 198 Others (DuckDB, PostgreSQL) create a new single-element array instead. 199 200 Args: 201 args: Function arguments [array, element] 202 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 203 204 Returns: 205 ArrayAppend expression with appropriate null_propagation flag 206 """ 207 return exp.ArrayAppend( 208 this=seq_get(args, 0), 209 expression=seq_get(args, 1), 210 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 211 ) 212 213 214def build_array_prepend(args: BuilderArgs, dialect: Dialect) -> exp.ArrayPrepend: 215 """ 216 Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration. 217 218 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 219 Others (DuckDB, PostgreSQL) create a new single-element array instead. 220 221 Args: 222 args: Function arguments [array, element] 223 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 224 225 Returns: 226 ArrayPrepend expression with appropriate null_propagation flag 227 """ 228 return exp.ArrayPrepend( 229 this=seq_get(args, 0), 230 expression=seq_get(args, 1), 231 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 232 ) 233 234 235def build_array_concat(args: BuilderArgs, dialect: Dialect) -> exp.ArrayConcat: 236 """ 237 Builds ArrayConcat with NULL propagation semantics based on the dialect configuration. 238 239 Some dialects (Redshift, Snowflake) return NULL when any input array is NULL. 240 Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation. 241 242 Args: 243 args: Function arguments [array1, array2, ...] (variadic) 244 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 245 246 Returns: 247 ArrayConcat expression with appropriate null_propagation flag 248 """ 249 return exp.ArrayConcat( 250 this=seq_get(args, 0), 251 expressions=args[1:], 252 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 253 ) 254 255 256def build_array_remove(args: BuilderArgs, dialect: Dialect) -> exp.ArrayRemove: 257 """ 258 Builds ArrayRemove with NULL propagation semantics based on the dialect configuration. 259 260 Some dialects (Snowflake) return NULL when the removal value is NULL. 261 Others (DuckDB) may return empty array due to NULL comparison semantics. 262 263 Args: 264 args: Function arguments [array, value_to_remove] 265 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 266 267 Returns: 268 ArrayRemove expression with appropriate null_propagation flag 269 """ 270 return exp.ArrayRemove( 271 this=seq_get(args, 0), 272 expression=seq_get(args, 1), 273 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 274 ) 275 276 277def _resolve_dialect(dialect: DialectType) -> Dialect: 278 from sqlglot.dialects.dialect import Dialect 279 280 return Dialect.get_or_raise(dialect) 281 282 283def _unpivot_target(expr: exp.Expr) -> exp.Expr: 284 # UNPIVOT's pre-FOR values and FOR field are new output names, not column references. 285 if isinstance(expr, exp.Column) and not expr.table: 286 return expr.this 287 if isinstance(expr, exp.Tuple): 288 expr.set("expressions", [_unpivot_target(e) for e in expr.expressions]) 289 return expr 290 291 292# Builders for the JSON `->` / `->>` / `#>` / `#>>` / `?` operators, shared between 293# COLUMN_OPERATORS (accessor-tier dialects) and JSON_OPERATORS (Postgres/DuckDB's 294# binary-operator tier). 295def build_json_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONExtract: 296 return self.expression( 297 exp.JSONExtract( 298 this=this, 299 expression=self.dialect.to_json_path(path), 300 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 301 ) 302 ) 303 304 305def build_json_extract_scalar( 306 self: Parser, this: exp.Expr, path: exp.Expr 307) -> exp.JSONExtractScalar: 308 return self.expression( 309 exp.JSONExtractScalar( 310 this=this, 311 expression=self.dialect.to_json_path(path), 312 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 313 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 314 ) 315 ) 316 317 318def build_jsonb_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONBExtract: 319 return self.expression(exp.JSONBExtract(this=this, expression=path)) 320 321 322def build_jsonb_extract_scalar( 323 self: Parser, this: exp.Expr, path: exp.Expr 324) -> exp.JSONBExtractScalar: 325 return self.expression(exp.JSONBExtractScalar(this=this, expression=path)) 326 327 328def build_jsonb_contains(self: Parser, this: exp.Expr, key: exp.Expr) -> exp.JSONBContains: 329 return self.expression(exp.JSONBContains(this=this, expression=key)) 330 331 332SENTINEL_NONE: Token = Token(TokenType.SENTINEL, "SENTINEL") 333 334 335class Parser: 336 """ 337 Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree. 338 339 Args: 340 error_level: The desired error level. 341 Default: ErrorLevel.IMMEDIATE 342 error_message_context: The amount of context to capture from a query string when displaying 343 the error message (in number of characters). 344 Default: 100 345 max_errors: Maximum number of error messages to include in a raised ParseError. 346 This is only relevant if error_level is ErrorLevel.RAISE. 347 Default: 3 348 max_nodes: Maximum number of AST nodes to prevent memory exhaustion. 349 Set to -1 (default) to disable the check. 350 """ 351 352 __slots__ = ( 353 "error_level", 354 "error_message_context", 355 "max_errors", 356 "max_nodes", 357 "dialect", 358 "sql", 359 "errors", 360 "_tokens", 361 "_index", 362 "_curr", 363 "_next", 364 "_prev", 365 "_prev_comments", 366 "_pipe_cte_counter", 367 "_chunks", 368 "_chunk_index", 369 "_tokens_size", 370 "_node_count", 371 ) 372 373 FUNCTIONS: t.ClassVar[dict[str, t.Callable]] = { 374 **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()}, 375 **dict.fromkeys(("COALESCE", "IFNULL", "NVL"), build_coalesce), 376 "ARRAY": lambda args, dialect: exp.Array(expressions=args), 377 "ARRAYAGG": lambda args, dialect: exp.ArrayAgg( 378 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 379 ), 380 "ARRAY_AGG": lambda args, dialect: exp.ArrayAgg( 381 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 382 ), 383 "ARRAY_APPEND": build_array_append, 384 "ARRAY_CAT": build_array_concat, 385 "ARRAY_CONCAT": build_array_concat, 386 "ARRAY_INTERSECT": lambda args: exp.ArrayIntersect(expressions=args), 387 "ARRAY_INTERSECTION": lambda args: exp.ArrayIntersect(expressions=args), 388 "ARRAY_PREPEND": build_array_prepend, 389 "ARRAY_REMOVE": build_array_remove, 390 "COUNT": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True), 391 "CONCAT": lambda args, dialect: exp.Concat( 392 expressions=args, 393 safe=not dialect.STRICT_STRING_CONCAT, 394 coalesce=dialect.CONCAT_COALESCE, 395 ), 396 "CONCAT_WS": lambda args, dialect: exp.ConcatWs( 397 expressions=args, 398 safe=not dialect.STRICT_STRING_CONCAT, 399 coalesce=dialect.CONCAT_WS_COALESCE, 400 ), 401 "CONVERT_TIMEZONE": build_convert_timezone, 402 "DATE_TO_DATE_STR": lambda args: exp.Cast( 403 this=seq_get(args, 0), 404 to=exp.DataType(this=exp.DType.TEXT), 405 ), 406 "GENERATE_DATE_ARRAY": lambda args: exp.GenerateDateArray( 407 start=seq_get(args, 0), 408 end=seq_get(args, 1), 409 step=seq_get(args, 2) or exp.Interval(this=exp.Literal.string(1), unit=exp.var("DAY")), 410 ), 411 "GENERATE_UUID": lambda args, dialect: exp.Uuid( 412 is_string=dialect.UUID_IS_STRING_TYPE or None 413 ), 414 "GLOB": lambda args: exp.Glob(this=seq_get(args, 1), expression=seq_get(args, 0)), 415 "GREATEST": lambda args, dialect: exp.Greatest( 416 this=seq_get(args, 0), 417 expressions=args[1:], 418 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 419 ), 420 "LEAST": lambda args, dialect: exp.Least( 421 this=seq_get(args, 0), 422 expressions=args[1:], 423 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 424 ), 425 "HEX": build_hex, 426 "JSON_EXTRACT": build_extract_json_with_path(exp.JSONExtract), 427 "JSON_EXTRACT_SCALAR": build_extract_json_with_path(exp.JSONExtractScalar), 428 "JSON_EXTRACT_PATH_TEXT": build_extract_json_with_path(exp.JSONExtractScalar), 429 "JSON_KEYS": lambda args, dialect: exp.JSONKeys( 430 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 431 ), 432 "LIKE": build_like, 433 "LOG": build_logarithm, 434 "LOG2": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)), 435 "LOG10": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)), 436 "LOWER": build_lower, 437 "LPAD": lambda args: build_pad(args), 438 "LEFTPAD": lambda args: build_pad(args), 439 "LTRIM": lambda args: build_trim(args), 440 "MOD": build_mod, 441 "RIGHTPAD": lambda args: build_pad(args, is_left=False), 442 "RPAD": lambda args: build_pad(args, is_left=False), 443 "RTRIM": lambda args: build_trim(args, is_left=False), 444 "SCOPE_RESOLUTION": lambda args: ( 445 exp.ScopeResolution(expression=seq_get(args, 0)) 446 if len(args) != 2 447 else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1)) 448 ), 449 "STRPOS": exp.StrPosition.from_arg_list, 450 "CHARINDEX": lambda args: build_locate_strposition(args), 451 "INSTR": exp.StrPosition.from_arg_list, 452 "LOCATE": lambda args: build_locate_strposition(args), 453 "TIME_TO_TIME_STR": lambda args: exp.Cast( 454 this=seq_get(args, 0), 455 to=exp.DataType(this=exp.DType.TEXT), 456 ), 457 "TO_HEX": build_hex, 458 "TS_OR_DS_TO_DATE_STR": lambda args: exp.Substring( 459 this=exp.Cast( 460 this=seq_get(args, 0), 461 to=exp.DataType(this=exp.DType.TEXT), 462 ), 463 start=exp.Literal.number(1), 464 length=exp.Literal.number(10), 465 ), 466 "UNNEST": lambda args: exp.Unnest(expressions=ensure_list(seq_get(args, 0))), 467 "UPPER": build_upper, 468 "UUID": lambda args, dialect: exp.Uuid(is_string=dialect.UUID_IS_STRING_TYPE or None), 469 "UUID_STRING": lambda args, dialect: exp.Uuid( 470 this=seq_get(args, 0), 471 name=seq_get(args, 1), 472 is_string=dialect.UUID_IS_STRING_TYPE or None, 473 ), 474 "VAR_MAP": build_var_map, 475 } 476 477 NO_PAREN_FUNCTIONS: t.ClassVar[dict] = { 478 TokenType.CURRENT_DATE: exp.CurrentDate, 479 TokenType.CURRENT_DATETIME: exp.CurrentDate, 480 TokenType.CURRENT_TIME: exp.CurrentTime, 481 TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp, 482 TokenType.CURRENT_USER: exp.CurrentUser, 483 TokenType.CURRENT_ROLE: exp.CurrentRole, 484 } 485 486 STRUCT_TYPE_TOKENS: t.ClassVar = { 487 TokenType.NESTED, 488 TokenType.OBJECT, 489 TokenType.STRUCT, 490 TokenType.UNION, 491 } 492 493 NESTED_TYPE_TOKENS: t.ClassVar = { 494 TokenType.ARRAY, 495 TokenType.LIST, 496 TokenType.LOWCARDINALITY, 497 TokenType.MAP, 498 TokenType.NULLABLE, 499 TokenType.RANGE, 500 *STRUCT_TYPE_TOKENS, 501 } 502 503 ENUM_TYPE_TOKENS: t.ClassVar = { 504 TokenType.DYNAMIC, 505 TokenType.ENUM, 506 TokenType.ENUM8, 507 TokenType.ENUM16, 508 } 509 510 AGGREGATE_TYPE_TOKENS: t.ClassVar = { 511 TokenType.AGGREGATEFUNCTION, 512 TokenType.SIMPLEAGGREGATEFUNCTION, 513 } 514 515 TYPE_TOKENS: t.ClassVar = { 516 TokenType.BIT, 517 TokenType.BOOLEAN, 518 TokenType.TINYINT, 519 TokenType.UTINYINT, 520 TokenType.SMALLINT, 521 TokenType.USMALLINT, 522 TokenType.INT, 523 TokenType.UINT, 524 TokenType.BIGINT, 525 TokenType.UBIGINT, 526 TokenType.BIGNUM, 527 TokenType.INT128, 528 TokenType.UINT128, 529 TokenType.INT256, 530 TokenType.UINT256, 531 TokenType.MEDIUMINT, 532 TokenType.UMEDIUMINT, 533 TokenType.FIXEDSTRING, 534 TokenType.FLOAT, 535 TokenType.DOUBLE, 536 TokenType.UDOUBLE, 537 TokenType.CHAR, 538 TokenType.NCHAR, 539 TokenType.VARCHAR, 540 TokenType.NVARCHAR, 541 TokenType.BPCHAR, 542 TokenType.TEXT, 543 TokenType.MEDIUMTEXT, 544 TokenType.LONGTEXT, 545 TokenType.BLOB, 546 TokenType.MEDIUMBLOB, 547 TokenType.LONGBLOB, 548 TokenType.BINARY, 549 TokenType.VARBINARY, 550 TokenType.JSON, 551 TokenType.JSONB, 552 TokenType.INTERVAL, 553 TokenType.TINYBLOB, 554 TokenType.TINYTEXT, 555 TokenType.TIME, 556 TokenType.TIMETZ, 557 TokenType.TIME_NS, 558 TokenType.TIMESTAMP, 559 TokenType.TIMESTAMP_S, 560 TokenType.TIMESTAMP_MS, 561 TokenType.TIMESTAMP_NS, 562 TokenType.TIMESTAMPTZ, 563 TokenType.TIMESTAMPLTZ, 564 TokenType.TIMESTAMPNTZ, 565 TokenType.DATETIME, 566 TokenType.DATETIME2, 567 TokenType.DATETIME64, 568 TokenType.SMALLDATETIME, 569 TokenType.DATE, 570 TokenType.DATE32, 571 TokenType.INT4RANGE, 572 TokenType.INT4MULTIRANGE, 573 TokenType.INT8RANGE, 574 TokenType.INT8MULTIRANGE, 575 TokenType.NUMRANGE, 576 TokenType.NUMMULTIRANGE, 577 TokenType.TSRANGE, 578 TokenType.TSMULTIRANGE, 579 TokenType.TSTZRANGE, 580 TokenType.TSTZMULTIRANGE, 581 TokenType.DATERANGE, 582 TokenType.DATEMULTIRANGE, 583 TokenType.DECIMAL, 584 TokenType.DECIMAL32, 585 TokenType.DECIMAL64, 586 TokenType.DECIMAL128, 587 TokenType.DECIMAL256, 588 TokenType.DECFLOAT, 589 TokenType.UDECIMAL, 590 TokenType.BIGDECIMAL, 591 TokenType.UUID, 592 TokenType.GEOGRAPHY, 593 TokenType.GEOGRAPHYPOINT, 594 TokenType.GEOMETRY, 595 TokenType.POINT, 596 TokenType.RING, 597 TokenType.LINESTRING, 598 TokenType.MULTILINESTRING, 599 TokenType.POLYGON, 600 TokenType.MULTIPOLYGON, 601 TokenType.HLLSKETCH, 602 TokenType.HSTORE, 603 TokenType.PSEUDO_TYPE, 604 TokenType.SUPER, 605 TokenType.SERIAL, 606 TokenType.SMALLSERIAL, 607 TokenType.BIGSERIAL, 608 TokenType.XML, 609 TokenType.YEAR, 610 TokenType.USERDEFINED, 611 TokenType.MONEY, 612 TokenType.SMALLMONEY, 613 TokenType.ROWVERSION, 614 TokenType.IMAGE, 615 TokenType.VARIANT, 616 TokenType.VECTOR, 617 TokenType.VOID, 618 TokenType.OBJECT, 619 TokenType.OBJECT_IDENTIFIER, 620 TokenType.INET, 621 TokenType.IPADDRESS, 622 TokenType.IPPREFIX, 623 TokenType.IPV4, 624 TokenType.IPV6, 625 TokenType.UNKNOWN, 626 TokenType.NOTHING, 627 TokenType.NULL, 628 TokenType.NAME, 629 TokenType.TDIGEST, 630 TokenType.DYNAMIC, 631 *ENUM_TYPE_TOKENS, 632 *NESTED_TYPE_TOKENS, 633 *AGGREGATE_TYPE_TOKENS, 634 } 635 636 SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = { 637 TokenType.BIGINT: TokenType.UBIGINT, 638 TokenType.INT: TokenType.UINT, 639 TokenType.MEDIUMINT: TokenType.UMEDIUMINT, 640 TokenType.SMALLINT: TokenType.USMALLINT, 641 TokenType.TINYINT: TokenType.UTINYINT, 642 TokenType.DECIMAL: TokenType.UDECIMAL, 643 TokenType.DOUBLE: TokenType.UDOUBLE, 644 } 645 646 SUBQUERY_PREDICATES: t.ClassVar = { 647 TokenType.ANY: exp.Any, 648 TokenType.ALL: exp.All, 649 TokenType.EXISTS: exp.Exists, 650 TokenType.SOME: exp.Any, 651 } 652 653 SUBQUERY_TOKENS: t.ClassVar = { 654 TokenType.SELECT, 655 TokenType.WITH, 656 TokenType.FROM, 657 } 658 659 RESERVED_TOKENS: t.ClassVar = { 660 *Tokenizer.SINGLE_TOKENS.values(), 661 TokenType.SELECT, 662 } - {TokenType.IDENTIFIER} 663 664 # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers, 665 # string literals), so they must never be treated as keywords when matching by text 666 TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset( 667 { 668 TokenType.BIT_STRING, 669 TokenType.BYTE_STRING, 670 TokenType.HEREDOC_STRING, 671 TokenType.HEX_STRING, 672 TokenType.IDENTIFIER, 673 TokenType.NATIONAL_STRING, 674 TokenType.RAW_STRING, 675 TokenType.STRING, 676 TokenType.UNICODE_STRING, 677 } 678 ) 679 680 DB_CREATABLES: t.ClassVar = { 681 TokenType.DATABASE, 682 TokenType.DICTIONARY, 683 TokenType.FILE_FORMAT, 684 TokenType.MODEL, 685 TokenType.NAMESPACE, 686 TokenType.SCHEMA, 687 TokenType.SEMANTIC_VIEW, 688 TokenType.SEQUENCE, 689 TokenType.SINK, 690 TokenType.SOURCE, 691 TokenType.STAGE, 692 TokenType.STORAGE_INTEGRATION, 693 TokenType.STREAMLIT, 694 TokenType.TABLE, 695 TokenType.TAG, 696 TokenType.VIEW, 697 TokenType.WAREHOUSE, 698 } 699 700 CREATABLES: t.ClassVar = { 701 TokenType.COLUMN, 702 TokenType.CONSTRAINT, 703 TokenType.FOREIGN_KEY, 704 TokenType.FUNCTION, 705 TokenType.INDEX, 706 TokenType.PROCEDURE, 707 TokenType.TRIGGER, 708 TokenType.TYPE, 709 *DB_CREATABLES, 710 } 711 712 TRIGGER_EVENTS: t.ClassVar = { 713 TokenType.INSERT, 714 TokenType.UPDATE, 715 TokenType.DELETE, 716 TokenType.TRUNCATE, 717 } 718 719 ALTERABLES: t.ClassVar = { 720 TokenType.INDEX, 721 TokenType.TABLE, 722 TokenType.VIEW, 723 TokenType.SESSION, 724 } 725 726 # Tokens that can represent identifiers 727 ID_VAR_TOKENS: t.ClassVar[set] = { 728 TokenType.ALL, 729 TokenType.ANALYZE, 730 TokenType.ATTACH, 731 TokenType.VAR, 732 TokenType.ANTI, 733 TokenType.APPLY, 734 TokenType.ASC, 735 TokenType.ASOF, 736 TokenType.AUTO_INCREMENT, 737 TokenType.BEGIN, 738 TokenType.BPCHAR, 739 TokenType.CACHE, 740 TokenType.CASE, 741 TokenType.COLLATE, 742 TokenType.COMMAND, 743 TokenType.COMMENT, 744 TokenType.COMMIT, 745 TokenType.CONSTRAINT, 746 TokenType.COPY, 747 TokenType.CUBE, 748 TokenType.CURRENT_SCHEMA, 749 TokenType.DECLARE, 750 TokenType.DEFAULT, 751 TokenType.DELETE, 752 TokenType.DESC, 753 TokenType.DESCRIBE, 754 TokenType.DETACH, 755 TokenType.DICTIONARY, 756 TokenType.DIV, 757 TokenType.END, 758 TokenType.EXECUTE, 759 TokenType.EXPORT, 760 TokenType.ESCAPE, 761 TokenType.FALSE, 762 TokenType.FIRST, 763 TokenType.FILE, 764 TokenType.FILTER, 765 TokenType.FINAL, 766 TokenType.FORMAT, 767 TokenType.FULL, 768 TokenType.GET, 769 TokenType.IDENTIFIER, 770 TokenType.INOUT, 771 TokenType.IS, 772 TokenType.ISNULL, 773 TokenType.INTERVAL, 774 TokenType.KEEP, 775 TokenType.KILL, 776 TokenType.LEFT, 777 TokenType.LIMIT, 778 TokenType.LOAD, 779 TokenType.LOCK, 780 TokenType.MATCH, 781 TokenType.MERGE, 782 TokenType.NATURAL, 783 TokenType.NEXT, 784 TokenType.OFFSET, 785 TokenType.OPERATOR, 786 TokenType.ORDINALITY, 787 TokenType.OUT, 788 TokenType.OVER, 789 TokenType.OVERLAPS, 790 TokenType.OVERWRITE, 791 TokenType.PARTITION, 792 TokenType.PERCENT, 793 TokenType.PIVOT, 794 TokenType.PROJECTION, 795 TokenType.PRAGMA, 796 TokenType.PUT, 797 TokenType.RANGE, 798 TokenType.RECURSIVE, 799 TokenType.REFERENCES, 800 TokenType.REFRESH, 801 TokenType.RENAME, 802 TokenType.REPLACE, 803 TokenType.RIGHT, 804 TokenType.ROLLUP, 805 TokenType.ROW, 806 TokenType.ROWS, 807 TokenType.SEMI, 808 TokenType.SET, 809 TokenType.SETTINGS, 810 TokenType.SHOW, 811 TokenType.STREAM, 812 TokenType.STREAMLIT, 813 TokenType.TEMPORARY, 814 TokenType.TOP, 815 TokenType.TRUE, 816 TokenType.TRUNCATE, 817 TokenType.UNIQUE, 818 TokenType.UNNEST, 819 TokenType.UNPIVOT, 820 TokenType.UPDATE, 821 TokenType.USE, 822 TokenType.VOLATILE, 823 TokenType.WINDOW, 824 TokenType.CURRENT_CATALOG, 825 TokenType.LOCALTIME, 826 TokenType.LOCALTIMESTAMP, 827 TokenType.SESSION_USER, 828 TokenType.STRAIGHT_JOIN, 829 *ALTERABLES, 830 *CREATABLES, 831 *SUBQUERY_PREDICATES, 832 *TYPE_TOKENS, 833 *NO_PAREN_FUNCTIONS, 834 } - {TokenType.UNION} 835 836 TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - { 837 TokenType.ANTI, 838 TokenType.ASOF, 839 TokenType.FULL, 840 TokenType.LEFT, 841 TokenType.LOCK, 842 TokenType.NATURAL, 843 TokenType.RIGHT, 844 TokenType.SEMI, 845 TokenType.WINDOW, 846 } 847 848 ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS 849 850 COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS 851 852 ARRAY_CONSTRUCTORS: t.ClassVar = { 853 "ARRAY": exp.Array, 854 "LIST": exp.List, 855 } 856 857 COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS} 858 859 UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET} 860 861 TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"} 862 863 # Tokens that indicate a simple column reference 864 IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER}) 865 866 BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE}) 867 868 # Postfix tokens that prevent the bare column fast path 869 COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 870 { 871 TokenType.L_PAREN, 872 TokenType.L_BRACKET, 873 TokenType.L_BRACE, 874 TokenType.COLON, 875 TokenType.JOIN_MARKER, 876 } 877 ) 878 879 TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 880 { 881 TokenType.L_PAREN, 882 TokenType.L_BRACKET, 883 TokenType.L_BRACE, 884 TokenType.PIVOT, 885 TokenType.UNPIVOT, 886 TokenType.TABLE_SAMPLE, 887 } 888 ) 889 890 FUNC_TOKENS: t.ClassVar = { 891 TokenType.COLLATE, 892 TokenType.COMMAND, 893 TokenType.CURRENT_DATE, 894 TokenType.CURRENT_DATETIME, 895 TokenType.CURRENT_SCHEMA, 896 TokenType.CURRENT_TIMESTAMP, 897 TokenType.CURRENT_TIME, 898 TokenType.CURRENT_USER, 899 TokenType.CURRENT_CATALOG, 900 TokenType.DECLARE, 901 TokenType.FILTER, 902 TokenType.FIRST, 903 TokenType.FORMAT, 904 TokenType.GET, 905 TokenType.GLOB, 906 TokenType.IDENTIFIER, 907 TokenType.INDEX, 908 TokenType.ISNULL, 909 TokenType.ILIKE, 910 TokenType.INSERT, 911 TokenType.LIKE, 912 TokenType.LOCALTIME, 913 TokenType.LOCALTIMESTAMP, 914 TokenType.MERGE, 915 TokenType.NEXT, 916 TokenType.OFFSET, 917 TokenType.PRIMARY_KEY, 918 TokenType.RANGE, 919 TokenType.REPLACE, 920 TokenType.RLIKE, 921 TokenType.ROW, 922 TokenType.SESSION_USER, 923 TokenType.UNNEST, 924 TokenType.VAR, 925 TokenType.LEFT, 926 TokenType.RIGHT, 927 TokenType.SEQUENCE, 928 TokenType.DATE, 929 TokenType.DATETIME, 930 TokenType.TABLE, 931 TokenType.TIMESTAMP, 932 TokenType.TIMESTAMPTZ, 933 TokenType.TRUNCATE, 934 TokenType.UTC_DATE, 935 TokenType.UTC_TIME, 936 TokenType.UTC_TIMESTAMP, 937 TokenType.WINDOW, 938 TokenType.XOR, 939 *TYPE_TOKENS, 940 *SUBQUERY_PREDICATES, 941 } 942 943 CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 944 TokenType.AND: exp.And, 945 } 946 947 ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 948 TokenType.COLON_EQ: exp.PropertyEQ, 949 } 950 951 DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 952 TokenType.OR: exp.Or, 953 } 954 955 EQUALITY: t.ClassVar = { 956 TokenType.EQ: exp.EQ, 957 TokenType.NEQ: exp.NEQ, 958 TokenType.NULLSAFE_EQ: exp.NullSafeEQ, 959 } 960 961 COMPARISON: t.ClassVar = { 962 TokenType.GT: exp.GT, 963 TokenType.GTE: exp.GTE, 964 TokenType.LT: exp.LT, 965 TokenType.LTE: exp.LTE, 966 } 967 968 BITWISE: t.ClassVar = { 969 TokenType.AMP: exp.BitwiseAnd, 970 TokenType.CARET: exp.BitwiseXor, 971 TokenType.PIPE: exp.BitwiseOr, 972 } 973 974 TERM: t.ClassVar = { 975 TokenType.DASH: exp.Sub, 976 TokenType.PLUS: exp.Add, 977 TokenType.MOD: exp.Mod, 978 TokenType.COLLATE: exp.Collate, 979 } 980 981 FACTOR: t.ClassVar = { 982 TokenType.DIV: exp.IntDiv, 983 TokenType.LR_ARROW: exp.Distance, 984 TokenType.LLRR_ARROW: exp.DistanceNd, 985 TokenType.SLASH: exp.Div, 986 TokenType.STAR: exp.Mul, 987 } 988 989 EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {} 990 991 TIMES: t.ClassVar = { 992 TokenType.TIME, 993 TokenType.TIMETZ, 994 } 995 996 TIMESTAMPS: t.ClassVar = { 997 TokenType.TIMESTAMP, 998 TokenType.TIMESTAMPNTZ, 999 TokenType.TIMESTAMPTZ, 1000 TokenType.TIMESTAMPLTZ, 1001 *TIMES, 1002 } 1003 1004 SET_OPERATIONS: t.ClassVar = { 1005 TokenType.UNION, 1006 TokenType.INTERSECT, 1007 TokenType.EXCEPT, 1008 } 1009 1010 JOIN_METHODS: t.ClassVar = { 1011 TokenType.ASOF, 1012 TokenType.NATURAL, 1013 TokenType.POSITIONAL, 1014 } 1015 1016 JOIN_SIDES: t.ClassVar = { 1017 TokenType.LEFT, 1018 TokenType.RIGHT, 1019 TokenType.FULL, 1020 } 1021 1022 JOIN_KINDS: t.ClassVar = { 1023 TokenType.ANTI, 1024 TokenType.CROSS, 1025 TokenType.INNER, 1026 TokenType.OUTER, 1027 TokenType.SEMI, 1028 TokenType.STRAIGHT_JOIN, 1029 } 1030 1031 JOIN_HINTS: t.ClassVar[set[str]] = set() 1032 1033 # Tokens that unambiguously end a table reference on the fast path 1034 TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset( 1035 { 1036 TokenType.COMMA, 1037 TokenType.GROUP_BY, 1038 TokenType.HAVING, 1039 TokenType.JOIN, 1040 TokenType.LIMIT, 1041 TokenType.ON, 1042 TokenType.ORDER_BY, 1043 TokenType.R_PAREN, 1044 TokenType.SEMICOLON, 1045 TokenType.SENTINEL, 1046 TokenType.WHERE, 1047 *SET_OPERATIONS, 1048 *JOIN_KINDS, 1049 *JOIN_METHODS, 1050 *JOIN_SIDES, 1051 } 1052 ) 1053 1054 LAMBDAS: t.ClassVar = { 1055 TokenType.ARROW: lambda self, expressions: self.expression( 1056 exp.Lambda( 1057 this=self._replace_lambda( 1058 self._parse_disjunction(), 1059 expressions, 1060 ), 1061 expressions=expressions, 1062 ) 1063 ), 1064 TokenType.FARROW: lambda self, expressions: self.expression( 1065 exp.Kwarg( 1066 this=exp.var(expressions[0].name), 1067 expression=self._parse_disjunction() or self._parse_select(), 1068 ) 1069 ), 1070 } 1071 1072 # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake 1073 TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False 1074 1075 LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN}) 1076 1077 COLUMN_OPERATORS: t.ClassVar = { 1078 TokenType.DOT: None, 1079 TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)), 1080 TokenType.DCOLON: lambda self, this, to: self.build_cast( 1081 strict=self.STRICT_CAST, this=this, to=to 1082 ), 1083 TokenType.ARROW: lambda self, this, path: self.expression( 1084 exp.JSONExtract( 1085 this=this, 1086 expression=self.dialect.to_json_path(path), 1087 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1088 ) 1089 ), 1090 TokenType.DARROW: lambda self, this, path: self.expression( 1091 exp.JSONExtractScalar( 1092 this=this, 1093 expression=self.dialect.to_json_path(path), 1094 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1095 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 1096 ) 1097 ), 1098 TokenType.HASH_ARROW: lambda self, this, path: self.expression( 1099 exp.JSONBExtract(this=this, expression=path) 1100 ), 1101 TokenType.DHASH_ARROW: lambda self, this, path: self.expression( 1102 exp.JSONBExtractScalar(this=this, expression=path) 1103 ), 1104 TokenType.PLACEHOLDER: lambda self, this, key: self.expression( 1105 exp.JSONBContains(this=this, expression=key) 1106 ), 1107 } 1108 1109 # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator" 1110 # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs). 1111 JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {} 1112 1113 CAST_COLUMN_OPERATORS: t.ClassVar = { 1114 TokenType.DOTCOLON, 1115 TokenType.DCOLON, 1116 } 1117 1118 EXPRESSION_PARSERS: t.ClassVar = { 1119 exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY), 1120 exp.Column: lambda self: self._parse_column(), 1121 exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()), 1122 exp.Condition: lambda self: self._parse_disjunction(), 1123 exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True), 1124 exp.Expr: lambda self: self._parse_expression(), 1125 exp.From: lambda self: self._parse_from(joins=True), 1126 exp.GrantPrincipal: lambda self: self._parse_grant_principal(), 1127 exp.GrantPrivilege: lambda self: self._parse_grant_privilege(), 1128 exp.Group: lambda self: self._parse_group(), 1129 exp.Having: lambda self: self._parse_having(), 1130 exp.Hint: lambda self: self._parse_hint_body(), 1131 exp.Identifier: lambda self: self._parse_id_var(), 1132 exp.Join: lambda self: self._parse_join(), 1133 exp.Lambda: lambda self: self._parse_lambda(), 1134 exp.Lateral: lambda self: self._parse_lateral(), 1135 exp.Limit: lambda self: self._parse_limit(), 1136 exp.Offset: lambda self: self._parse_offset(), 1137 exp.Order: lambda self: self._parse_order(), 1138 exp.Ordered: lambda self: self._parse_ordered(), 1139 exp.Properties: lambda self: self._parse_properties(), 1140 exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(), 1141 exp.Qualify: lambda self: self._parse_qualify(), 1142 exp.Returning: lambda self: self._parse_returning(), 1143 exp.Select: lambda self: self._parse_select(), 1144 exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY), 1145 exp.Table: lambda self: self._parse_table_parts(), 1146 exp.TableAlias: lambda self: self._parse_table_alias(), 1147 exp.Tuple: lambda self: self._parse_value(values=False), 1148 exp.Whens: lambda self: self._parse_when_matched(), 1149 exp.Where: lambda self: self._parse_where(), 1150 exp.Window: lambda self: self._parse_named_window(), 1151 exp.With: lambda self: self._parse_with(), 1152 } 1153 1154 STATEMENT_PARSERS: t.ClassVar = { 1155 TokenType.ALTER: lambda self: self._parse_alter(), 1156 TokenType.ANALYZE: lambda self: self._parse_analyze(), 1157 TokenType.BEGIN: lambda self: self._parse_transaction(), 1158 TokenType.CACHE: lambda self: self._parse_cache(), 1159 TokenType.COMMENT: lambda self: self._parse_comment(), 1160 TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(), 1161 TokenType.COPY: lambda self: self._parse_copy(), 1162 TokenType.CREATE: lambda self: self._parse_create(), 1163 TokenType.DECLARE: lambda self: self._parse_declare(), 1164 TokenType.DELETE: lambda self: self._parse_delete(), 1165 TokenType.DESC: lambda self: self._parse_describe(), 1166 TokenType.DESCRIBE: lambda self: self._parse_describe(), 1167 TokenType.DROP: lambda self: self._parse_drop(), 1168 TokenType.GRANT: lambda self: self._parse_grant(), 1169 TokenType.REVOKE: lambda self: self._parse_revoke(), 1170 TokenType.INSERT: lambda self: self._parse_insert(), 1171 TokenType.KILL: lambda self: self._parse_kill(), 1172 TokenType.LOAD: lambda self: self._parse_load(), 1173 TokenType.MERGE: lambda self: self._parse_merge(), 1174 TokenType.PIVOT: lambda self: self._parse_simplified_pivot(), 1175 TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())), 1176 TokenType.REFRESH: lambda self: self._parse_refresh(), 1177 TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(), 1178 TokenType.SET: lambda self: self._parse_set(), 1179 TokenType.TRUNCATE: lambda self: self._parse_truncate_table(), 1180 TokenType.UNCACHE: lambda self: self._parse_uncache(), 1181 TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True), 1182 TokenType.UPDATE: lambda self: self._parse_update(), 1183 TokenType.USE: lambda self: self._parse_use(), 1184 TokenType.SEMICOLON: lambda self: exp.Semicolon(), 1185 } 1186 1187 UNARY_PARSERS: t.ClassVar = { 1188 TokenType.PLUS: lambda self: self._parse_unary(), # Unary + is handled as a no-op 1189 TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())), 1190 TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())), 1191 TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())), 1192 TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())), 1193 TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())), 1194 } 1195 1196 STRING_PARSERS: t.ClassVar = { 1197 TokenType.HEREDOC_STRING: lambda self, token: self.expression( 1198 exp.RawString(this=token.text), token 1199 ), 1200 TokenType.NATIONAL_STRING: lambda self, token: self.expression( 1201 exp.National(this=token.text), token 1202 ), 1203 TokenType.RAW_STRING: lambda self, token: self.expression( 1204 exp.RawString(this=token.text), token 1205 ), 1206 TokenType.STRING: lambda self, token: self.expression( 1207 exp.Literal(this=token.text, is_string=True), token 1208 ), 1209 TokenType.UNICODE_STRING: lambda self, token: self.expression( 1210 exp.UnicodeString( 1211 this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string() 1212 ), 1213 token, 1214 ), 1215 } 1216 1217 NUMERIC_PARSERS: t.ClassVar = { 1218 TokenType.BIT_STRING: lambda self, token: self.expression( 1219 exp.BitString(this=token.text), token 1220 ), 1221 TokenType.BYTE_STRING: lambda self, token: self.expression( 1222 exp.ByteString( 1223 this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None 1224 ), 1225 token, 1226 ), 1227 TokenType.HEX_STRING: lambda self, token: self.expression( 1228 exp.HexString( 1229 this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None 1230 ), 1231 token, 1232 ), 1233 TokenType.NUMBER: lambda self, token: self.expression( 1234 exp.Literal(this=token.text, is_string=False), token 1235 ), 1236 } 1237 1238 PRIMARY_PARSERS: t.ClassVar = { 1239 **STRING_PARSERS, 1240 **NUMERIC_PARSERS, 1241 TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token), 1242 TokenType.NULL: lambda self, _: self.expression(exp.Null()), 1243 TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)), 1244 TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)), 1245 TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(), 1246 TokenType.STAR: lambda self, _: self._parse_star_ops(), 1247 } 1248 1249 PLACEHOLDER_PARSERS: t.ClassVar = { 1250 TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()), 1251 TokenType.PARAMETER: lambda self: self._parse_parameter(), 1252 TokenType.COLON: lambda self: ( 1253 self.expression(exp.Placeholder(this=self._prev.text)) 1254 if self._match_set(self.COLON_PLACEHOLDER_TOKENS) 1255 else None 1256 ), 1257 } 1258 1259 RANGE_PARSERS: t.ClassVar = { 1260 TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll), 1261 TokenType.BETWEEN: lambda self, this: self._parse_between(this), 1262 TokenType.GLOB: binary_range_parser(exp.Glob), 1263 TokenType.ILIKE: binary_range_parser(exp.ILike), 1264 TokenType.IN: lambda self, this: self._parse_in(this), 1265 TokenType.IRLIKE: binary_range_parser(exp.RegexpILike), 1266 TokenType.IS: lambda self, this: self._parse_is(this), 1267 TokenType.LIKE: binary_range_parser(exp.Like), 1268 TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy), 1269 TokenType.OVERLAPS: binary_range_parser(exp.Overlaps), 1270 TokenType.RLIKE: binary_range_parser(exp.RegexpLike), 1271 TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo), 1272 TokenType.FOR: lambda self, this: self._parse_comprehension(this), 1273 TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys), 1274 TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys), 1275 TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath), 1276 TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists), 1277 TokenType.ADJACENT: binary_range_parser(exp.Adjacent), 1278 TokenType.OPERATOR: lambda self, this: self._parse_operator(this), 1279 TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft), 1280 TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight), 1281 } 1282 1283 PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = { 1284 "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query), 1285 "AS": lambda self, query: self._build_pipe_cte( 1286 query, [exp.Star()], self._parse_table_alias() 1287 ), 1288 "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False), 1289 "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query), 1290 "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query), 1291 "ORDER BY": lambda self, query: query.order_by( 1292 self._parse_order(), append=False, copy=False 1293 ), 1294 "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1295 "SELECT": lambda self, query: self._parse_pipe_syntax_select(query), 1296 "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query), 1297 "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1298 "WHERE": lambda self, query: query.where(self._parse_where(), copy=False), 1299 } 1300 1301 PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1302 "ALLOWED_VALUES": lambda self: self.expression( 1303 exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary)) 1304 ), 1305 "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty), 1306 "AUTO": lambda self: self._parse_auto_property(), 1307 "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty), 1308 "BACKUP": lambda self: self.expression( 1309 exp.BackupProperty(this=self._parse_var(any_token=True)) 1310 ), 1311 "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(), 1312 "CALLED": lambda self: self._parse_called_on_null_input_property(), 1313 "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs), 1314 "CHECKSUM": lambda self: self._parse_checksum(), 1315 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1316 "CLUSTERED": lambda self: self._parse_clustered_by(), 1317 "COLLATE": lambda self, **kwargs: self._parse_property_assignment( 1318 exp.CollateProperty, **kwargs 1319 ), 1320 "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty), 1321 "CONTAINS": lambda self: self._parse_contains_property(), 1322 "COPY": lambda self: self._parse_copy_property(), 1323 "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs), 1324 "DATA_DELETION": lambda self: self._parse_data_deletion_property(), 1325 "DEFINER": lambda self: self._parse_definer(), 1326 "DETERMINISTIC": lambda self: self.expression( 1327 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1328 ), 1329 "DISTRIBUTED": lambda self: self._parse_distributed_property(), 1330 "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty), 1331 "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()), 1332 "DISTKEY": lambda self: self._parse_distkey(), 1333 "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty), 1334 "EMPTY": lambda self: self.expression(exp.EmptyProperty()), 1335 "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty), 1336 "ENVIRONMENT": lambda self: self.expression( 1337 exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment)) 1338 ), 1339 "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty), 1340 "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty), 1341 "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()), 1342 "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs), 1343 "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1344 "FREESPACE": lambda self: self._parse_freespace(), 1345 "GLOBAL": lambda self: self.expression(exp.GlobalProperty()), 1346 "HEAP": lambda self: self.expression(exp.HeapProperty()), 1347 "ICEBERG": lambda self: self.expression(exp.IcebergProperty()), 1348 "IMMUTABLE": lambda self: self.expression( 1349 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1350 ), 1351 "INHERITS": lambda self: self.expression( 1352 exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table)) 1353 ), 1354 "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())), 1355 "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs), 1356 "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty), 1357 "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"), 1358 "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"), 1359 "LIKE": lambda self: self._parse_create_like(), 1360 "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty), 1361 "LOCK": lambda self: self._parse_locking(), 1362 "LOCKING": lambda self: self._parse_locking(), 1363 "LOG": lambda self, **kwargs: self._parse_log(**kwargs), 1364 "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()), 1365 "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs), 1366 "MODIFIES": lambda self: self._parse_modifies_property(), 1367 "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)), 1368 "NO": lambda self: self._parse_no_property(), 1369 "ON": lambda self: self._parse_on_property(), 1370 "ORDER BY": lambda self: self._parse_order(skip_order_token=True), 1371 "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())), 1372 "PARTITION": lambda self: self._parse_partitioned_of(), 1373 "PARTITION BY": lambda self: self._parse_partitioned_by(), 1374 "PARTITIONED BY": lambda self: self._parse_partitioned_by(), 1375 "PARTITIONED_BY": lambda self: self._parse_partitioned_by(), 1376 "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True), 1377 "RANGE": lambda self: self._parse_dict_range(this="RANGE"), 1378 "READS": lambda self: self._parse_reads_property(), 1379 "REMOTE": lambda self: self._parse_remote_with_connection(), 1380 "RETURNS": lambda self: self._parse_returns(), 1381 "STRICT": lambda self: self.expression(exp.StrictProperty()), 1382 "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()), 1383 "ROW": lambda self: self._parse_row(), 1384 "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty), 1385 "SAMPLE": lambda self: self.expression( 1386 exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise()) 1387 ), 1388 "SECURE": lambda self: self.expression(exp.SecureProperty()), 1389 "SECURITY": lambda self: self._parse_sql_security(), 1390 "SQL SECURITY": lambda self: self._parse_sql_security(), 1391 "SET": lambda self: self.expression(exp.SetProperty(multi=False)), 1392 "SETTINGS": lambda self: self._parse_settings_property(), 1393 "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty), 1394 "SORTKEY": lambda self: self._parse_sortkey(), 1395 "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"), 1396 "STABLE": lambda self: self.expression( 1397 exp.StabilityProperty(this=exp.Literal.string("STABLE")) 1398 ), 1399 "STORED": lambda self: self._parse_stored(), 1400 "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(), 1401 "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(), 1402 "TEMP": lambda self: self.expression(exp.TemporaryProperty()), 1403 "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()), 1404 "TO": lambda self: self._parse_to_table(), 1405 "TRANSIENT": lambda self: self.expression(exp.TransientProperty()), 1406 "TRANSFORM": lambda self: self.expression( 1407 exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression)) 1408 ), 1409 "TTL": lambda self: self._parse_ttl(), 1410 "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1411 "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()), 1412 "VOLATILE": lambda self: self._parse_volatile_property(), 1413 "WITH": lambda self: self._parse_with_property(), 1414 } 1415 1416 CONSTRAINT_PARSERS: t.ClassVar = { 1417 "AUTOINCREMENT": lambda self: self._parse_auto_increment(), 1418 "AUTO_INCREMENT": lambda self: self._parse_auto_increment(), 1419 "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)), 1420 "CHECK": lambda self: self._parse_check_constraint(), 1421 "COLLATE": lambda self: self.expression( 1422 exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column()) 1423 ), 1424 "COMMENT": lambda self: self.expression( 1425 exp.CommentColumnConstraint(this=self._parse_string()) 1426 ), 1427 "COMPRESS": lambda self: self._parse_compress(), 1428 "CLUSTERED": lambda self: self.expression( 1429 exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1430 ), 1431 "NONCLUSTERED": lambda self: self.expression( 1432 exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1433 ), 1434 "DEFAULT": lambda self: self.expression( 1435 exp.DefaultColumnConstraint(this=self._parse_bitwise()) 1436 ), 1437 "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())), 1438 "EPHEMERAL": lambda self: self.expression( 1439 exp.EphemeralColumnConstraint(this=self._parse_bitwise()) 1440 ), 1441 "EXCLUDE": lambda self: self.expression( 1442 exp.ExcludeColumnConstraint(this=self._parse_index_params()) 1443 ), 1444 "FOREIGN KEY": lambda self: self._parse_foreign_key(), 1445 "FORMAT": lambda self: self.expression( 1446 exp.DateFormatColumnConstraint(this=self._parse_var_or_string()) 1447 ), 1448 "GENERATED": lambda self: self._parse_generated_as_identity(), 1449 "IDENTITY": lambda self: self._parse_auto_increment(), 1450 "INLINE": lambda self: self._parse_inline(), 1451 "LIKE": lambda self: self._parse_create_like(), 1452 "NOT": lambda self: self._parse_not_constraint(), 1453 "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)), 1454 "ON": lambda self: ( 1455 ( 1456 self._match(TokenType.UPDATE) 1457 and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function())) 1458 ) 1459 or self.expression(exp.OnProperty(this=self._parse_id_var())) 1460 ), 1461 "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())), 1462 "PERIOD": lambda self: self._parse_period_for_system_time(), 1463 "PRIMARY KEY": lambda self: self._parse_primary_key(), 1464 "REFERENCES": lambda self: self._parse_references(match=False), 1465 "TITLE": lambda self: self.expression( 1466 exp.TitleColumnConstraint(this=self._parse_var_or_string()) 1467 ), 1468 "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])), 1469 "UNIQUE": lambda self: self._parse_unique(), 1470 "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()), 1471 "WITH": lambda self: self.expression( 1472 exp.Properties(expressions=self._parse_wrapped_properties()) 1473 ), 1474 "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1475 "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1476 } 1477 1478 def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None: 1479 if not self._match(TokenType.L_PAREN, advance=False): 1480 # Partitioning by bucket or truncate follows the syntax: 1481 # PARTITION BY (BUCKET(..) | TRUNCATE(..)) 1482 # If we don't have parenthesis after each keyword, we should instead parse this as an identifier 1483 self._retreat(self._index - 1) 1484 return None 1485 1486 klass = ( 1487 exp.PartitionedByBucket 1488 if self._prev.text.upper() == "BUCKET" 1489 else exp.PartitionByTruncate 1490 ) 1491 1492 args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column()) 1493 this, expression = seq_get(args, 0), seq_get(args, 1) 1494 1495 if isinstance(this, exp.Literal): 1496 # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order 1497 # - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)` 1498 # - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)` 1499 # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)` 1500 # 1501 # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning 1502 # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties 1503 this, expression = expression, this 1504 1505 return self.expression(klass(this=this, expression=expression)) 1506 1507 ALTER_PARSERS: t.ClassVar = { 1508 "ADD": lambda self: self._parse_alter_table_add(), 1509 "AS": lambda self: self._parse_select(), 1510 "ALTER": lambda self: self._parse_alter_table_alter(), 1511 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1512 "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())), 1513 "DROP": lambda self: self._parse_alter_table_drop(), 1514 "RENAME": lambda self: self._parse_alter_table_rename(), 1515 "SET": lambda self: self._parse_alter_table_set(), 1516 "SWAP": lambda self: self.expression( 1517 exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True)) 1518 ), 1519 } 1520 1521 ALTER_ALTER_PARSERS: t.ClassVar = { 1522 "DISTKEY": lambda self: self._parse_alter_diststyle(), 1523 "DISTSTYLE": lambda self: self._parse_alter_diststyle(), 1524 "SORTKEY": lambda self: self._parse_alter_sortkey(), 1525 "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True), 1526 } 1527 1528 SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = { 1529 "CHECK", 1530 "EXCLUDE", 1531 "FOREIGN KEY", 1532 "LIKE", 1533 "PERIOD", 1534 "PRIMARY KEY", 1535 "UNIQUE", 1536 "BUCKET", 1537 "TRUNCATE", 1538 } 1539 1540 NO_PAREN_FUNCTION_PARSERS: t.ClassVar = { 1541 "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())), 1542 "CASE": lambda self: self._parse_case(), 1543 "CONNECT_BY_ROOT": lambda self: self.expression( 1544 exp.ConnectByRoot(this=self._parse_column()) 1545 ), 1546 "IF": lambda self: self._parse_if(), 1547 } 1548 1549 INVALID_FUNC_NAME_TOKENS: t.ClassVar = { 1550 TokenType.IDENTIFIER, 1551 TokenType.STRING, 1552 } 1553 1554 FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"} 1555 1556 KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice) 1557 1558 FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1559 **{ 1560 name: lambda self: self._parse_distinct_arg_function(exp.ArgMax) 1561 for name in exp.ArgMax.sql_names() 1562 }, 1563 **{ 1564 name: lambda self: self._parse_distinct_arg_function(exp.ArgMin) 1565 for name in exp.ArgMin.sql_names() 1566 }, 1567 "CAST": lambda self: self._parse_cast(self.STRICT_CAST), 1568 "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil), 1569 "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST), 1570 "CHAR": lambda self: self._parse_char(), 1571 "CHR": lambda self: self._parse_char(), 1572 "DECODE": lambda self: self._parse_decode(), 1573 "EXTRACT": lambda self: self._parse_extract(), 1574 "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor), 1575 "GAP_FILL": lambda self: self._parse_gap_fill(), 1576 "INITCAP": lambda self: self._parse_initcap(), 1577 "JSON_OBJECT": lambda self: self._parse_json_object(), 1578 "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True), 1579 "JSON_TABLE": lambda self: self._parse_json_table(), 1580 "MATCH": lambda self: self._parse_match_against(), 1581 "NORMALIZE": lambda self: self._parse_normalize(), 1582 "OPENJSON": lambda self: self._parse_open_json(), 1583 "OVERLAY": lambda self: self._parse_overlay(), 1584 "POSITION": lambda self: self._parse_position(), 1585 "SAFE_CAST": lambda self: self._parse_cast(False, safe=True), 1586 "STRING_AGG": lambda self: self._parse_string_agg(), 1587 "SUBSTRING": lambda self: self._parse_substring(), 1588 "TRIM": lambda self: self._parse_trim(), 1589 "TRY_CAST": lambda self: self._parse_cast(False, safe=True), 1590 "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True), 1591 "XMLELEMENT": lambda self: self._parse_xml_element(), 1592 "XMLTABLE": lambda self: self._parse_xml_table(), 1593 } 1594 1595 QUERY_MODIFIER_PARSERS: t.ClassVar = { 1596 TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()), 1597 TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()), 1598 TokenType.WHERE: lambda self: ("where", self._parse_where()), 1599 TokenType.GROUP_BY: lambda self: ("group", self._parse_group()), 1600 TokenType.HAVING: lambda self: ("having", self._parse_having()), 1601 TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()), 1602 TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()), 1603 TokenType.ORDER_BY: lambda self: ("order", self._parse_order()), 1604 TokenType.LIMIT: lambda self: ("limit", self._parse_limit()), 1605 TokenType.FETCH: lambda self: ("limit", self._parse_limit()), 1606 TokenType.OFFSET: lambda self: ("offset", self._parse_offset()), 1607 TokenType.FOR: lambda self: ("locks", self._parse_locks()), 1608 TokenType.LOCK: lambda self: ("locks", self._parse_locks()), 1609 TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1610 TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1611 TokenType.CLUSTER_BY: lambda self: ( 1612 "cluster", 1613 self._parse_cluster(), 1614 ), 1615 TokenType.DISTRIBUTE_BY: lambda self: ( 1616 "distribute", 1617 self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY), 1618 ), 1619 TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)), 1620 TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)), 1621 } 1622 QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS) 1623 1624 SET_PARSERS: t.ClassVar = { 1625 "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"), 1626 "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"), 1627 "SESSION": lambda self: self._parse_set_item_assignment("SESSION"), 1628 "TRANSACTION": lambda self: self._parse_set_transaction(), 1629 } 1630 1631 SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {} 1632 1633 TYPE_LITERAL_PARSERS: t.ClassVar = { 1634 exp.DType.JSON: lambda self, this, _: self.expression(exp.ParseJSON(this=this)), 1635 } 1636 1637 TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {} 1638 1639 DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN} 1640 1641 PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE} 1642 1643 TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"} 1644 TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = { 1645 "ISOLATION": ( 1646 ("LEVEL", "REPEATABLE", "READ"), 1647 ("LEVEL", "READ", "COMMITTED"), 1648 ("LEVEL", "READ", "UNCOMITTED"), 1649 ("LEVEL", "SERIALIZABLE"), 1650 ), 1651 "READ": ("WRITE", "ONLY"), 1652 } 1653 1654 CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = { 1655 **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()), 1656 "DO": ("NOTHING", "UPDATE"), 1657 } 1658 1659 TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = { 1660 "INSTEAD": (("OF",),), 1661 "BEFORE": tuple(), 1662 "AFTER": tuple(), 1663 } 1664 1665 TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = { 1666 "NOT": (("DEFERRABLE",),), 1667 "DEFERRABLE": tuple(), 1668 } 1669 1670 CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = { 1671 "SCALE": ("EXTEND", "NOEXTEND"), 1672 "SHARD": ("EXTEND", "NOEXTEND"), 1673 "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"), 1674 **dict.fromkeys( 1675 ( 1676 "SESSION", 1677 "GLOBAL", 1678 "KEEP", 1679 "NOKEEP", 1680 "ORDER", 1681 "NOORDER", 1682 "NOCACHE", 1683 "CYCLE", 1684 "NOCYCLE", 1685 "NOMINVALUE", 1686 "NOMAXVALUE", 1687 "NOSCALE", 1688 "NOSHARD", 1689 ), 1690 tuple(), 1691 ), 1692 } 1693 1694 ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")} 1695 1696 USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1697 ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple() 1698 ) 1699 1700 CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",)) 1701 1702 SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1703 "TYPE": ("EVOLUTION",), 1704 **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()), 1705 } 1706 1707 PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {} 1708 1709 EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1710 ("CALLER", "SELF", "OWNER"), tuple() 1711 ) 1712 1713 KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1714 "NOT": ("ENFORCED",), 1715 "MATCH": ( 1716 "FULL", 1717 "PARTIAL", 1718 "SIMPLE", 1719 ), 1720 "INITIALLY": ("DEFERRED", "IMMEDIATE"), 1721 "USING": ( 1722 "BTREE", 1723 "HASH", 1724 ), 1725 **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()), 1726 } 1727 1728 WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1729 "NO": ("OTHERS",), 1730 "CURRENT": ("ROW",), 1731 **dict.fromkeys(("GROUP", "TIES"), tuple()), 1732 } 1733 1734 INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"} 1735 1736 CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"} 1737 # Time travel clause prefixes, mapped to whether they pin a timestamp or a version 1738 VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = { 1739 ("FOR", "SYSTEM_TIME"): "TIMESTAMP", 1740 ("FOR", "SYSTEM", "TIME"): "TIMESTAMP", 1741 ("FOR", "TIMESTAMP"): "TIMESTAMP", 1742 ("FOR", "VERSION"): "VERSION", 1743 ("TIMESTAMP", "AS", "OF"): "TIMESTAMP", 1744 ("VERSION", "AS", "OF"): "VERSION", 1745 } 1746 1747 HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"} 1748 HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"} 1749 1750 OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"} 1751 1752 OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN} 1753 1754 TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE} 1755 1756 VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"} 1757 1758 WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS} 1759 WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER} 1760 WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"} 1761 1762 JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS} 1763 1764 FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT} 1765 1766 ADD_CONSTRAINT_TOKENS: t.ClassVar = { 1767 TokenType.CONSTRAINT, 1768 TokenType.FOREIGN_KEY, 1769 TokenType.INDEX, 1770 TokenType.KEY, 1771 TokenType.PRIMARY_KEY, 1772 TokenType.UNIQUE, 1773 } 1774 1775 DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT} 1776 1777 UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS 1778 1779 SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT} 1780 1781 COPY_INTO_VARLEN_OPTIONS: t.ClassVar = { 1782 "FILE_FORMAT", 1783 "COPY_OPTIONS", 1784 "FORMAT_OPTIONS", 1785 "CREDENTIAL", 1786 } 1787 1788 IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"} 1789 1790 ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {} 1791 1792 ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"} 1793 1794 PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN} 1795 1796 # The style options for the DESCRIBE statement 1797 DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"} 1798 1799 SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"} 1800 1801 # The style options for the ANALYZE statement 1802 ANALYZE_STYLES: t.ClassVar = { 1803 "BUFFER_USAGE_LIMIT", 1804 "FULL", 1805 "LOCAL", 1806 "NO_WRITE_TO_BINLOG", 1807 "SAMPLE", 1808 "SKIP_LOCKED", 1809 "VERBOSE", 1810 } 1811 1812 ANALYZE_EXPRESSION_PARSERS: t.ClassVar = { 1813 "ALL": lambda self: self._parse_analyze_columns(), 1814 "COMPUTE": lambda self: self._parse_analyze_statistics(), 1815 "DELETE": lambda self: self._parse_analyze_delete(), 1816 "DROP": lambda self: self._parse_analyze_histogram(), 1817 "ESTIMATE": lambda self: self._parse_analyze_statistics(), 1818 "LIST": lambda self: self._parse_analyze_list(), 1819 "PREDICATE": lambda self: self._parse_analyze_columns(), 1820 "UPDATE": lambda self: self._parse_analyze_histogram(), 1821 "VALIDATE": lambda self: self._parse_analyze_validate(), 1822 } 1823 1824 PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"} 1825 1826 AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET) 1827 1828 OPERATION_MODIFIERS: t.ClassVar[set[str]] = set() 1829 1830 RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"} 1831 1832 SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"} 1833 1834 MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values) 1835 1836 STRICT_CAST: t.ClassVar = True 1837 1838 PREFIXED_PIVOT_COLUMNS: t.ClassVar = False 1839 IDENTIFY_PIVOT_STRINGS: t.ClassVar = False 1840 # Whether an UNPIVOT outputs its value column(s) before the name column 1841 UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False 1842 # Controls when an aggregation's name is included in a pivoted column's name: 1843 # "agg_name_if_aliased" - only for aggregations that carry an explicit alias 1844 # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations 1845 # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only) 1846 PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased" 1847 1848 LOG_DEFAULTS_TO_LN: t.ClassVar = False 1849 1850 # Whether the table sample clause expects CSV syntax 1851 TABLESAMPLE_CSV: t.ClassVar = False 1852 1853 # The default method used for table sampling 1854 DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None 1855 1856 # Whether the SET command needs a delimiter (e.g. "=") for assignments 1857 SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True 1858 1859 # Whether the TRIM function expects the characters to trim as its first argument 1860 TRIM_PATTERN_FIRST: t.ClassVar = False 1861 1862 # Whether string aliases are supported `SELECT COUNT(*) 'count'` 1863 STRING_ALIASES: t.ClassVar = False 1864 1865 # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand) 1866 MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True 1867 SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset"} 1868 1869 # Whether to parse IF statements that aren't followed by a left parenthesis as commands 1870 NO_PAREN_IF_COMMANDS: t.ClassVar = True 1871 1872 # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres) 1873 JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False 1874 1875 # Whether the `:` operator is used to extract a value from a VARIANT column 1876 COLON_IS_VARIANT_EXTRACT: t.ClassVar = False 1877 1878 # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged 1879 # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks) 1880 COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True 1881 1882 # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause. 1883 # If this is True and '(' is not found, the keyword will be treated as an identifier 1884 VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True 1885 1886 # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift) 1887 SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False 1888 1889 # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS 1890 INTERVAL_SPANS: t.ClassVar = True 1891 1892 # Whether a PARTITION clause can follow a table reference 1893 SUPPORTS_PARTITION_SELECTION: t.ClassVar = False 1894 1895 # Whether the `name AS expr` schema/column constraint requires parentheses around `expr` 1896 WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True 1897 1898 # Whether the 'AS' keyword is optional in the CTE definition syntax 1899 OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True 1900 1901 # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword 1902 ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True 1903 1904 # Whether Alter statements are allowed to contain Partition specifications 1905 ALTER_TABLE_PARTITIONS: t.ClassVar = False 1906 1907 # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree. 1908 # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is 1909 # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such 1910 # as BigQuery, where all joins have the same precedence. 1911 JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False 1912 1913 # Whether TIMESTAMP <literal> can produce a zone-aware timestamp 1914 ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False 1915 1916 # Whether map literals support arbitrary expressions as keys. 1917 # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB). 1918 # When False, keys are typically restricted to identifiers. 1919 MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False 1920 1921 # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this 1922 # is true for Snowflake but not for BigQuery which can also process strings 1923 JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False 1924 1925 # Dialects like Databricks support JOINS without join criteria 1926 # Adding an ON TRUE, makes transpilation semantically correct for other dialects 1927 ADD_JOIN_ON_TRUE: t.ClassVar = False 1928 1929 # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]' 1930 # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND` 1931 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False 1932 1933 # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them 1934 # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation. 1935 ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False 1936 1937 SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS) 1938 SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS) 1939 1940 def __init__( 1941 self, 1942 error_level: ErrorLevel | None = None, 1943 error_message_context: int = 100, 1944 max_errors: int = 3, 1945 max_nodes: int = -1, 1946 dialect: DialectType = None, 1947 ): 1948 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 1949 self.error_message_context: int = error_message_context 1950 self.max_errors: int = max_errors 1951 self.max_nodes: int = max_nodes 1952 self.dialect: t.Any = _resolve_dialect(dialect) 1953 self.sql: str = "" 1954 self.errors: list[ParseError] = [] 1955 self._tokens: list[Token] = [] 1956 self._tokens_size: i64 = 0 1957 self._index: i64 = 0 1958 self._curr: Token = SENTINEL_NONE 1959 self._next: Token = SENTINEL_NONE 1960 self._prev: Token = SENTINEL_NONE 1961 self._prev_comments: list[str] = [] 1962 self._pipe_cte_counter: int = 0 1963 self._chunks: list[list[Token]] = [] 1964 self._chunk_index: i64 = 0 1965 self._node_count: int = 0 1966 1967 def reset(self) -> None: 1968 self.sql = "" 1969 self.errors = [] 1970 self._tokens = [] 1971 self._tokens_size = 0 1972 self._index = 0 1973 self._curr = SENTINEL_NONE 1974 self._next = SENTINEL_NONE 1975 self._prev = SENTINEL_NONE 1976 self._prev_comments = [] 1977 self._pipe_cte_counter = 0 1978 self._chunks = [] 1979 self._chunk_index = 0 1980 self._node_count = 0 1981 1982 def _advance(self, times: i64 = 1) -> None: 1983 index = self._index + times 1984 self._index = index 1985 tokens = self._tokens 1986 size = self._tokens_size 1987 self._curr = tokens[index] if index < size else SENTINEL_NONE 1988 self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE 1989 1990 if index > 0: 1991 prev = tokens[index - 1] 1992 self._prev = prev 1993 self._prev_comments = prev.comments 1994 else: 1995 self._prev = SENTINEL_NONE 1996 self._prev_comments = [] 1997 1998 def _advance_chunk(self) -> None: 1999 self._index = -1 2000 self._tokens = self._chunks[self._chunk_index] 2001 self._tokens_size = i64(len(self._tokens)) 2002 self._chunk_index += 1 2003 self._advance() 2004 2005 def _retreat(self, index: i64) -> None: 2006 if index != self._index: 2007 self._advance(index - self._index) 2008 2009 def _add_comments(self, expression: exp.Expr | None) -> None: 2010 if expression and self._prev_comments: 2011 expression.add_comments(self._prev_comments) 2012 self._prev_comments = [] 2013 2014 def _match( 2015 self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None 2016 ) -> bool: 2017 if self._curr.token_type == token_type: 2018 if advance: 2019 self._advance() 2020 self._add_comments(expression) 2021 return True 2022 return False 2023 2024 def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool: 2025 if self._curr.token_type in types: 2026 if advance: 2027 self._advance() 2028 return True 2029 return False 2030 2031 def _match_pair( 2032 self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True 2033 ) -> bool: 2034 if self._curr.token_type == token_type_a and self._next.token_type == token_type_b: 2035 if advance: 2036 self._advance(2) 2037 return True 2038 return False 2039 2040 def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool: 2041 if ( 2042 self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS 2043 and self._curr.text.upper() in texts 2044 ): 2045 if advance: 2046 self._advance() 2047 return True 2048 return False 2049 2050 def _match_text_seq(self, *texts: str, advance: bool = True) -> bool: 2051 index = self._index 2052 excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS 2053 for text in texts: 2054 if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text: 2055 self._advance() 2056 else: 2057 self._retreat(index) 2058 return False 2059 2060 if not advance: 2061 self._retreat(index) 2062 2063 return True 2064 2065 def _is_connected(self) -> bool: 2066 prev = self._prev 2067 curr = self._curr 2068 return bool(prev and curr and prev.end + 1 == curr.start) 2069 2070 def _find_sql(self, start: Token, end: Token) -> str: 2071 return self.sql[start.start : end.end + 1] 2072 2073 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2074 token = token or self._curr or self._prev or Token.string("") 2075 formatted_sql, start_context, highlight, end_context = highlight_sql( 2076 sql=self.sql, 2077 positions=[(token.start, token.end)], 2078 context_length=self.error_message_context, 2079 ) 2080 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2081 2082 error = ParseError.new( 2083 formatted_message, 2084 description=message, 2085 line=token.line, 2086 col=token.col, 2087 start_context=start_context, 2088 highlight=highlight, 2089 end_context=end_context, 2090 ) 2091 2092 if self.error_level == ErrorLevel.IMMEDIATE: 2093 raise error 2094 2095 self.errors.append(error) 2096 2097 def validate_expression(self, expression: E, args: list | None = None) -> E: 2098 if self.max_nodes > -1: 2099 self._node_count += 1 2100 if self._node_count > self.max_nodes: 2101 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2102 if self.error_level != ErrorLevel.IGNORE: 2103 for error_message in expression.error_messages(args): 2104 self.raise_error(error_message) 2105 return expression 2106 2107 def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None: 2108 index = self._index 2109 error_level = self.error_level 2110 this: T | None = None 2111 2112 self.error_level = ErrorLevel.IMMEDIATE 2113 try: 2114 this = parse_method() 2115 except ParseError: 2116 this = None 2117 finally: 2118 if not this or retreat: 2119 self._retreat(index) 2120 self.error_level = error_level 2121 2122 return this 2123 2124 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2125 """ 2126 Parses a list of tokens and returns a list of syntax trees, one tree 2127 per parsed SQL statement. 2128 2129 Args: 2130 raw_tokens: The list of tokens. 2131 sql: The original SQL string. 2132 2133 Returns: 2134 The list of the produced syntax trees. 2135 """ 2136 return self._parse( 2137 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2138 ) 2139 2140 def parse_into( 2141 self, 2142 expression_types: exp.IntoType, 2143 raw_tokens: list[Token], 2144 sql: str | None = None, 2145 ) -> list[exp.Expr | None]: 2146 """ 2147 Parses a list of tokens into a given Expr type. If a collection of Expr 2148 types is given instead, this method will try to parse the token list into each one 2149 of them, stopping at the first for which the parsing succeeds. 2150 2151 Args: 2152 expression_types: The expression type(s) to try and parse the token list into. 2153 raw_tokens: The list of tokens. 2154 sql: The original SQL string, used to produce helpful debug messages. 2155 2156 Returns: 2157 The target Expr. 2158 """ 2159 errors = [] 2160 for expression_type in ensure_list(expression_types): 2161 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2162 if not parser: 2163 raise TypeError(f"No parser registered for {expression_type}") 2164 2165 try: 2166 return self._parse(parser, raw_tokens, sql) 2167 except ParseError as e: 2168 e.errors[0]["into_expression"] = expression_type 2169 errors.append(e) 2170 2171 raise ParseError( 2172 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2173 errors=merge_errors(errors), 2174 ) from errors[-1] 2175 2176 def check_errors(self) -> None: 2177 """Logs or raises any found errors, depending on the chosen error level setting.""" 2178 if self.error_level == ErrorLevel.WARN: 2179 for error in self.errors: 2180 logger.error(str(error)) 2181 elif self.error_level == ErrorLevel.RAISE and self.errors: 2182 raise ParseError( 2183 concat_messages(self.errors, self.max_errors), 2184 errors=merge_errors(self.errors), 2185 ) 2186 2187 def expression( 2188 self, 2189 instance: E, 2190 token: Token | None = None, 2191 comments: list[str] | None = None, 2192 ) -> E: 2193 if token: 2194 instance.update_positions(token) 2195 instance.add_comments(comments) if comments else self._add_comments(instance) 2196 if not instance.is_primitive: 2197 instance = self.validate_expression(instance) 2198 return instance 2199 2200 def _parse_batch_statements( 2201 self, 2202 parse_method: t.Callable[[Parser], exp.Expr | None], 2203 sep_first_statement: bool = True, 2204 ) -> list[exp.Expr | None]: 2205 expressions = [] 2206 2207 # Chunkification binds if/while statements with the first statement of the body 2208 if sep_first_statement: 2209 self._match(TokenType.BEGIN) 2210 expressions.append(parse_method(self)) 2211 2212 chunks_length = len(self._chunks) 2213 while self._chunk_index < chunks_length: 2214 self._advance_chunk() 2215 2216 if self._match(TokenType.ELSE, advance=False): 2217 return expressions 2218 2219 if expressions and not self._next and self._match(TokenType.END): 2220 expressions.append(exp.EndStatement()) 2221 continue 2222 2223 expressions.append(parse_method(self)) 2224 2225 if self._index < self._tokens_size: 2226 self.raise_error("Invalid expression / Unexpected token") 2227 2228 self.check_errors() 2229 2230 return expressions 2231 2232 def _parse( 2233 self, 2234 parse_method: t.Callable[[Parser], exp.Expr | None], 2235 raw_tokens: list[Token], 2236 sql: str | None = None, 2237 ) -> list[exp.Expr | None]: 2238 self.reset() 2239 self.sql = sql or "" 2240 2241 total = len(raw_tokens) 2242 chunks: list[list[Token]] = [[]] 2243 2244 for i, token in enumerate(raw_tokens): 2245 if token.token_type == TokenType.SEMICOLON: 2246 if token.comments: 2247 chunks.append([token]) 2248 2249 if i < total - 1: 2250 chunks.append([]) 2251 else: 2252 chunks[-1].append(token) 2253 2254 self._chunks = chunks 2255 2256 return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False) 2257 2258 def _warn_unsupported(self) -> None: 2259 if self._tokens_size <= 1: 2260 return 2261 2262 # We use _find_sql because self.sql may comprise multiple chunks, and we're only 2263 # interested in emitting a warning for the one being currently processed. 2264 sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context] 2265 2266 logger.warning( 2267 f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'." 2268 ) 2269 2270 def _parse_command(self) -> exp.Command: 2271 self._warn_unsupported() 2272 comments = self._prev_comments 2273 return self.expression( 2274 exp.Command(this=self._prev.text.upper(), expression=self._parse_string()), 2275 comments=comments, 2276 ) 2277 2278 def _parse_comment(self, allow_exists: bool = True) -> exp.Expr: 2279 start = self._prev 2280 exists = self._parse_exists() if allow_exists else None 2281 2282 self._match(TokenType.ON) 2283 2284 materialized = self._match_text_seq("MATERIALIZED") 2285 kind = self._match_set(self.CREATABLES) and self._prev 2286 if not kind: 2287 return self._parse_as_command(start) 2288 2289 if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2290 this = self._parse_user_defined_function(kind=kind.token_type) 2291 elif kind.token_type == TokenType.TABLE: 2292 this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS) 2293 elif kind.token_type == TokenType.COLUMN: 2294 this = self._parse_column() 2295 else: 2296 this = self._parse_table_parts(schema=True) 2297 2298 self._match(TokenType.IS) 2299 2300 return self.expression( 2301 exp.Comment( 2302 this=this, 2303 kind=kind.text, 2304 expression=self._parse_string(), 2305 exists=exists, 2306 materialized=materialized, 2307 ) 2308 ) 2309 2310 def _parse_to_table( 2311 self, 2312 ) -> exp.ToTableProperty: 2313 table = self._parse_table_parts(schema=True) 2314 return self.expression(exp.ToTableProperty(this=table)) 2315 2316 # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl 2317 def _parse_ttl(self) -> exp.Expr: 2318 def _parse_ttl_action() -> exp.Expr | None: 2319 this = self._parse_bitwise() 2320 2321 if self._match_text_seq("DELETE"): 2322 return self.expression(exp.MergeTreeTTLAction(this=this, delete=True)) 2323 if self._match_text_seq("RECOMPRESS"): 2324 return self.expression( 2325 exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise()) 2326 ) 2327 if self._match_text_seq("TO", "DISK"): 2328 return self.expression( 2329 exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string()) 2330 ) 2331 if self._match_text_seq("TO", "VOLUME"): 2332 return self.expression( 2333 exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string()) 2334 ) 2335 2336 return this 2337 2338 expressions = self._parse_csv(_parse_ttl_action) 2339 where = self._parse_where() 2340 group = self._parse_group() 2341 2342 aggregates = None 2343 if group and self._match(TokenType.SET): 2344 aggregates = self._parse_csv(self._parse_set_item) 2345 2346 return self.expression( 2347 exp.MergeTreeTTL( 2348 expressions=expressions, where=where, group=group, aggregates=aggregates 2349 ) 2350 ) 2351 2352 def _parse_condition(self) -> exp.Expr | None: 2353 return self._parse_wrapped(parse_method=self._parse_expression, optional=True) 2354 2355 def _parse_block(self) -> exp.Block: 2356 return self.expression( 2357 exp.Block( 2358 expressions=self._parse_batch_statements( 2359 parse_method=lambda self: self._parse_statement() 2360 ) 2361 ) 2362 ) 2363 2364 def _parse_whileblock(self) -> exp.WhileBlock: 2365 return self.expression( 2366 exp.WhileBlock(this=self._parse_condition(), body=self._parse_block()) 2367 ) 2368 2369 def _parse_statement(self) -> exp.Expr | None: 2370 if not self._curr: 2371 return None 2372 2373 if self._match_set(self.STATEMENT_PARSERS): 2374 comments = self._prev_comments 2375 stmt = self.STATEMENT_PARSERS[self._prev.token_type](self) 2376 stmt.add_comments(comments, prepend=True) 2377 return stmt 2378 2379 if self._match_set(self.dialect.tokenizer_class.COMMANDS): 2380 return self._parse_command() 2381 2382 if self._match_text_seq("WHILE"): 2383 return self._parse_whileblock() 2384 2385 expression = self._parse_expression() 2386 expression = self._parse_set_operations(expression) if expression else self._parse_select() 2387 2388 if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False): 2389 expression = self._parse_pipe_syntax_query(expression) 2390 2391 return self._parse_query_modifiers(expression) 2392 2393 def _parse_drop(self, exists: bool = False) -> exp.Drop | exp.Command: 2394 start = self._prev 2395 temporary = self._match(TokenType.TEMPORARY) 2396 materialized = self._match_text_seq("MATERIALIZED") 2397 iceberg = self._match_text_seq("ICEBERG") 2398 2399 kind = self._match_set(self.CREATABLES) and self._prev.text.upper() 2400 if not kind or (iceberg and kind and kind != "TABLE"): 2401 return self._parse_as_command(start) 2402 2403 concurrently = self._match_text_seq("CONCURRENTLY") 2404 if_exists = exists or self._parse_exists() 2405 2406 if kind == "COLUMN": 2407 this = self._parse_column() 2408 else: 2409 this = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA") 2410 2411 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 2412 2413 if self._match(TokenType.L_PAREN, advance=False): 2414 expressions = self._parse_wrapped_csv(self._parse_types) 2415 else: 2416 expressions = None 2417 2418 cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper() 2419 2420 return self.expression( 2421 exp.Drop( 2422 exists=if_exists, 2423 this=this, 2424 expressions=expressions, 2425 kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind, 2426 temporary=temporary, 2427 materialized=materialized, 2428 cascade=cascade_or_restrict == "CASCADE", 2429 restrict=cascade_or_restrict == "RESTRICT", 2430 constraints=self._match_text_seq("CONSTRAINTS"), 2431 purge=self._match_text_seq("PURGE"), 2432 cluster=cluster, 2433 concurrently=concurrently, 2434 sync=self._match_text_seq("SYNC"), 2435 iceberg=iceberg, 2436 force=self._match_text_seq("FORCE"), 2437 ) 2438 ) 2439 2440 def _parse_exists(self, not_: bool = False) -> bool | None: 2441 return ( 2442 self._match_text_seq("IF") 2443 and (not not_ or self._match(TokenType.NOT)) 2444 and self._match(TokenType.EXISTS) 2445 ) 2446 2447 def _parse_create(self) -> exp.Create | exp.Command: 2448 # Note: this can't be None because we've matched a statement parser 2449 start = self._prev 2450 2451 replace = ( 2452 start.token_type == TokenType.REPLACE 2453 or self._match_pair(TokenType.OR, TokenType.REPLACE) 2454 or self._match_pair(TokenType.OR, TokenType.ALTER) 2455 ) 2456 refresh = self._match_pair(TokenType.OR, TokenType.REFRESH) 2457 2458 unique = self._match(TokenType.UNIQUE) 2459 2460 if self._match_text_seq("CLUSTERED", "COLUMNSTORE"): 2461 clustered = True 2462 elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq( 2463 "COLUMNSTORE" 2464 ): 2465 clustered = False 2466 else: 2467 clustered = None 2468 2469 if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False): 2470 self._advance() 2471 2472 properties = None 2473 create_token = self._match_set(self.CREATABLES) and self._prev 2474 2475 if not create_token: 2476 # exp.Properties.Location.POST_CREATE 2477 properties = self._parse_properties() 2478 create_token = self._match_set(self.CREATABLES) and self._prev 2479 2480 if not properties or not create_token: 2481 return self._parse_as_command(start) 2482 2483 create_token_type = t.cast(Token, create_token).token_type 2484 2485 concurrently = self._match_text_seq("CONCURRENTLY") 2486 exists = self._parse_exists(not_=True) 2487 this = None 2488 expression: exp.Expr | None = None 2489 indexes = None 2490 no_schema_binding = None 2491 begin = None 2492 clone = None 2493 2494 def extend_props(temp_props: exp.Properties | None) -> None: 2495 nonlocal properties 2496 if properties and temp_props: 2497 properties.expressions.extend(temp_props.expressions) 2498 elif temp_props: 2499 properties = temp_props 2500 2501 if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2502 this = self._parse_user_defined_function(kind=create_token_type) 2503 2504 # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature) 2505 extend_props(self._parse_properties()) 2506 2507 expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None 2508 2509 if ( 2510 not expression 2511 and create_token_type == TokenType.FUNCTION 2512 and isinstance(this, exp.UserDefinedFunction) 2513 and this.args.get("wrapped") 2514 ): 2515 pre_table_index = self._index 2516 is_table = self._match(TokenType.TABLE) 2517 2518 expression = self._parse_expression() 2519 overload_mode = bool( 2520 expression 2521 and self._curr.token_type == TokenType.COMMA 2522 and self._next.token_type == TokenType.L_PAREN 2523 ) 2524 if not overload_mode: 2525 self._retreat(pre_table_index) 2526 is_table = False 2527 expression = None 2528 else: 2529 is_table = False 2530 overload_mode = False 2531 2532 extend_props(self._parse_function_properties()) 2533 2534 if not expression: 2535 if self._match(TokenType.COMMAND): 2536 expression = self._parse_as_command(self._prev) 2537 else: 2538 begin = self._match(TokenType.BEGIN) 2539 return_ = self._match_text_seq("RETURN") 2540 2541 if self._match(TokenType.STRING, advance=False): 2542 # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property 2543 # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement 2544 expression = self._parse_string() 2545 extend_props(self._parse_properties()) 2546 else: 2547 expression = ( 2548 self._parse_user_defined_function_expression() 2549 if create_token_type == TokenType.FUNCTION 2550 else self._parse_block() 2551 ) 2552 2553 if return_: 2554 expression = self.expression(exp.Return(this=expression)) 2555 2556 if overload_mode and expression: 2557 expression = self._parse_macro_overloads( 2558 t.cast(exp.UserDefinedFunction, this), expression, is_table 2559 ) 2560 elif create_token_type == TokenType.INDEX: 2561 # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c) 2562 if not self._match(TokenType.ON): 2563 index = self._parse_id_var() 2564 anonymous = False 2565 else: 2566 index = None 2567 anonymous = True 2568 2569 this = self._parse_index(index=index, anonymous=anonymous) 2570 elif ( 2571 create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER) 2572 ) or create_token_type == TokenType.TRIGGER: 2573 if is_constraint := (create_token_type == TokenType.CONSTRAINT): 2574 create_token = self._prev 2575 2576 trigger_name = self._parse_id_var() 2577 if not trigger_name: 2578 return self._parse_as_command(start) 2579 2580 timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False) 2581 timing = timing_var.this if timing_var else None 2582 if not timing: 2583 return self._parse_as_command(start) 2584 2585 events = self._parse_trigger_events() 2586 if not self._match(TokenType.ON): 2587 self.raise_error("Expected ON in trigger definition") 2588 2589 table = self._parse_table_parts() 2590 referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None 2591 deferrable, initially = self._parse_trigger_deferrable() 2592 referencing = self._parse_trigger_referencing() 2593 for_each = self._parse_trigger_for_each() 2594 when = self._match_text_seq("WHEN") and self._parse_wrapped( 2595 self._parse_disjunction, optional=True 2596 ) 2597 execute = self._parse_trigger_execute() 2598 2599 if execute is None: 2600 return self._parse_as_command(start) 2601 2602 trigger_props = self.expression( 2603 exp.TriggerProperties( 2604 table=table, 2605 timing=timing, 2606 events=events, 2607 execute=execute, 2608 constraint=is_constraint, 2609 referenced_table=referenced_table, 2610 deferrable=deferrable, 2611 initially=initially, 2612 referencing=referencing, 2613 for_each=for_each, 2614 when=when, 2615 ) 2616 ) 2617 2618 this = trigger_name 2619 extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else [])) 2620 elif create_token_type == TokenType.TYPE: 2621 this = self._parse_table_parts(schema=True) 2622 if not this or not self._match(TokenType.ALIAS): 2623 return self._parse_as_command(start) 2624 2625 if self._match(TokenType.ENUM): 2626 expression = exp.DataType( 2627 this=exp.DType.ENUM, 2628 expressions=self._parse_wrapped_csv(self._parse_string), 2629 ) 2630 elif self._match(TokenType.L_PAREN, advance=False): 2631 expression = self._parse_schema() 2632 else: 2633 return self._parse_as_command(start) 2634 elif create_token_type in self.DB_CREATABLES: 2635 table_parts = self._parse_table_parts( 2636 schema=True, is_db_reference=create_token_type == TokenType.SCHEMA 2637 ) 2638 2639 # exp.Properties.Location.POST_NAME 2640 self._match(TokenType.COMMA) 2641 extend_props(self._parse_properties(before=True)) 2642 2643 this = self._parse_schema(this=table_parts) 2644 2645 # exp.Properties.Location.POST_SCHEMA and POST_WITH 2646 extend_props(self._parse_properties()) 2647 2648 has_alias = self._match(TokenType.ALIAS) 2649 if not self._match_set(self.DDL_SELECT_TOKENS, advance=False): 2650 # exp.Properties.Location.POST_ALIAS 2651 extend_props(self._parse_properties()) 2652 2653 if create_token_type == TokenType.SEQUENCE: 2654 expression = self._parse_types() 2655 props = self._parse_properties() 2656 if props: 2657 sequence_props = exp.SequenceProperties() 2658 options = [] 2659 for prop in props: 2660 if isinstance(prop, exp.SequenceProperties): 2661 for arg, value in prop.args.items(): 2662 if arg == "options": 2663 options.extend(value) 2664 else: 2665 sequence_props.set(arg, value) 2666 prop.pop() 2667 2668 if options: 2669 sequence_props.set("options", options) 2670 2671 props.append("expressions", sequence_props) 2672 extend_props(props) 2673 else: 2674 expression = self._parse_ddl_select() 2675 2676 # Some dialects also support using a table as an alias instead of a SELECT. 2677 # Here we fallback to this as an alternative. 2678 if not expression and has_alias: 2679 expression = self._try_parse(self._parse_table_parts) 2680 2681 if create_token_type == TokenType.TABLE: 2682 # exp.Properties.Location.POST_EXPRESSION 2683 extend_props(self._parse_properties()) 2684 2685 indexes = [] 2686 while True: 2687 index = self._parse_index() 2688 2689 # exp.Properties.Location.POST_INDEX 2690 extend_props(self._parse_properties()) 2691 if not index: 2692 break 2693 else: 2694 self._match(TokenType.COMMA) 2695 indexes.append(index) 2696 elif create_token_type == TokenType.VIEW: 2697 if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"): 2698 no_schema_binding = True 2699 elif create_token_type in (TokenType.SINK, TokenType.SOURCE): 2700 extend_props(self._parse_properties()) 2701 2702 shallow = self._match_text_seq("SHALLOW") 2703 2704 if self._match_texts(self.CLONE_KEYWORDS): 2705 copy = self._prev.text.lower() == "copy" 2706 clone = self.expression( 2707 exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy) 2708 ) 2709 2710 if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False): 2711 return self._parse_as_command(start) 2712 2713 create_kind_text = create_token.text.upper() 2714 return self.expression( 2715 exp.Create( 2716 this=this, 2717 kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text, 2718 replace=replace, 2719 refresh=refresh, 2720 unique=unique, 2721 expression=expression, 2722 exists=exists, 2723 properties=properties, 2724 indexes=indexes, 2725 no_schema_binding=no_schema_binding, 2726 begin=begin, 2727 clone=clone, 2728 concurrently=concurrently, 2729 clustered=clustered, 2730 ) 2731 ) 2732 2733 def _parse_sequence_properties(self) -> exp.SequenceProperties | None: 2734 seq = exp.SequenceProperties() 2735 2736 options = [] 2737 index = self._index 2738 2739 while self._curr: 2740 self._match(TokenType.COMMA) 2741 if self._match_text_seq("INCREMENT"): 2742 self._match_text_seq("BY") 2743 self._match_text_seq("=") 2744 seq.set("increment", self._parse_term()) 2745 elif self._match_text_seq("MINVALUE"): 2746 seq.set("minvalue", self._parse_term()) 2747 elif self._match_text_seq("MAXVALUE"): 2748 seq.set("maxvalue", self._parse_term()) 2749 elif self._match_text_seq("START"): 2750 self._match_text_seq("WITH") 2751 self._match_text_seq("=") 2752 seq.set("start", self._parse_term()) 2753 elif self._match_text_seq("CACHE"): 2754 # T-SQL allows empty CACHE which is initialized dynamically 2755 seq.set("cache", self._parse_number() or True) 2756 elif self._match_text_seq("OWNED", "BY"): 2757 # "OWNED BY NONE" is the default 2758 seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column()) 2759 else: 2760 opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False) 2761 if opt: 2762 options.append(opt) 2763 else: 2764 break 2765 2766 seq.set("options", options if options else None) 2767 return None if self._index == index else seq 2768 2769 def _parse_trigger_events(self) -> list[exp.TriggerEvent]: 2770 events = [] 2771 2772 while True: 2773 event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper() 2774 2775 if not event_type: 2776 self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)") 2777 2778 columns = ( 2779 self._parse_csv(self._parse_column) 2780 if event_type == "UPDATE" and self._match_text_seq("OF") 2781 else None 2782 ) 2783 2784 events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns))) 2785 2786 if not self._match(TokenType.OR): 2787 break 2788 2789 return events 2790 2791 def _parse_trigger_deferrable( 2792 self, 2793 ) -> tuple[str | None, str | None]: 2794 deferrable_var = self._parse_var_from_options( 2795 self.TRIGGER_DEFERRABLE, raise_unmatched=False 2796 ) 2797 deferrable = deferrable_var.this if deferrable_var else None 2798 2799 initially = None 2800 if deferrable and self._match_text_seq("INITIALLY"): 2801 initially = ( 2802 self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None 2803 ) 2804 2805 return deferrable, initially 2806 2807 def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None: 2808 if not self._match_text_seq(keyword): 2809 return None 2810 if not self._match_text_seq("TABLE"): 2811 self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause") 2812 self._match_text_seq("AS") 2813 return self._parse_id_var() 2814 2815 def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None: 2816 if not self._match_text_seq("REFERENCING"): 2817 return None 2818 2819 old_alias = None 2820 new_alias = None 2821 2822 while True: 2823 if alias := self._parse_trigger_referencing_clause("OLD"): 2824 if old_alias is not None: 2825 self.raise_error("Duplicate OLD clause in REFERENCING") 2826 old_alias = alias 2827 elif alias := self._parse_trigger_referencing_clause("NEW"): 2828 if new_alias is not None: 2829 self.raise_error("Duplicate NEW clause in REFERENCING") 2830 new_alias = alias 2831 else: 2832 break 2833 2834 if old_alias is None and new_alias is None: 2835 self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE") 2836 2837 return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias)) 2838 2839 def _parse_trigger_for_each(self) -> str | None: 2840 if not self._match_text_seq("FOR", "EACH"): 2841 return None 2842 2843 return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None 2844 2845 def _parse_trigger_execute(self) -> exp.TriggerExecute | None: 2846 if not self._match(TokenType.EXECUTE): 2847 return None 2848 2849 if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)): 2850 self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE") 2851 2852 func_call = self._parse_column() 2853 return self.expression(exp.TriggerExecute(this=func_call)) 2854 2855 def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None: 2856 # only used for teradata currently 2857 self._match(TokenType.COMMA) 2858 2859 kwargs = { 2860 "no": self._match_text_seq("NO"), 2861 "dual": self._match_text_seq("DUAL"), 2862 "before": self._match_text_seq("BEFORE"), 2863 "default": self._match_text_seq("DEFAULT"), 2864 "local": (self._match_text_seq("LOCAL") and "LOCAL") 2865 or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"), 2866 "after": self._match_text_seq("AFTER"), 2867 "minimum": self._match_texts(("MIN", "MINIMUM")), 2868 "maximum": self._match_texts(("MAX", "MAXIMUM")), 2869 } 2870 2871 if self._match_texts(self.PROPERTY_PARSERS): 2872 parser = self.PROPERTY_PARSERS[self._prev.text.upper()] 2873 try: 2874 return parser(self, **{k: v for k, v in kwargs.items() if v}) 2875 except TypeError: 2876 self.raise_error(f"Cannot parse property '{self._prev.text}'") 2877 2878 if self._match_text_seq("CHARACTER", "SET"): 2879 return self._parse_character_set(default=bool(kwargs["default"])) 2880 2881 return None 2882 2883 def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]: 2884 return self._parse_wrapped_csv(self._parse_property) 2885 2886 def _parse_property(self) -> exp.Expr | list[exp.Expr] | None: 2887 if self._match_texts(self.PROPERTY_PARSERS): 2888 return self.PROPERTY_PARSERS[self._prev.text.upper()](self) 2889 2890 if self._match_text_seq("CHARACTER", "SET"): 2891 return self._parse_character_set() 2892 2893 if self._match(TokenType.DEFAULT): 2894 if self._match_texts(self.PROPERTY_PARSERS): 2895 return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True) 2896 2897 if self._match_text_seq("CHARACTER", "SET"): 2898 return self._parse_character_set(default=True) 2899 2900 if self._match_text_seq("COMPOUND", "SORTKEY"): 2901 return self._parse_sortkey(compound=True) 2902 2903 if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"): 2904 return self.expression(exp.ParameterStyleProperty(this="PANDAS")) 2905 2906 index = self._index 2907 2908 seq_props = self._parse_sequence_properties() 2909 if seq_props: 2910 return seq_props 2911 2912 self._retreat(index) 2913 return self._parse_key_value_property() 2914 2915 def _parse_key_value_property( 2916 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 2917 ) -> exp.Property | None: 2918 index = self._index 2919 key = self._parse_column() 2920 2921 if not self._match(TokenType.EQ): 2922 self._retreat(index) 2923 return None 2924 2925 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 2926 if isinstance(key, exp.Column): 2927 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 2928 2929 value = ( 2930 parse_value() 2931 if parse_value 2932 else self._parse_bitwise() or self._parse_var(any_token=True) 2933 ) 2934 2935 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 2936 if isinstance(value, exp.Column): 2937 value = exp.var(value.name) 2938 2939 return self.expression(exp.Property(this=key, value=value)) 2940 2941 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 2942 if self._match_text_seq("BY"): 2943 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 2944 2945 self._match(TokenType.ALIAS) 2946 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 2947 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 2948 2949 return self.expression( 2950 exp.FileFormatProperty( 2951 this=( 2952 self.expression( 2953 exp.InputOutputFormat( 2954 input_format=input_format, output_format=output_format 2955 ) 2956 ) 2957 if input_format or output_format 2958 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 2959 ), 2960 hive_format=True, 2961 ) 2962 ) 2963 2964 def _parse_unquoted_field(self) -> exp.Expr | None: 2965 field = self._parse_field() 2966 if isinstance(field, exp.Identifier) and not field.quoted: 2967 field = exp.var(field) 2968 2969 return field 2970 2971 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 2972 self._match(TokenType.EQ) 2973 self._match(TokenType.ALIAS) 2974 2975 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 2976 2977 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 2978 properties = [] 2979 while True: 2980 if before: 2981 prop = self._parse_property_before() 2982 else: 2983 prop = self._parse_property() 2984 if not prop: 2985 break 2986 for p in ensure_list(prop): 2987 properties.append(p) 2988 2989 if properties: 2990 return self.expression(exp.Properties(expressions=properties)) 2991 2992 return None 2993 2994 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 2995 return self.expression( 2996 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 2997 ) 2998 2999 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3000 return self.expression( 3001 exp.SqlSecurityProperty( 3002 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3003 ) 3004 ) 3005 3006 def _parse_settings_property(self) -> exp.SettingsProperty: 3007 return self.expression( 3008 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3009 ) 3010 3011 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3012 if not self._match_text_seq("ON", "NULL", "INPUT"): 3013 self._retreat(self._index - 1) 3014 return None 3015 3016 return self.expression(exp.CalledOnNullInputProperty()) 3017 3018 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3019 if self._index >= 2: 3020 pre_volatile_token = self._tokens[self._index - 2] 3021 else: 3022 pre_volatile_token = None 3023 3024 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3025 return exp.VolatileProperty() 3026 3027 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3028 3029 def _parse_retention_period(self) -> exp.Var: 3030 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3031 number = self._parse_number() 3032 number_str = f"{number} " if number else "" 3033 unit = self._parse_var(any_token=True) 3034 return exp.var(f"{number_str}{unit}") 3035 3036 def _parse_system_versioning_property( 3037 self, with_: bool = False 3038 ) -> exp.WithSystemVersioningProperty: 3039 self._match(TokenType.EQ) 3040 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3041 3042 if self._match_text_seq("OFF"): 3043 prop.set("on", False) 3044 return prop 3045 3046 self._match(TokenType.ON) 3047 if self._match(TokenType.L_PAREN): 3048 while self._curr and not self._match(TokenType.R_PAREN): 3049 if self._match_text_seq("HISTORY_TABLE", "="): 3050 prop.set("this", self._parse_table_parts()) 3051 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3052 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3053 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3054 prop.set("retention_period", self._parse_retention_period()) 3055 3056 self._match(TokenType.COMMA) 3057 3058 return prop 3059 3060 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3061 self._match(TokenType.EQ) 3062 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3063 prop = self.expression(exp.DataDeletionProperty(on=on)) 3064 3065 if self._match(TokenType.L_PAREN): 3066 while self._curr and not self._match(TokenType.R_PAREN): 3067 if self._match_text_seq("FILTER_COLUMN", "="): 3068 prop.set("filter_column", self._parse_column()) 3069 elif self._match_text_seq("RETENTION_PERIOD", "="): 3070 prop.set("retention_period", self._parse_retention_period()) 3071 3072 self._match(TokenType.COMMA) 3073 3074 return prop 3075 3076 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3077 kind = "HASH" 3078 expressions: list[exp.Expr] | None = None 3079 if self._match_text_seq("BY", "HASH"): 3080 expressions = self._parse_wrapped_csv(self._parse_id_var) 3081 elif self._match_text_seq("BY", "RANDOM"): 3082 kind = "RANDOM" 3083 3084 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3085 buckets: exp.Expr | None = None 3086 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3087 buckets = self._parse_number() 3088 3089 return self.expression( 3090 exp.DistributedByProperty( 3091 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3092 ) 3093 ) 3094 3095 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3096 self._match_text_seq("KEY") 3097 expressions = self._parse_wrapped_id_vars() 3098 return self.expression(expr_type(expressions=expressions)) 3099 3100 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3101 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3102 prop = self._parse_system_versioning_property(with_=True) 3103 self._match_r_paren() 3104 return prop 3105 3106 if self._match(TokenType.L_PAREN, advance=False): 3107 result: list[exp.Expr] = [] 3108 for i in self._parse_wrapped_properties(): 3109 result.extend(i) if isinstance(i, list) else result.append(i) 3110 return result 3111 3112 if self._match_text_seq("JOURNAL"): 3113 return self._parse_withjournaltable() 3114 3115 if self._match_texts(self.VIEW_ATTRIBUTES): 3116 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3117 3118 if self._match_text_seq("DATA"): 3119 return self._parse_withdata(no=False) 3120 elif self._match_text_seq("NO", "DATA"): 3121 return self._parse_withdata(no=True) 3122 3123 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3124 return self._parse_serde_properties(with_=True) 3125 3126 if self._match(TokenType.SCHEMA): 3127 return self.expression( 3128 exp.WithSchemaBindingProperty( 3129 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3130 ) 3131 ) 3132 3133 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3134 return self.expression( 3135 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3136 ) 3137 3138 if not self._next: 3139 return None 3140 3141 return self._parse_withisolatedloading() 3142 3143 def _parse_procedure_option(self) -> exp.Expr | None: 3144 if self._match_text_seq("EXECUTE", "AS"): 3145 return self.expression( 3146 exp.ExecuteAsProperty( 3147 this=self._parse_var_from_options( 3148 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3149 ) 3150 or self._parse_string() 3151 ) 3152 ) 3153 3154 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3155 3156 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3157 def _parse_definer(self) -> exp.DefinerProperty | None: 3158 self._match(TokenType.EQ) 3159 3160 user = self._parse_id_var() 3161 self._match(TokenType.PARAMETER) 3162 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3163 3164 if not user or not host: 3165 return None 3166 3167 return exp.DefinerProperty(this=f"{user}@{host}") 3168 3169 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3170 self._match(TokenType.TABLE) 3171 self._match(TokenType.EQ) 3172 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3173 3174 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3175 return self.expression(exp.LogProperty(no=no)) 3176 3177 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3178 return self.expression(exp.JournalProperty(**kwargs)) 3179 3180 def _parse_checksum(self) -> exp.ChecksumProperty: 3181 self._match(TokenType.EQ) 3182 3183 on = None 3184 if self._match(TokenType.ON): 3185 on = True 3186 elif self._match_text_seq("OFF"): 3187 on = False 3188 3189 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3190 3191 def _parse_cluster(self) -> exp.Cluster: 3192 self._match(TokenType.CLUSTER_BY) 3193 return self.expression( 3194 exp.Cluster( 3195 expressions=self._parse_csv(self._parse_column), 3196 ) 3197 ) 3198 3199 def _parse_cluster_property(self) -> exp.ClusterProperty: 3200 return self.expression( 3201 exp.ClusterProperty( 3202 expressions=self._parse_wrapped_csv(self._parse_column), 3203 ) 3204 ) 3205 3206 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3207 self._match_text_seq("BY") 3208 3209 self._match_l_paren() 3210 expressions = self._parse_csv(self._parse_column) 3211 self._match_r_paren() 3212 3213 if self._match_text_seq("SORTED", "BY"): 3214 self._match_l_paren() 3215 sorted_by = self._parse_csv(self._parse_ordered) 3216 self._match_r_paren() 3217 else: 3218 sorted_by = None 3219 3220 self._match(TokenType.INTO) 3221 buckets = self._parse_number() 3222 self._match_text_seq("BUCKETS") 3223 3224 return self.expression( 3225 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3226 ) 3227 3228 def _parse_copy_property(self) -> exp.CopyGrantsProperty | None: 3229 if not self._match_text_seq("GRANTS"): 3230 self._retreat(self._index - 1) 3231 return None 3232 3233 return self.expression(exp.CopyGrantsProperty()) 3234 3235 def _parse_freespace(self) -> exp.FreespaceProperty: 3236 self._match(TokenType.EQ) 3237 return self.expression( 3238 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3239 ) 3240 3241 def _parse_mergeblockratio( 3242 self, no: bool = False, default: bool = False 3243 ) -> exp.MergeBlockRatioProperty: 3244 if self._match(TokenType.EQ): 3245 return self.expression( 3246 exp.MergeBlockRatioProperty( 3247 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3248 ) 3249 ) 3250 3251 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3252 3253 def _parse_datablocksize( 3254 self, 3255 default: bool | None = None, 3256 minimum: bool | None = None, 3257 maximum: bool | None = None, 3258 ) -> exp.DataBlocksizeProperty: 3259 self._match(TokenType.EQ) 3260 size = self._parse_number() 3261 3262 units = None 3263 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3264 units = self._prev.text 3265 3266 return self.expression( 3267 exp.DataBlocksizeProperty( 3268 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3269 ) 3270 ) 3271 3272 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3273 self._match(TokenType.EQ) 3274 always = self._match_text_seq("ALWAYS") 3275 manual = self._match_text_seq("MANUAL") 3276 never = self._match_text_seq("NEVER") 3277 default = self._match_text_seq("DEFAULT") 3278 3279 autotemp = None 3280 if self._match_text_seq("AUTOTEMP"): 3281 autotemp = self._parse_schema() 3282 3283 return self.expression( 3284 exp.BlockCompressionProperty( 3285 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3286 ) 3287 ) 3288 3289 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3290 index = self._index 3291 no = self._match_text_seq("NO") 3292 concurrent = self._match_text_seq("CONCURRENT") 3293 3294 if not self._match_text_seq("ISOLATED", "LOADING"): 3295 self._retreat(index) 3296 return None 3297 3298 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3299 return self.expression( 3300 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3301 ) 3302 3303 def _parse_locking(self) -> exp.LockingProperty: 3304 if self._match(TokenType.TABLE): 3305 kind = "TABLE" 3306 elif self._match(TokenType.VIEW): 3307 kind = "VIEW" 3308 elif self._match(TokenType.ROW): 3309 kind = "ROW" 3310 elif self._match_text_seq("DATABASE"): 3311 kind = "DATABASE" 3312 else: 3313 kind = None 3314 3315 if kind in ("DATABASE", "TABLE", "VIEW"): 3316 this = self._parse_table_parts() 3317 else: 3318 this = None 3319 3320 if self._match(TokenType.FOR): 3321 for_or_in = "FOR" 3322 elif self._match(TokenType.IN): 3323 for_or_in = "IN" 3324 else: 3325 for_or_in = None 3326 3327 if self._match_text_seq("ACCESS"): 3328 lock_type = "ACCESS" 3329 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3330 lock_type = "EXCLUSIVE" 3331 elif self._match_text_seq("SHARE"): 3332 lock_type = "SHARE" 3333 elif self._match_text_seq("READ"): 3334 lock_type = "READ" 3335 elif self._match_text_seq("WRITE"): 3336 lock_type = "WRITE" 3337 elif self._match_text_seq("CHECKSUM"): 3338 lock_type = "CHECKSUM" 3339 else: 3340 lock_type = None 3341 3342 override = self._match_text_seq("OVERRIDE") 3343 3344 return self.expression( 3345 exp.LockingProperty( 3346 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3347 ) 3348 ) 3349 3350 def _parse_partition_by(self) -> list[exp.Expr]: 3351 if self._match(TokenType.PARTITION_BY): 3352 return self._parse_csv(self._parse_disjunction) 3353 return [] 3354 3355 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3356 def _parse_partition_bound_expr() -> exp.Expr | None: 3357 if self._match_text_seq("MINVALUE"): 3358 return exp.var("MINVALUE") 3359 if self._match_text_seq("MAXVALUE"): 3360 return exp.var("MAXVALUE") 3361 return self._parse_bitwise() 3362 3363 this: exp.Expr | list[exp.Expr] | None = None 3364 expression = None 3365 from_expressions = None 3366 to_expressions = None 3367 3368 if self._match(TokenType.IN): 3369 this = self._parse_wrapped_csv(self._parse_bitwise) 3370 elif self._match(TokenType.FROM): 3371 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3372 self._match_text_seq("TO") 3373 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3374 elif self._match_text_seq("WITH", "(", "MODULUS"): 3375 this = self._parse_number() 3376 self._match_text_seq(",", "REMAINDER") 3377 expression = self._parse_number() 3378 self._match_r_paren() 3379 else: 3380 self.raise_error("Failed to parse partition bound spec.") 3381 3382 return self.expression( 3383 exp.PartitionBoundSpec( 3384 this=this, 3385 expression=expression, 3386 from_expressions=from_expressions, 3387 to_expressions=to_expressions, 3388 ) 3389 ) 3390 3391 # https://www.postgresql.org/docs/current/sql-createtable.html 3392 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3393 if not self._match_text_seq("OF"): 3394 self._retreat(self._index - 1) 3395 return None 3396 3397 this = self._parse_table(schema=True) 3398 3399 if self._match(TokenType.DEFAULT): 3400 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3401 elif self._match_text_seq("FOR", "VALUES"): 3402 expression = self._parse_partition_bound_spec() 3403 else: 3404 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3405 3406 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3407 3408 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3409 self._match(TokenType.EQ) 3410 return self.expression( 3411 exp.PartitionedByProperty( 3412 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3413 ) 3414 ) 3415 3416 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3417 if self._match_text_seq("AND", "STATISTICS"): 3418 statistics = True 3419 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3420 statistics = False 3421 else: 3422 statistics = None 3423 3424 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3425 3426 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3427 if self._match_text_seq("SQL"): 3428 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3429 return None 3430 3431 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3432 if self._match_text_seq("SQL", "DATA"): 3433 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3434 return None 3435 3436 def _parse_no_property(self) -> exp.Expr | None: 3437 if self._match_text_seq("PRIMARY", "INDEX"): 3438 return exp.NoPrimaryIndexProperty() 3439 if self._match_text_seq("SQL"): 3440 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3441 return None 3442 3443 def _parse_on_property(self) -> exp.Expr | None: 3444 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3445 return exp.OnCommitProperty() 3446 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3447 return exp.OnCommitProperty(delete=True) 3448 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3449 3450 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3451 if self._match_text_seq("SQL", "DATA"): 3452 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3453 return None 3454 3455 def _parse_distkey(self) -> exp.DistKeyProperty: 3456 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3457 3458 def _parse_create_like(self) -> exp.LikeProperty | None: 3459 table = self._parse_table(schema=True) 3460 3461 options = [] 3462 while self._match_texts(("INCLUDING", "EXCLUDING")): 3463 this = self._prev.text.upper() 3464 3465 id_var = self._parse_id_var() 3466 if not id_var: 3467 return None 3468 3469 options.append( 3470 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3471 ) 3472 3473 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3474 3475 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3476 return self.expression( 3477 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3478 ) 3479 3480 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3481 self._match(TokenType.EQ) 3482 return self.expression( 3483 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3484 ) 3485 3486 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3487 self._match_text_seq("WITH", "CONNECTION") 3488 return self.expression( 3489 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3490 ) 3491 3492 def _parse_returns(self) -> exp.ReturnsProperty: 3493 value: exp.Expr | None 3494 null = None 3495 is_table = self._match(TokenType.TABLE) 3496 3497 if is_table: 3498 if self._match(TokenType.LT): 3499 value = self.expression( 3500 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3501 ) 3502 if not self._match(TokenType.GT): 3503 self.raise_error("Expecting >") 3504 else: 3505 value = self._parse_schema(exp.var("TABLE")) 3506 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3507 null = True 3508 value = None 3509 else: 3510 value = self._parse_types() 3511 3512 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3513 3514 def _parse_describe(self) -> exp.Describe: 3515 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3516 style: str | None = ( 3517 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3518 ) 3519 if self._match(TokenType.DOT): 3520 style = None 3521 self._retreat(self._index - 2) 3522 3523 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3524 3525 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3526 this = self._parse_statement() 3527 else: 3528 this = self._parse_table(schema=True) 3529 3530 properties = self._parse_properties() 3531 expressions = properties.expressions if properties else None 3532 partition = self._parse_partition() 3533 return self.expression( 3534 exp.Describe( 3535 this=this, 3536 style=style, 3537 kind=kind, 3538 expressions=expressions, 3539 partition=partition, 3540 format=format, 3541 as_json=self._match_text_seq("AS", "JSON"), 3542 ) 3543 ) 3544 3545 def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts: 3546 kind = self._prev.text.upper() 3547 expressions = [] 3548 3549 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3550 if self._match(TokenType.WHEN): 3551 expression = self._parse_disjunction() 3552 self._match(TokenType.THEN) 3553 else: 3554 expression = None 3555 3556 else_ = self._match(TokenType.ELSE) 3557 3558 if not self._match(TokenType.INTO): 3559 return None 3560 3561 return self.expression( 3562 exp.ConditionalInsert( 3563 this=self.expression( 3564 exp.Insert( 3565 this=self._parse_table(schema=True), 3566 expression=self._parse_derived_table_values(), 3567 ) 3568 ), 3569 expression=expression, 3570 else_=else_, 3571 ) 3572 ) 3573 3574 expression = parse_conditional_insert() 3575 while expression is not None: 3576 expressions.append(expression) 3577 expression = parse_conditional_insert() 3578 3579 return self.expression( 3580 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3581 comments=comments, 3582 ) 3583 3584 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3585 comments: list[str] = [] 3586 hint = self._parse_hint() 3587 overwrite = self._match(TokenType.OVERWRITE) 3588 ignore = self._match(TokenType.IGNORE) 3589 local = self._match_text_seq("LOCAL") 3590 alternative = None 3591 is_function = None 3592 3593 if self._match_text_seq("DIRECTORY"): 3594 this: exp.Expr | None = self.expression( 3595 exp.Directory( 3596 this=self._parse_var_or_string(), 3597 local=local, 3598 row_format=self._parse_row_format(match_row=True), 3599 ) 3600 ) 3601 else: 3602 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3603 comments += ensure_list(self._prev_comments) 3604 return self._parse_multitable_inserts(comments) 3605 3606 if self._match(TokenType.OR): 3607 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3608 3609 self._match(TokenType.INTO) 3610 comments += ensure_list(self._prev_comments) 3611 self._match(TokenType.TABLE) 3612 is_function = self._match(TokenType.FUNCTION) 3613 3614 this = self._parse_function() if is_function else self._parse_insert_table() 3615 3616 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3617 set_values = None 3618 if self._match(TokenType.SET): 3619 columns = [] 3620 values = [] 3621 3622 def _parse_set_assignment() -> exp.Expr | None: 3623 target = self._parse_column() 3624 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3625 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3626 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3627 else: 3628 value = self._parse_disjunction() 3629 3630 if value: 3631 columns.append(target.this) 3632 values.append(value) 3633 return value 3634 3635 self.raise_error("Expected column assignment in INSERT ... SET") 3636 return None 3637 3638 self._parse_csv(_parse_set_assignment) 3639 3640 this = self.expression(exp.Schema(this=this, expressions=columns)) 3641 set_values = self.expression( 3642 exp.Values( 3643 expressions=[exp.Tuple(expressions=values)], 3644 alias=self._parse_table_alias(), 3645 ) 3646 ) 3647 3648 returning = self._parse_returning() # TSQL allows RETURNING before source 3649 3650 stored = self._match_text_seq("STORED") and self._parse_stored() 3651 by_name = self._match_text_seq("BY", "NAME") 3652 exists = self._parse_exists() 3653 replace_where = None 3654 replace_using = None 3655 3656 if self._match(TokenType.REPLACE): 3657 if self._match(TokenType.WHERE): 3658 replace_where = self._parse_disjunction() 3659 elif self._match(TokenType.USING): 3660 replace_using = self._parse_using_identifiers() 3661 3662 return self.expression( 3663 exp.Insert( 3664 hint=hint, 3665 is_function=is_function, 3666 this=this, 3667 stored=stored, 3668 by_name=by_name, 3669 exists=exists, 3670 where=replace_where, 3671 using=replace_using, 3672 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3673 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3674 default=self._match_text_seq("DEFAULT", "VALUES"), 3675 expression=set_values 3676 or self._parse_derived_table_values() 3677 or self._parse_ddl_select(), 3678 conflict=self._parse_on_conflict(), 3679 returning=returning or self._parse_returning(), 3680 overwrite=overwrite, 3681 alternative=alternative, 3682 ignore=ignore, 3683 source=self._match(TokenType.TABLE) and self._parse_table(), 3684 ), 3685 comments=comments, 3686 ) 3687 3688 def _parse_insert_table(self) -> exp.Expr | None: 3689 this = self._parse_table(schema=True, parse_partition=True) 3690 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False): 3691 this.set("alias", self._parse_table_alias()) 3692 return this 3693 3694 def _parse_kill(self) -> exp.Kill: 3695 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3696 3697 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3698 3699 def _parse_on_conflict(self) -> exp.OnConflict | None: 3700 conflict = self._match_text_seq("ON", "CONFLICT") 3701 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3702 3703 if not conflict and not duplicate: 3704 return None 3705 3706 conflict_keys = None 3707 constraint = None 3708 3709 if conflict: 3710 if self._match_text_seq("ON", "CONSTRAINT"): 3711 constraint = self._parse_id_var() 3712 elif self._match(TokenType.L_PAREN): 3713 conflict_keys = self._parse_csv(self._parse_indexed_column) 3714 self._match_r_paren() 3715 3716 index_predicate = self._parse_where() 3717 3718 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3719 if self._prev.token_type == TokenType.UPDATE: 3720 self._match(TokenType.SET) 3721 expressions = self._parse_csv(self._parse_equality) 3722 else: 3723 expressions = None 3724 3725 return self.expression( 3726 exp.OnConflict( 3727 duplicate=duplicate, 3728 expressions=expressions, 3729 action=action, 3730 conflict_keys=conflict_keys, 3731 index_predicate=index_predicate, 3732 constraint=constraint, 3733 where=self._parse_where(), 3734 ) 3735 ) 3736 3737 def _parse_returning(self) -> exp.Returning | None: 3738 if not self._match(TokenType.RETURNING): 3739 return None 3740 return self.expression( 3741 exp.Returning( 3742 expressions=self._parse_csv(self._parse_expression), 3743 into=self._match(TokenType.INTO) and self._parse_table_part(), 3744 ) 3745 ) 3746 3747 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3748 if not self._match(TokenType.FORMAT): 3749 return None 3750 return self._parse_row_format() 3751 3752 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3753 index = self._index 3754 with_ = with_ or self._match_text_seq("WITH") 3755 3756 if not self._match(TokenType.SERDE_PROPERTIES): 3757 self._retreat(index) 3758 return None 3759 return self.expression( 3760 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3761 ) 3762 3763 def _parse_row_format( 3764 self, match_row: bool = False 3765 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3766 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3767 return None 3768 3769 if self._match_text_seq("SERDE"): 3770 this = self._parse_string() 3771 3772 serde_properties = self._parse_serde_properties() 3773 3774 return self.expression( 3775 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3776 ) 3777 3778 self._match_text_seq("DELIMITED") 3779 3780 kwargs = {} 3781 3782 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3783 kwargs["fields"] = self._parse_string() 3784 if self._match_text_seq("ESCAPED", "BY"): 3785 kwargs["escaped"] = self._parse_string() 3786 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3787 kwargs["collection_items"] = self._parse_string() 3788 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3789 kwargs["map_keys"] = self._parse_string() 3790 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3791 kwargs["lines"] = self._parse_string() 3792 if self._match_text_seq("NULL", "DEFINED", "AS"): 3793 kwargs["null"] = self._parse_string() 3794 3795 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3796 3797 def _parse_load(self) -> exp.LoadData | exp.Command: 3798 if self._match_text_seq("DATA"): 3799 local = self._match_text_seq("LOCAL") 3800 self._match_text_seq("INPATH") 3801 inpath = self._parse_string() 3802 overwrite = self._match(TokenType.OVERWRITE) 3803 temp: bool | None = None 3804 if self._match(TokenType.INTO): 3805 temp = self._match(TokenType.TEMPORARY) 3806 self._match(TokenType.TABLE) 3807 3808 return self.expression( 3809 exp.LoadData( 3810 this=self._parse_table(schema=True), 3811 local=local, 3812 overwrite=overwrite, 3813 temp=temp, 3814 inpath=inpath, 3815 files=self._match_text_seq("FROM", "FILES") 3816 and exp.Properties(expressions=self._parse_wrapped_properties()), 3817 partition=self._parse_partition(), 3818 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3819 serde=self._match_text_seq("SERDE") and self._parse_string(), 3820 ) 3821 ) 3822 return self._parse_as_command(self._prev) 3823 3824 def _parse_delete(self) -> exp.Delete: 3825 hint = self._parse_hint() 3826 3827 # This handles MySQL's "Multiple-Table Syntax" 3828 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3829 tables = None 3830 if not self._match(TokenType.FROM, advance=False): 3831 tables = self._parse_csv(self._parse_table) or None 3832 3833 returning = self._parse_returning() 3834 3835 return self.expression( 3836 exp.Delete( 3837 hint=hint, 3838 tables=tables, 3839 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3840 using=self._match(TokenType.USING) 3841 and self._parse_csv(lambda: self._parse_table(joins=True)), 3842 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3843 where=self._parse_where(), 3844 returning=returning or self._parse_returning(), 3845 order=self._parse_order(), 3846 limit=self._parse_limit(), 3847 ) 3848 ) 3849 3850 def _parse_update(self) -> exp.Update: 3851 hint = self._parse_hint() 3852 kwargs: dict[str, object] = { 3853 "hint": hint, 3854 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3855 } 3856 while self._curr: 3857 if self._match(TokenType.SET): 3858 kwargs["expressions"] = self._parse_csv(self._parse_equality) 3859 elif self._match(TokenType.RETURNING, advance=False): 3860 kwargs["returning"] = self._parse_returning() 3861 elif self._match(TokenType.FROM, advance=False): 3862 from_ = self._parse_from(joins=True) 3863 table = from_.this if from_ else None 3864 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 3865 table.set("joins", list(self._parse_joins()) or None) 3866 3867 kwargs["from_"] = from_ 3868 elif self._match(TokenType.WHERE, advance=False): 3869 kwargs["where"] = self._parse_where() 3870 elif self._match(TokenType.ORDER_BY, advance=False): 3871 kwargs["order"] = self._parse_order() 3872 elif self._match(TokenType.LIMIT, advance=False): 3873 kwargs["limit"] = self._parse_limit() 3874 else: 3875 break 3876 3877 return self.expression(exp.Update(**kwargs)) 3878 3879 def _parse_use(self) -> exp.Use: 3880 return self.expression( 3881 exp.Use( 3882 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 3883 this=self._parse_table(schema=False), 3884 ) 3885 ) 3886 3887 def _parse_uncache(self) -> exp.Uncache: 3888 if not self._match(TokenType.TABLE): 3889 self.raise_error("Expecting TABLE after UNCACHE") 3890 3891 return self.expression( 3892 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 3893 ) 3894 3895 def _parse_cache(self) -> exp.Cache: 3896 lazy = self._match_text_seq("LAZY") 3897 self._match(TokenType.TABLE) 3898 table = self._parse_table(schema=True) 3899 3900 options = [] 3901 if self._match_text_seq("OPTIONS"): 3902 self._match_l_paren() 3903 k = self._parse_string() 3904 self._match(TokenType.EQ) 3905 v = self._parse_string() 3906 options = [k, v] 3907 self._match_r_paren() 3908 3909 self._match(TokenType.ALIAS) 3910 return self.expression( 3911 exp.Cache( 3912 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 3913 ) 3914 ) 3915 3916 def _parse_partition(self) -> exp.Partition | None: 3917 if not self._match_texts(self.PARTITION_KEYWORDS): 3918 return None 3919 3920 return self.expression( 3921 exp.Partition( 3922 subpartition=self._prev.text.upper() == "SUBPARTITION", 3923 expressions=self._parse_wrapped_csv(self._parse_disjunction), 3924 ) 3925 ) 3926 3927 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 3928 def _parse_value_expression() -> exp.Expr | None: 3929 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3930 return exp.var(self._prev.text.upper()) 3931 return self._parse_expression() 3932 3933 if self._match(TokenType.L_PAREN): 3934 expressions = self._parse_csv(_parse_value_expression) 3935 self._match_r_paren() 3936 return self.expression(exp.Tuple(expressions=expressions)) 3937 3938 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 3939 expression = self._parse_expression() 3940 if expression: 3941 return self.expression(exp.Tuple(expressions=[expression])) 3942 return None 3943 3944 def _parse_projections( 3945 self, 3946 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 3947 return self._parse_expressions(), None 3948 3949 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 3950 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 3951 this: exp.Expr | None = self._parse_simplified_pivot( 3952 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 3953 ) 3954 elif self._match(TokenType.FROM): 3955 from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True) 3956 # Support parentheses for duckdb FROM-first syntax 3957 select = self._parse_select(from_=from_) 3958 if select: 3959 if not select.args.get("from_"): 3960 select.set("from_", from_) 3961 this = select 3962 else: 3963 this = exp.select("*").from_(t.cast(exp.From, from_)) 3964 this = self._parse_query_modifiers(self._parse_set_operations(this)) 3965 else: 3966 this = ( 3967 self._parse_table(consume_pipe=True) 3968 if table 3969 else self._parse_select(nested=True, parse_set_operation=False) 3970 ) 3971 3972 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 3973 # in case a modifier (e.g. join) is following 3974 if table and isinstance(this, exp.Values) and this.alias: 3975 alias = this.args["alias"].pop() 3976 this = exp.Table(this=this, alias=alias) 3977 3978 this = self._parse_query_modifiers(self._parse_set_operations(this)) 3979 3980 return this 3981 3982 def _parse_select( 3983 self, 3984 nested: bool = False, 3985 table: bool = False, 3986 parse_subquery_alias: bool = True, 3987 parse_set_operation: bool = True, 3988 consume_pipe: bool = True, 3989 from_: exp.From | None = None, 3990 ) -> exp.Expr | None: 3991 query = self._parse_select_query( 3992 nested=nested, 3993 table=table, 3994 parse_subquery_alias=parse_subquery_alias, 3995 parse_set_operation=parse_set_operation, 3996 ) 3997 3998 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 3999 if not query and from_: 4000 query = exp.select("*").from_(from_) 4001 if isinstance(query, exp.Query): 4002 query = self._parse_pipe_syntax_query(query) 4003 query = query.subquery(copy=False) if query and table else query 4004 4005 return query 4006 4007 def _parse_select_query( 4008 self, 4009 nested: bool = False, 4010 table: bool = False, 4011 parse_subquery_alias: bool = True, 4012 parse_set_operation: bool = True, 4013 ) -> exp.Expr | None: 4014 cte = self._parse_with() 4015 4016 if cte: 4017 this = self._parse_statement() 4018 4019 if not this: 4020 self.raise_error("Failed to parse any statement following CTE") 4021 return cte 4022 4023 while isinstance(this, exp.Subquery) and this.is_wrapper: 4024 this = this.this 4025 4026 assert this is not None 4027 if "with_" in this.arg_types: 4028 if inner_cte := this.args.get("with_"): 4029 cte.set("expressions", cte.expressions + inner_cte.expressions) 4030 if inner_cte.args.get("recursive"): 4031 cte.set("recursive", True) 4032 this.set("with_", cte) 4033 else: 4034 self.raise_error(f"{this.key} does not support CTE") 4035 this = cte 4036 4037 return this 4038 4039 # duckdb supports leading with FROM x 4040 from_ = ( 4041 self._parse_from(joins=True, consume_pipe=True) 4042 if self._match(TokenType.FROM, advance=False) 4043 else None 4044 ) 4045 4046 if self._match(TokenType.SELECT): 4047 comments = self._prev_comments 4048 4049 hint = self._parse_hint() 4050 4051 if self._next and not self._next.token_type == TokenType.DOT: 4052 all_ = self._match(TokenType.ALL) 4053 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4054 else: 4055 all_, matched_distinct = None, False 4056 4057 kind = ( 4058 self._prev.text.upper() 4059 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4060 else None 4061 ) 4062 4063 distinct: exp.Expr | None = ( 4064 self.expression( 4065 exp.Distinct( 4066 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4067 ) 4068 ) 4069 if matched_distinct 4070 else None 4071 ) 4072 4073 operation_modifiers = [] 4074 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4075 operation_modifiers.append(exp.var(self._prev.text.upper())) 4076 4077 limit = self._parse_limit(top=True) 4078 4079 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4080 if limit and not matched_distinct and not all_: 4081 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4082 if matched_distinct: 4083 distinct = self.expression( 4084 exp.Distinct( 4085 on=self._parse_value(values=False) 4086 if self._match(TokenType.ON) 4087 else None 4088 ) 4089 ) 4090 else: 4091 all_ = self._match(TokenType.ALL) 4092 4093 if all_ and distinct: 4094 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4095 4096 projections, exclude = self._parse_projections() 4097 4098 this = self.expression( 4099 exp.Select( 4100 kind=kind, 4101 hint=hint, 4102 distinct=distinct, 4103 expressions=projections, 4104 limit=limit, 4105 exclude=exclude, 4106 operation_modifiers=operation_modifiers or None, 4107 ) 4108 ) 4109 this.comments = comments 4110 4111 into = self._parse_into() 4112 if into: 4113 this.set("into", into) 4114 4115 if not from_: 4116 from_ = self._parse_from() 4117 4118 if from_: 4119 this.set("from_", from_) 4120 4121 this = self._parse_query_modifiers(this) 4122 elif (table or nested) and self._match(TokenType.L_PAREN): 4123 comments = self._prev_comments 4124 this = self._parse_wrapped_select(table=table) 4125 4126 if this: 4127 this.add_comments(comments, prepend=True) 4128 4129 # We return early here so that the UNION isn't attached to the subquery by the 4130 # following call to _parse_set_operations, but instead becomes the parent node 4131 self._match_r_paren() 4132 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4133 elif self._match(TokenType.VALUES, advance=False): 4134 this = self._parse_derived_table_values() 4135 elif from_: 4136 this = exp.select("*").from_(from_.this, copy=False) 4137 this = self._parse_query_modifiers(this) 4138 elif self._match(TokenType.SUMMARIZE): 4139 table = self._match(TokenType.TABLE) 4140 this = self._parse_select() or self._parse_string() or self._parse_table() 4141 return self.expression(exp.Summarize(this=this, table=table)) 4142 elif self._match(TokenType.DESCRIBE): 4143 this = self._parse_describe() 4144 else: 4145 this = None 4146 4147 return self._parse_set_operations(this) if parse_set_operation else this 4148 4149 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4150 self._match_text_seq("SEARCH") 4151 4152 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4153 4154 if not kind: 4155 return None 4156 4157 self._match_text_seq("FIRST", "BY") 4158 4159 return self.expression( 4160 exp.RecursiveWithSearch( 4161 kind=kind, 4162 this=self._parse_id_var(), 4163 expression=self._match_text_seq("SET") and self._parse_id_var(), 4164 using=self._match_text_seq("USING") and self._parse_id_var(), 4165 ) 4166 ) 4167 4168 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4169 if not skip_with_token and not self._match(TokenType.WITH): 4170 return None 4171 4172 comments = self._prev_comments 4173 recursive = self._match(TokenType.RECURSIVE) 4174 4175 last_comments = None 4176 expressions = [] 4177 udfs = [] 4178 while True: 4179 cte = self._parse_cte() 4180 if cte: 4181 if isinstance(cte, exp.FunctionSpecification): 4182 udfs.append(cte) 4183 else: 4184 expressions.append(cte) 4185 4186 if last_comments: 4187 cte.add_comments(last_comments) 4188 4189 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4190 break 4191 else: 4192 self._match(TokenType.WITH) 4193 recursive = self._match(TokenType.RECURSIVE) or recursive 4194 4195 last_comments = self._prev_comments 4196 4197 return self.expression( 4198 exp.With( 4199 expressions=expressions, 4200 recursive=recursive or None, 4201 search=self._parse_recursive_with_search(), 4202 udfs=udfs or None, 4203 ), 4204 comments=comments, 4205 ) 4206 4207 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4208 index = self._index 4209 4210 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4211 if not alias or not alias.this: 4212 self.raise_error("Expected CTE to have alias") 4213 4214 key_expressions = ( 4215 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4216 ) 4217 4218 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4219 self._retreat(index) 4220 return None 4221 4222 comments = self._prev_comments 4223 4224 if self._match_text_seq("NOT", "MATERIALIZED"): 4225 materialized = False 4226 elif self._match_text_seq("MATERIALIZED"): 4227 materialized = True 4228 else: 4229 materialized = None 4230 4231 cte = self.expression( 4232 exp.CTE( 4233 this=self._parse_wrapped(self._parse_statement), 4234 alias=alias, 4235 materialized=materialized, 4236 key_expressions=key_expressions, 4237 ), 4238 comments=comments, 4239 ) 4240 4241 values = cte.this 4242 if isinstance(values, exp.Values): 4243 cte.set("this", self._values_to_select(values)) 4244 4245 return cte 4246 4247 def _values_to_select(self, values: exp.Values) -> exp.Select: 4248 if values.alias: 4249 return exp.select("*").from_(values) 4250 return exp.select("*").from_(exp.alias_(values, "_values", table=True)) 4251 4252 def _parse_table_alias( 4253 self, alias_tokens: t.Collection[TokenType] | None = None 4254 ) -> exp.TableAlias | None: 4255 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4256 # so this section tries to parse the clause version and if it fails, it treats the token 4257 # as an identifier (alias) 4258 if self._can_parse_limit_or_offset(): 4259 return None 4260 4261 # START is never treated as an implicit alias when followed by WITH, since that 4262 # would swallow the beginning of a START WITH ... CONNECT BY clause 4263 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4264 return None 4265 4266 any_token = self._match(TokenType.ALIAS) 4267 alias = ( 4268 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4269 or self._parse_string_as_identifier() 4270 ) 4271 4272 index = self._index 4273 if self._match(TokenType.L_PAREN): 4274 columns = self._parse_csv(self._parse_function_parameter) 4275 self._match_r_paren() if columns else self._retreat(index) 4276 else: 4277 columns = None 4278 4279 if not alias and not columns: 4280 return None 4281 4282 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4283 4284 # We bubble up comments from the Identifier to the TableAlias 4285 if isinstance(alias, exp.Identifier): 4286 table_alias.add_comments(alias.pop_comments()) 4287 4288 return table_alias 4289 4290 def _parse_subquery( 4291 self, this: exp.Expr | None, parse_alias: bool = True 4292 ) -> exp.Subquery | None: 4293 if not this: 4294 return None 4295 4296 return self.expression( 4297 exp.Subquery( 4298 this=this, 4299 pivots=self._parse_pivots(), 4300 alias=self._parse_table_alias() if parse_alias else None, 4301 sample=self._parse_table_sample(), 4302 ) 4303 ) 4304 4305 def _implicit_unnests_to_explicit(self, this: E) -> E: 4306 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4307 4308 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4309 for i, join in enumerate(this.args.get("joins") or []): 4310 table = join.this 4311 normalized_table = table.copy() 4312 normalized_table.meta["maybe_column"] = True 4313 normalized_table = _norm(normalized_table, dialect=self.dialect) 4314 4315 if isinstance(table, exp.Table) and not join.args.get("on"): 4316 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4317 table_as_column = table.to_column() 4318 unnest = exp.Unnest(expressions=[table_as_column]) 4319 4320 # Table.to_column creates a parent Alias node that we want to convert to 4321 # a TableAlias and attach to the Unnest, so it matches the parser's output 4322 if isinstance(table.args.get("alias"), exp.TableAlias): 4323 table_as_column.replace(table_as_column.this) 4324 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4325 4326 table.replace(unnest) 4327 4328 refs.add(normalized_table.alias_or_name) 4329 4330 return this 4331 4332 @t.overload 4333 def _parse_query_modifiers(self, this: E) -> E: ... 4334 4335 @t.overload 4336 def _parse_query_modifiers(self, this: None) -> None: ... 4337 4338 def _parse_query_modifiers(self, this): 4339 if isinstance(this, self.MODIFIABLES): 4340 for join in self._parse_joins(): 4341 this.append("joins", join) 4342 for lateral in iter(self._parse_lateral, None): 4343 this.append("laterals", lateral) 4344 4345 while True: 4346 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4347 modifier_token = self._curr 4348 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4349 key, expression = parser(self) 4350 4351 if expression: 4352 if this.args.get(key): 4353 self.raise_error( 4354 f"Found multiple '{modifier_token.text.upper()}' clauses", 4355 token=modifier_token, 4356 ) 4357 4358 this.set(key, expression) 4359 if key == "limit": 4360 offset = expression.args.get("offset") 4361 expression.set("offset", None) 4362 4363 if offset: 4364 offset = exp.Offset(expression=offset) 4365 this.set("offset", offset) 4366 4367 limit_by_expressions = expression.expressions 4368 expression.set("expressions", None) 4369 offset.set("expressions", limit_by_expressions) 4370 continue 4371 4372 if self._curr.text.upper() == "START": 4373 modifier_token = self._curr 4374 connect = self._parse_connect() 4375 if connect: 4376 if this.args.get("connect"): 4377 self.raise_error( 4378 "Found multiple 'START WITH' clauses", token=modifier_token 4379 ) 4380 4381 this.set("connect", connect) 4382 continue 4383 break 4384 4385 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4386 this = self._implicit_unnests_to_explicit(this) 4387 4388 return this 4389 4390 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4391 start = self._curr 4392 while self._curr: 4393 self._advance() 4394 4395 end = self._tokens[self._index - 1] 4396 return exp.Hint(expressions=[self._find_sql(start, end)]) 4397 4398 def _parse_hint_function_call(self) -> exp.Expr | None: 4399 return self._parse_function_call() 4400 4401 def _parse_hint_body(self) -> exp.Hint | None: 4402 start_index = self._index 4403 should_fallback_to_string = False 4404 4405 hints = [] 4406 try: 4407 for hint in iter( 4408 lambda: self._parse_csv( 4409 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4410 ), 4411 [], 4412 ): 4413 hints.extend(hint) 4414 except ParseError: 4415 should_fallback_to_string = True 4416 4417 if should_fallback_to_string or self._curr: 4418 self._retreat(start_index) 4419 return self._parse_hint_fallback_to_string() 4420 4421 return self.expression(exp.Hint(expressions=hints)) 4422 4423 def _parse_hint(self) -> exp.Hint | None: 4424 if self._match(TokenType.HINT) and self._prev_comments: 4425 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4426 4427 return None 4428 4429 def _parse_into(self) -> exp.Into | None: 4430 if not self._match(TokenType.INTO): 4431 return None 4432 4433 temp = self._match(TokenType.TEMPORARY) 4434 unlogged = self._match_text_seq("UNLOGGED") 4435 self._match(TokenType.TABLE) 4436 4437 return self.expression( 4438 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4439 ) 4440 4441 def _parse_from( 4442 self, 4443 joins: bool = False, 4444 skip_from_token: bool = False, 4445 consume_pipe: bool = False, 4446 ) -> exp.From | None: 4447 if not skip_from_token and not self._match(TokenType.FROM): 4448 return None 4449 4450 comments = self._prev_comments 4451 return self.expression( 4452 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4453 comments=comments, 4454 ) 4455 4456 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4457 return self.expression( 4458 exp.MatchRecognizeMeasure( 4459 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4460 this=self._parse_expression(), 4461 ) 4462 ) 4463 4464 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4465 if not self._match(TokenType.MATCH_RECOGNIZE): 4466 return None 4467 4468 self._match_l_paren() 4469 4470 partition = self._parse_partition_by() 4471 order = self._parse_order() 4472 4473 measures = ( 4474 self._parse_csv(self._parse_match_recognize_measure) 4475 if self._match_text_seq("MEASURES") 4476 else None 4477 ) 4478 4479 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4480 rows = exp.var("ONE ROW PER MATCH") 4481 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4482 text = "ALL ROWS PER MATCH" 4483 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4484 text += " SHOW EMPTY MATCHES" 4485 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4486 text += " OMIT EMPTY MATCHES" 4487 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4488 text += " WITH UNMATCHED ROWS" 4489 rows = exp.var(text) 4490 else: 4491 rows = None 4492 4493 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4494 text = "AFTER MATCH SKIP" 4495 if self._match_text_seq("PAST", "LAST", "ROW"): 4496 text += " PAST LAST ROW" 4497 elif self._match_text_seq("TO", "NEXT", "ROW"): 4498 text += " TO NEXT ROW" 4499 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4500 direction = self._prev.text.upper() 4501 pattern_var = self._advance_any() 4502 if not pattern_var: 4503 self.raise_error( 4504 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4505 ) 4506 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4507 after = exp.var(text) 4508 else: 4509 after = None 4510 4511 if self._match_text_seq("PATTERN"): 4512 self._match_l_paren() 4513 4514 if not self._curr: 4515 self.raise_error("Expecting )", self._curr) 4516 4517 paren = 1 4518 start = self._curr 4519 4520 while self._curr and paren > 0: 4521 if self._curr.token_type == TokenType.L_PAREN: 4522 paren += 1 4523 if self._curr.token_type == TokenType.R_PAREN: 4524 paren -= 1 4525 4526 end = self._prev 4527 self._advance() 4528 4529 if paren > 0: 4530 self.raise_error("Expecting )", self._curr) 4531 4532 pattern = exp.var(self._find_sql(start, end)) 4533 else: 4534 pattern = None 4535 4536 define = ( 4537 self._parse_csv(self._parse_name_as_expression) 4538 if self._match_text_seq("DEFINE") 4539 else None 4540 ) 4541 4542 self._match_r_paren() 4543 4544 return self.expression( 4545 exp.MatchRecognize( 4546 partition_by=partition, 4547 order=order, 4548 measures=measures, 4549 rows=rows, 4550 after=after, 4551 pattern=pattern, 4552 define=define, 4553 alias=self._parse_table_alias(), 4554 ) 4555 ) 4556 4557 def _parse_lateral(self) -> exp.Lateral | None: 4558 cross_apply: bool | None = None 4559 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4560 cross_apply = True 4561 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4562 cross_apply = False 4563 4564 if cross_apply is not None: 4565 this = self._parse_select(table=True) 4566 view = None 4567 outer = None 4568 elif self._match(TokenType.LATERAL): 4569 this = self._parse_select(table=True) 4570 view = self._match(TokenType.VIEW) 4571 outer = self._match(TokenType.OUTER) 4572 else: 4573 return None 4574 4575 if not this: 4576 this = ( 4577 self._parse_unnest() 4578 or self._parse_function() 4579 or self._parse_id_var(any_token=False) 4580 ) 4581 4582 while self._match(TokenType.DOT): 4583 this = exp.Dot( 4584 this=this, 4585 expression=self._parse_function() or self._parse_id_var(any_token=False), 4586 ) 4587 4588 ordinality: bool | None = None 4589 4590 if view: 4591 table = self._parse_id_var(any_token=False) 4592 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4593 table_alias: exp.TableAlias | None = self.expression( 4594 exp.TableAlias(this=table, columns=columns) 4595 ) 4596 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4597 # We move the alias from the lateral's child node to the lateral itself 4598 table_alias = this.args["alias"].pop() 4599 else: 4600 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4601 table_alias = self._parse_table_alias() 4602 4603 return self.expression( 4604 exp.Lateral( 4605 this=this, 4606 view=view, 4607 outer=outer, 4608 alias=table_alias, 4609 cross_apply=cross_apply, 4610 ordinality=ordinality, 4611 ) 4612 ) 4613 4614 def _parse_stream(self) -> exp.Stream | None: 4615 index = self._index 4616 if self._match(TokenType.STREAM): 4617 if this := self._try_parse(self._parse_table): 4618 return self.expression(exp.Stream(this=this)) 4619 self._retreat(index) 4620 return None 4621 4622 def _parse_join_parts( 4623 self, 4624 ) -> tuple[Token | None, Token | None, Token | None]: 4625 return ( 4626 self._prev if self._match_set(self.JOIN_METHODS) else None, 4627 self._prev if self._match_set(self.JOIN_SIDES) else None, 4628 self._prev if self._match_set(self.JOIN_KINDS) else None, 4629 ) 4630 4631 def _parse_using_identifiers(self) -> list[exp.Expr]: 4632 def _parse_column_as_identifier() -> exp.Expr | None: 4633 this = self._parse_column() 4634 if isinstance(this, exp.Column): 4635 return this.this 4636 return this 4637 4638 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4639 4640 def _parse_join( 4641 self, 4642 skip_join_token: bool = False, 4643 parse_bracket: bool = False, 4644 alias_tokens: t.Collection[TokenType] | None = None, 4645 ) -> exp.Join | None: 4646 if self._match(TokenType.COMMA): 4647 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4648 cross_join = self.expression(exp.Join(this=table)) if table else None 4649 4650 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4651 cross_join.set("kind", "CROSS") 4652 4653 return cross_join 4654 4655 index = self._index 4656 method, side, kind = self._parse_join_parts() 4657 directed = self._match_text_seq("DIRECTED") 4658 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4659 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4660 join_comments = self._prev_comments 4661 4662 if not skip_join_token and not join: 4663 self._retreat(index) 4664 kind = None 4665 method = None 4666 side = None 4667 4668 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4669 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4670 4671 if not skip_join_token and not join and not outer_apply and not cross_apply: 4672 return None 4673 4674 kwargs: dict[str, t.Any] = { 4675 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4676 } 4677 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4678 kwargs["expressions"] = self._parse_csv( 4679 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4680 ) 4681 4682 if method: 4683 kwargs["method"] = method.text.upper() 4684 if side: 4685 kwargs["side"] = side.text.upper() 4686 if kind: 4687 kwargs["kind"] = kind.text.upper() 4688 if hint: 4689 kwargs["hint"] = hint 4690 4691 if self._match(TokenType.MATCH_CONDITION): 4692 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4693 4694 if self._match(TokenType.ON): 4695 kwargs["on"] = self._parse_disjunction() 4696 elif self._match(TokenType.USING): 4697 kwargs["using"] = self._parse_using_identifiers() 4698 elif ( 4699 not method 4700 and not (outer_apply or cross_apply) 4701 and not isinstance(kwargs["this"], exp.Unnest) 4702 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4703 ): 4704 index = self._index 4705 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4706 4707 if joins and self._match(TokenType.ON): 4708 kwargs["on"] = self._parse_disjunction() 4709 elif joins and self._match(TokenType.USING): 4710 kwargs["using"] = self._parse_using_identifiers() 4711 else: 4712 joins = None 4713 self._retreat(index) 4714 4715 kwargs["this"].set("joins", joins if joins else None) 4716 4717 kwargs["pivots"] = self._parse_pivots() 4718 4719 comments = [c for token in (method, side, kind) if token for c in token.comments] 4720 comments = (join_comments or []) + comments 4721 4722 if ( 4723 self.ADD_JOIN_ON_TRUE 4724 and not kwargs.get("on") 4725 and not kwargs.get("using") 4726 and not kwargs.get("method") 4727 and kwargs.get("kind") in (None, "INNER", "OUTER") 4728 ): 4729 kwargs["on"] = exp.true() 4730 4731 if directed: 4732 kwargs["directed"] = directed 4733 4734 return self.expression(exp.Join(**kwargs), comments=comments) 4735 4736 def _parse_opclass(self) -> exp.Expr | None: 4737 this = self._parse_disjunction() 4738 4739 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4740 return this 4741 4742 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4743 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4744 4745 return this 4746 4747 def _parse_index_params(self) -> exp.IndexParameters: 4748 using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None 4749 4750 if self._match(TokenType.L_PAREN, advance=False): 4751 columns = self._parse_wrapped_csv(self._parse_with_operator) 4752 else: 4753 columns = None 4754 4755 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4756 partition_by = self._parse_partition_by() 4757 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4758 tablespace = ( 4759 self._parse_var(any_token=True) 4760 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4761 else None 4762 ) 4763 where = self._parse_where() 4764 4765 on = self._parse_field() if self._match(TokenType.ON) else None 4766 4767 return self.expression( 4768 exp.IndexParameters( 4769 using=using, 4770 columns=columns, 4771 include=include, 4772 partition_by=partition_by, 4773 where=where, 4774 with_storage=with_storage, 4775 tablespace=tablespace, 4776 on=on, 4777 ) 4778 ) 4779 4780 def _parse_index( 4781 self, index: exp.Expr | None = None, anonymous: bool = False 4782 ) -> exp.Index | None: 4783 if index or anonymous: 4784 unique = None 4785 primary = None 4786 amp = None 4787 4788 self._match(TokenType.ON) 4789 self._match(TokenType.TABLE) # hive 4790 table = self._parse_table_parts(schema=True) 4791 else: 4792 unique = self._match(TokenType.UNIQUE) 4793 primary = self._match_text_seq("PRIMARY") 4794 amp = self._match_text_seq("AMP") 4795 4796 if not self._match(TokenType.INDEX): 4797 return None 4798 4799 index = self._parse_id_var() 4800 table = None 4801 4802 params = self._parse_index_params() 4803 4804 return self.expression( 4805 exp.Index( 4806 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4807 ) 4808 ) 4809 4810 def _parse_table_hints(self) -> list[exp.Expr] | None: 4811 hints: list[exp.Expr] = [] 4812 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 4813 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 4814 hints.append( 4815 self.expression( 4816 exp.WithTableHint( 4817 expressions=self._parse_csv( 4818 lambda: self._parse_function() or self._parse_var(any_token=True) 4819 ) 4820 ) 4821 ) 4822 ) 4823 self._match_r_paren() 4824 else: 4825 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 4826 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 4827 hint = exp.IndexTableHint(this=self._prev.text.upper()) 4828 4829 self._match_set((TokenType.INDEX, TokenType.KEY)) 4830 if self._match(TokenType.FOR): 4831 hint.set("target", self._advance_any() and self._prev.text.upper()) 4832 4833 hint.set("expressions", self._parse_wrapped_id_vars()) 4834 hints.append(hint) 4835 4836 return hints or None 4837 4838 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 4839 return ( 4840 (not schema and self._parse_function(optional_parens=False)) 4841 or self._parse_id_var(any_token=False) 4842 or self._parse_string_as_identifier() 4843 or self._parse_placeholder() 4844 ) 4845 4846 def _parse_table_parts_fast(self) -> exp.Table | None: 4847 index = self._index 4848 parts: list[exp.Identifier] | None = None 4849 all_comments: list[str] | None = None 4850 4851 while self._match_set(self.IDENTIFIER_TOKENS): 4852 token = self._prev 4853 comments = self._prev_comments 4854 4855 has_dot = self._match(TokenType.DOT) 4856 curr_tt = self._curr.token_type 4857 4858 if not has_dot: 4859 if curr_tt in self.TABLE_POSTFIX_TOKENS: 4860 self._retreat(index) 4861 return None 4862 elif curr_tt not in self.IDENTIFIER_TOKENS: 4863 self._retreat(index) 4864 return None 4865 4866 if parts is None: 4867 parts = [] 4868 4869 if comments: 4870 if all_comments is None: 4871 all_comments = [] 4872 all_comments.extend(comments) 4873 self._prev_comments = [] 4874 4875 parts.append( 4876 self.expression( 4877 exp.Identifier( 4878 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 4879 ), 4880 token, 4881 ) 4882 ) 4883 4884 if not has_dot: 4885 break 4886 4887 if parts is None: 4888 return None 4889 4890 n = len(parts) 4891 4892 if n == 1: 4893 table: exp.Table = exp.Table(this=parts[0]) 4894 elif n == 2: 4895 table = exp.Table(this=parts[1], db=parts[0]) 4896 elif n >= 3: 4897 this: exp.Identifier | exp.Dot = parts[2] 4898 for i in range(3, n): 4899 this = exp.Dot(this=this, expression=parts[i]) 4900 4901 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 4902 4903 if table is None: 4904 self._retreat(index) 4905 elif all_comments: 4906 table.add_comments(all_comments) 4907 return table 4908 4909 def _parse_table_parts( 4910 self, 4911 schema: bool = False, 4912 is_db_reference: bool = False, 4913 wildcard: bool = False, 4914 fast: bool = False, 4915 ) -> exp.Table | exp.Dot | None: 4916 if fast: 4917 return self._parse_table_parts_fast() 4918 4919 catalog: exp.Expr | str | None = None 4920 db: exp.Expr | str | None = None 4921 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 4922 4923 while self._match(TokenType.DOT): 4924 if catalog: 4925 # This allows nesting the table in arbitrarily many dot expressions if needed 4926 table = self.expression( 4927 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 4928 ) 4929 else: 4930 catalog = db 4931 db = table 4932 # "" used for tsql FROM a..b case 4933 table = self._parse_table_part(schema=schema) or "" 4934 4935 if ( 4936 wildcard 4937 and self._is_connected() 4938 and (isinstance(table, exp.Identifier) or not table) 4939 and self._match(TokenType.STAR) 4940 ): 4941 if isinstance(table, exp.Identifier): 4942 table.args["this"] += "*" 4943 else: 4944 table = exp.Identifier(this="*") 4945 4946 if is_db_reference: 4947 catalog = db 4948 db = table 4949 table = None 4950 4951 if not table and not is_db_reference: 4952 self.raise_error(f"Expected table name but got {self._curr}") 4953 if not db and is_db_reference: 4954 self.raise_error(f"Expected database name but got {self._curr}") 4955 4956 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 4957 4958 # Bubble up comments from identifier parts to the Table 4959 comments = [] 4960 for part in table.parts: 4961 if part_comments := part.pop_comments(): 4962 comments.extend(part_comments) 4963 if comments: 4964 table.add_comments(comments) 4965 4966 changes = self._parse_changes() 4967 if changes: 4968 table.set("changes", changes) 4969 4970 at_before = self._parse_historical_data() 4971 if at_before: 4972 table.set("when", at_before) 4973 4974 pivots = self._parse_pivots() 4975 if pivots: 4976 table.set("pivots", pivots) 4977 4978 return table 4979 4980 def _parse_table( 4981 self, 4982 schema: bool = False, 4983 joins: bool = False, 4984 alias_tokens: t.Collection[TokenType] | None = None, 4985 parse_bracket: bool = False, 4986 is_db_reference: bool = False, 4987 parse_partition: bool = False, 4988 consume_pipe: bool = False, 4989 ) -> exp.Expr | None: 4990 if not schema and not is_db_reference and not consume_pipe and not joins: 4991 index = self._index 4992 table = self._parse_table_parts(fast=True) 4993 4994 if table is not None: 4995 curr_tt = self._curr.token_type 4996 next_tt = self._next.token_type 4997 4998 fast_terminators = self.TABLE_TERMINATORS 4999 5000 # only return the table if we're sure there are no other operators 5001 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5002 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5003 return table 5004 5005 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5006 5007 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5008 if alias := self._parse_table_alias( 5009 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5010 ): 5011 table.set("alias", alias) 5012 5013 if self._curr.token_type in fast_terminators: 5014 return table 5015 5016 self._retreat(index) 5017 5018 if stream := self._parse_stream(): 5019 return stream 5020 5021 if lateral := self._parse_lateral(): 5022 return lateral 5023 5024 if unnest := self._parse_unnest(): 5025 return unnest 5026 5027 if values := self._parse_derived_table_values(): 5028 return values 5029 5030 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5031 if not subquery.args.get("pivots"): 5032 subquery.set("pivots", self._parse_pivots()) 5033 if joins: 5034 for join in self._parse_joins(): 5035 subquery.append("joins", join) 5036 return subquery 5037 5038 bracket = parse_bracket and self._parse_bracket(None) 5039 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5040 5041 rows_from_tables = ( 5042 self._parse_wrapped_csv(self._parse_table) 5043 if self._match_text_seq("ROWS", "FROM") 5044 else None 5045 ) 5046 rows_from = ( 5047 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5048 ) 5049 5050 only = self._match(TokenType.ONLY) 5051 5052 this = t.cast( 5053 exp.Expr, 5054 bracket 5055 or rows_from 5056 or self._parse_bracket( 5057 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5058 ), 5059 ) 5060 5061 if only: 5062 this.set("only", only) 5063 5064 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5065 self._match(TokenType.STAR) 5066 5067 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5068 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5069 this.set("partition", self._parse_partition()) 5070 5071 if schema: 5072 return self._parse_schema(this=this) 5073 5074 if self.dialect.ALIAS_POST_VERSION: 5075 this.set("version", self._parse_version()) 5076 5077 if self.dialect.ALIAS_POST_TABLESAMPLE: 5078 this.set("sample", self._parse_table_sample()) 5079 5080 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5081 if alias: 5082 this.set("alias", alias) 5083 5084 # DuckDB requires the time-travel clause to come after the alias, e.g. 5085 # SELECT * FROM t AS a AT (VERSION => 1) 5086 if isinstance(this, exp.Table) and not this.args.get("when"): 5087 this.set("when", self._parse_historical_data()) 5088 5089 if self._match(TokenType.INDEXED_BY): 5090 this.set("indexed", self._parse_table_parts()) 5091 elif self._match_text_seq("NOT", "INDEXED"): 5092 this.set("indexed", False) 5093 5094 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5095 return self.expression( 5096 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5097 ) 5098 5099 this.set("hints", self._parse_table_hints()) 5100 5101 if not this.args.get("pivots"): 5102 this.set("pivots", self._parse_pivots()) 5103 5104 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5105 this.set("sample", self._parse_table_sample()) 5106 5107 if not self.dialect.ALIAS_POST_VERSION: 5108 this.set("version", self._parse_version()) 5109 5110 if joins: 5111 for join in self._parse_joins(alias_tokens=alias_tokens): 5112 this.append("joins", join) 5113 5114 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5115 this.set("ordinality", True) 5116 this.set("alias", self._parse_table_alias()) 5117 5118 return this 5119 5120 def _parse_version(self) -> exp.Version | None: 5121 for phrase, this in self.VERSION_PHRASES.items(): 5122 if self._match_text_seq(*phrase): 5123 break 5124 else: 5125 return None 5126 5127 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5128 kind = self._prev.text.upper() 5129 start = self._parse_bitwise() 5130 self._match_texts(("TO", "AND")) 5131 end = self._parse_bitwise() 5132 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5133 elif self._match_text_seq("CONTAINED", "IN"): 5134 kind = "CONTAINED IN" 5135 expression = self.expression( 5136 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5137 ) 5138 elif self._match(TokenType.ALL): 5139 kind = "ALL" 5140 expression = None 5141 else: 5142 self._match_text_seq("AS", "OF") 5143 kind = "AS OF" 5144 expression = self._parse_type() 5145 5146 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5147 5148 def _parse_historical_data(self) -> exp.HistoricalData | None: 5149 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5150 index = self._index 5151 historical_data = None 5152 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5153 this = self._prev.text.upper() 5154 kind = ( 5155 self._match(TokenType.L_PAREN) 5156 and self._match_texts(self.HISTORICAL_DATA_KIND) 5157 and self._prev.text.upper() 5158 ) 5159 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5160 5161 if expression: 5162 self._match_r_paren() 5163 historical_data = self.expression( 5164 exp.HistoricalData(this=this, kind=kind, expression=expression) 5165 ) 5166 else: 5167 self._retreat(index) 5168 5169 return historical_data 5170 5171 def _parse_changes(self) -> exp.Changes | None: 5172 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5173 return None 5174 5175 information = self._parse_var(any_token=True) 5176 self._match_r_paren() 5177 5178 return self.expression( 5179 exp.Changes( 5180 information=information, 5181 at_before=self._parse_historical_data(), 5182 end=self._parse_historical_data(), 5183 ) 5184 ) 5185 5186 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5187 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5188 return None 5189 5190 self._advance() 5191 5192 expressions = self._parse_wrapped_csv(self._parse_equality) 5193 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5194 5195 alias = self._parse_table_alias() if with_alias else None 5196 5197 if alias: 5198 if self.dialect.UNNEST_COLUMN_ONLY: 5199 if alias.args.get("columns"): 5200 self.raise_error("Unexpected extra column alias in unnest.") 5201 5202 alias.set("columns", [alias.this]) 5203 alias.set("this", None) 5204 5205 columns = alias.args.get("columns") or [] 5206 if offset and len(expressions) < len(columns): 5207 offset = columns.pop() 5208 5209 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5210 self._match(TokenType.ALIAS) 5211 offset = self._parse_id_var( 5212 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5213 ) or exp.to_identifier("offset") 5214 5215 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5216 5217 def _parse_derived_table_values(self) -> exp.Values | None: 5218 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5219 if not is_derived and not ( 5220 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5221 self._match_text_seq("VALUES") or self._match_text_seq("FORMAT", "VALUES") 5222 ): 5223 return None 5224 5225 expressions = self._parse_csv(self._parse_value) 5226 alias = self._parse_table_alias() 5227 5228 if is_derived: 5229 self._match_r_paren() 5230 5231 return self.expression( 5232 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5233 ) 5234 5235 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5236 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5237 as_modifier and self._match_text_seq("USING", "SAMPLE") 5238 ): 5239 return None 5240 5241 bucket_numerator = None 5242 bucket_denominator = None 5243 bucket_field = None 5244 percent = None 5245 size = None 5246 seed = None 5247 5248 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5249 matched_l_paren = self._match(TokenType.L_PAREN) 5250 5251 if self.TABLESAMPLE_CSV: 5252 num = None 5253 expressions = self._parse_csv(self._parse_primary) 5254 else: 5255 expressions = None 5256 num = ( 5257 self._parse_factor() 5258 if self._match(TokenType.NUMBER, advance=False) 5259 else self._parse_primary() or self._parse_placeholder() 5260 ) 5261 5262 if self._match_text_seq("BUCKET"): 5263 bucket_numerator = self._parse_number() 5264 self._match_text_seq("OUT", "OF") 5265 bucket_denominator = bucket_denominator = self._parse_number() 5266 self._match(TokenType.ON) 5267 bucket_field = self._parse_field() 5268 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5269 percent = num 5270 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5271 size = num 5272 else: 5273 percent = num 5274 5275 if matched_l_paren: 5276 self._match_r_paren() 5277 5278 if self._match(TokenType.L_PAREN): 5279 method = self._parse_var(upper=True) 5280 seed = self._match(TokenType.COMMA) and self._parse_number() 5281 self._match_r_paren() 5282 elif self._match_texts(("SEED", "REPEATABLE")): 5283 seed = self._parse_wrapped(self._parse_number) 5284 5285 if not method and self.DEFAULT_SAMPLING_METHOD: 5286 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5287 5288 return self.expression( 5289 exp.TableSample( 5290 expressions=expressions, 5291 method=method, 5292 bucket_numerator=bucket_numerator, 5293 bucket_denominator=bucket_denominator, 5294 bucket_field=bucket_field, 5295 percent=percent, 5296 size=size, 5297 seed=seed, 5298 ) 5299 ) 5300 5301 def _parse_pivots(self) -> list[exp.Pivot] | None: 5302 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5303 return None 5304 return list(iter(self._parse_pivot, None)) or None 5305 5306 def _parse_joins( 5307 self, alias_tokens: t.Collection[TokenType] | None = None 5308 ) -> t.Iterator[exp.Join]: 5309 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5310 5311 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5312 if not self._match(TokenType.INTO): 5313 return None 5314 5315 return self.expression( 5316 exp.UnpivotColumns( 5317 this=self._match_text_seq("NAME") and self._parse_column(), 5318 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5319 ) 5320 ) 5321 5322 # https://duckdb.org/docs/sql/statements/pivot 5323 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5324 def _parse_on() -> exp.Expr | None: 5325 this = self._parse_bitwise() 5326 5327 if self._match(TokenType.IN): 5328 # PIVOT ... ON col IN (row_val1, row_val2) 5329 return self._parse_in(this) 5330 if self._match(TokenType.ALIAS, advance=False): 5331 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5332 return self._parse_alias(this) 5333 5334 return this 5335 5336 this = self._parse_table() 5337 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5338 into = self._parse_unpivot_columns() 5339 using = self._match(TokenType.USING) and self._parse_csv( 5340 lambda: self._parse_alias(self._parse_column()) 5341 ) 5342 group = self._parse_group() 5343 5344 return self.expression( 5345 exp.Pivot( 5346 this=this, 5347 expressions=expressions, 5348 using=using, 5349 group=group, 5350 unpivot=is_unpivot, 5351 into=into, 5352 ) 5353 ) 5354 5355 def _parse_pivot_in(self) -> exp.In: 5356 def _parse_aliased_expression() -> exp.Expr | None: 5357 this = self._parse_select_or_expression() 5358 5359 self._match(TokenType.ALIAS) 5360 alias = self._parse_bitwise() 5361 if alias: 5362 if isinstance(alias, exp.Column) and not alias.db: 5363 alias = alias.this 5364 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5365 5366 return this 5367 5368 value = self._parse_column() 5369 5370 if not self._match(TokenType.IN): 5371 self.raise_error("Expecting IN") 5372 5373 if self._match(TokenType.L_PAREN): 5374 if self._match(TokenType.ANY): 5375 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5376 else: 5377 exprs = self._parse_csv(_parse_aliased_expression) 5378 self._match_r_paren() 5379 return self.expression(exp.In(this=value, expressions=exprs)) 5380 5381 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5382 5383 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5384 func = self._parse_function() 5385 if not func: 5386 if self._prev.token_type == TokenType.COMMA: 5387 return None 5388 self.raise_error("Expecting an aggregation function in PIVOT") 5389 5390 return self._parse_alias(func) 5391 5392 def _parse_pivot(self) -> exp.Pivot | None: 5393 index = self._index 5394 include_nulls = None 5395 5396 if self._match(TokenType.PIVOT): 5397 unpivot = False 5398 elif self._match(TokenType.UNPIVOT): 5399 unpivot = True 5400 5401 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5402 if self._match_text_seq("INCLUDE", "NULLS"): 5403 include_nulls = True 5404 elif self._match_text_seq("EXCLUDE", "NULLS"): 5405 include_nulls = False 5406 else: 5407 return None 5408 5409 expressions = [] 5410 5411 if not self._match(TokenType.L_PAREN): 5412 self._retreat(index) 5413 return None 5414 5415 if unpivot: 5416 expressions = self._parse_csv(self._parse_column) 5417 else: 5418 expressions = self._parse_csv(self._parse_pivot_aggregation) 5419 5420 if not expressions: 5421 self.raise_error("Failed to parse PIVOT's aggregation list") 5422 5423 if not self._match(TokenType.FOR): 5424 self.raise_error("Expecting FOR") 5425 5426 fields = [] 5427 while True: 5428 field = self._try_parse(self._parse_pivot_in) 5429 if not field: 5430 break 5431 fields.append(field) 5432 5433 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5434 self._parse_bitwise 5435 ) 5436 5437 group = self._parse_group() 5438 5439 self._match_r_paren() 5440 5441 pivot = self.expression( 5442 exp.Pivot( 5443 expressions=expressions, 5444 fields=fields, 5445 unpivot=unpivot, 5446 include_nulls=include_nulls, 5447 default_on_null=default_on_null, 5448 group=group, 5449 ) 5450 ) 5451 5452 if unpivot: 5453 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5454 for pivot_field in pivot.fields: 5455 if isinstance(pivot_field, exp.In): 5456 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5457 5458 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5459 5460 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5461 pivot.set("alias", self._parse_table_alias()) 5462 5463 if not unpivot: 5464 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5465 5466 columns: list[exp.Expr] = [] 5467 all_fields = [] 5468 for pivot_field in pivot.fields: 5469 pivot_field_expressions = pivot_field.expressions 5470 5471 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5472 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5473 continue 5474 5475 all_fields.append( 5476 [ 5477 # An explicit `<field> AS <alias>` names the output column directly, 5478 # so it wins over the dialect's string-identifying convention 5479 fld.sql() 5480 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5481 else fld.alias_or_name 5482 for fld in pivot_field_expressions 5483 ] 5484 ) 5485 5486 if all_fields: 5487 if names: 5488 all_fields.append(names) 5489 5490 # Generate all possible combinations of the pivot columns 5491 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5492 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5493 for fld_parts_tuple in itertools.product(*all_fields): 5494 fld_parts = list(fld_parts_tuple) 5495 5496 if names and self.PREFIXED_PIVOT_COLUMNS: 5497 # Move the "name" to the front of the list 5498 fld_parts.insert(0, fld_parts.pop(-1)) 5499 5500 columns.append(exp.to_identifier("_".join(fld_parts))) 5501 5502 pivot.set("columns", columns) 5503 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5504 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5505 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5506 5507 return pivot 5508 5509 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5510 return [agg.alias for agg in aggregations if agg.alias] 5511 5512 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5513 if not skip_where_token and not self._match(TokenType.PREWHERE): 5514 return None 5515 5516 comments = self._prev_comments 5517 return self.expression( 5518 exp.PreWhere(this=self._parse_disjunction()), 5519 comments=comments, 5520 ) 5521 5522 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5523 if not skip_where_token and not self._match(TokenType.WHERE): 5524 return None 5525 5526 comments = self._prev_comments 5527 return self.expression( 5528 exp.Where(this=self._parse_disjunction()), 5529 comments=comments, 5530 ) 5531 5532 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5533 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5534 return None 5535 comments = self._prev_comments 5536 5537 elements: dict[str, t.Any] = defaultdict(list) 5538 5539 if self._match(TokenType.ALL): 5540 elements["all"] = True 5541 elif self._match(TokenType.DISTINCT): 5542 elements["all"] = False 5543 5544 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5545 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5546 5547 while True: 5548 index = self._index 5549 5550 elements["expressions"].extend( 5551 self._parse_csv( 5552 lambda: ( 5553 None 5554 if self._match_set((TokenType.CUBE, TokenType.ROLLUP), advance=False) 5555 else self._parse_disjunction() 5556 ) 5557 ) 5558 ) 5559 5560 before_with_index = self._index 5561 with_prefix = self._match(TokenType.WITH) 5562 5563 if cube_or_rollup := self._parse_cube_or_rollup(with_prefix=with_prefix): 5564 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5565 elements[key].append(cube_or_rollup) 5566 elif grouping_sets := self._parse_grouping_sets(): 5567 elements["grouping_sets"].append(grouping_sets) 5568 elif self._match_text_seq("TOTALS"): 5569 elements["totals"] = True # type: ignore 5570 5571 if before_with_index <= self._index <= before_with_index + 1: 5572 self._retreat(before_with_index) 5573 break 5574 5575 if index == self._index: 5576 break 5577 5578 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5579 5580 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5581 if self._match(TokenType.CUBE): 5582 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5583 elif self._match(TokenType.ROLLUP): 5584 kind = exp.Rollup 5585 else: 5586 return None 5587 5588 return self.expression( 5589 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5590 ) 5591 5592 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5593 if self._match(TokenType.GROUPING_SETS): 5594 return self.expression( 5595 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5596 ) 5597 return None 5598 5599 def _parse_grouping_set(self) -> exp.Expr | None: 5600 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5601 5602 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5603 if not skip_having_token and not self._match(TokenType.HAVING): 5604 return None 5605 comments = self._prev_comments 5606 return self.expression( 5607 exp.Having(this=self._parse_disjunction()), 5608 comments=comments, 5609 ) 5610 5611 def _parse_qualify(self) -> exp.Qualify | None: 5612 if not self._match(TokenType.QUALIFY): 5613 return None 5614 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5615 5616 def _parse_connect_with_prior(self) -> exp.Expr | None: 5617 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5618 exp.Prior(this=self._parse_bitwise()) 5619 ) 5620 connect = self._parse_disjunction() 5621 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5622 return connect 5623 5624 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5625 if skip_start_token: 5626 start = None 5627 elif self._match_text_seq("START", "WITH"): 5628 start = self._parse_disjunction() 5629 else: 5630 return None 5631 5632 self._match(TokenType.CONNECT_BY) 5633 nocycle = self._match_text_seq("NOCYCLE") 5634 connect = self._parse_connect_with_prior() 5635 5636 if not start and self._match_text_seq("START", "WITH"): 5637 start = self._parse_disjunction() 5638 5639 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5640 5641 def _parse_name_as_expression(self) -> exp.Expr | None: 5642 this = self._parse_id_var(any_token=True) 5643 if self._match(TokenType.ALIAS): 5644 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5645 return this 5646 5647 def _parse_interpolate(self) -> list[exp.Expr] | None: 5648 if self._match_text_seq("INTERPOLATE"): 5649 return self._parse_wrapped_csv(self._parse_name_as_expression) 5650 return None 5651 5652 def _parse_order( 5653 self, this: exp.Expr | None = None, skip_order_token: bool = False 5654 ) -> exp.Expr | None: 5655 siblings = None 5656 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5657 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5658 return this 5659 5660 siblings = True 5661 5662 comments = self._prev_comments 5663 return self.expression( 5664 exp.Order( 5665 this=this, 5666 expressions=self._parse_csv(self._parse_ordered), 5667 siblings=siblings, 5668 ), 5669 comments=comments, 5670 ) 5671 5672 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5673 if not self._match(token): 5674 return None 5675 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5676 5677 def _parse_ordered( 5678 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5679 ) -> exp.Ordered | None: 5680 this = parse_method() if parse_method else self._parse_disjunction() 5681 if not this: 5682 return None 5683 5684 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5685 this = exp.var("ALL") 5686 5687 asc = self._match(TokenType.ASC) 5688 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5689 5690 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5691 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5692 5693 nulls_first = is_nulls_first or False 5694 explicitly_null_ordered = is_nulls_first or is_nulls_last 5695 5696 if ( 5697 not explicitly_null_ordered 5698 and ( 5699 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5700 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5701 ) 5702 and self.dialect.NULL_ORDERING != "nulls_are_last" 5703 ): 5704 nulls_first = True 5705 5706 if self._match_text_seq("WITH", "FILL"): 5707 with_fill = self.expression( 5708 exp.WithFill( 5709 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5710 to=self._match_text_seq("TO") and self._parse_bitwise(), 5711 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5712 interpolate=self._parse_interpolate(), 5713 ) 5714 ) 5715 else: 5716 with_fill = None 5717 5718 return self.expression( 5719 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5720 ) 5721 5722 def _parse_limit_options(self) -> exp.LimitOptions | None: 5723 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5724 rows = self._match_set((TokenType.ROW, TokenType.ROWS)) 5725 self._match_text_seq("ONLY") 5726 with_ties = self._match_text_seq("WITH", "TIES") 5727 5728 if not (percent or rows or with_ties): 5729 return None 5730 5731 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5732 5733 def _parse_limit( 5734 self, 5735 this: exp.Expr | None = None, 5736 top: bool = False, 5737 skip_limit_token: bool = False, 5738 ) -> exp.Expr | None: 5739 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5740 comments = self._prev_comments 5741 if top: 5742 limit_paren = self._match(TokenType.L_PAREN) 5743 expression = ( 5744 self._parse_term() or self._parse_select() 5745 if limit_paren 5746 else self._parse_number() 5747 ) 5748 5749 if limit_paren: 5750 self._match_r_paren() 5751 5752 else: 5753 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5754 return this 5755 5756 # Parsing LIMIT x% (i.e x PERCENT) as a term leads to an error, since 5757 # we try to build an exp.Mod expr. For that matter, we backtrack and instead 5758 # consume the factor plus parse the percentage separately 5759 index = self._index 5760 expression = self._try_parse(self._parse_term) 5761 if isinstance(expression, exp.Mod): 5762 self._retreat(index) 5763 expression = self._parse_factor() 5764 elif not expression: 5765 expression = self._parse_factor() 5766 limit_options = self._parse_limit_options() 5767 5768 if self._match(TokenType.COMMA): 5769 offset = expression 5770 expression = self._parse_term() 5771 else: 5772 offset = None 5773 5774 limit_exp = self.expression( 5775 exp.Limit( 5776 this=this, 5777 expression=expression, 5778 offset=offset, 5779 limit_options=limit_options, 5780 expressions=self._parse_limit_by(), 5781 ), 5782 comments=comments, 5783 ) 5784 5785 return limit_exp 5786 5787 if self._match(TokenType.FETCH): 5788 direction = ( 5789 self._prev.text.upper() 5790 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5791 else "FIRST" 5792 ) 5793 5794 count = self._parse_field(tokens=self.FETCH_TOKENS) 5795 5796 return self.expression( 5797 exp.Fetch( 5798 direction=direction, count=count, limit_options=self._parse_limit_options() 5799 ) 5800 ) 5801 5802 return this 5803 5804 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 5805 if not self._match(TokenType.OFFSET): 5806 return this 5807 5808 count = self._parse_term() 5809 self._match_set((TokenType.ROW, TokenType.ROWS)) 5810 5811 return self.expression( 5812 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 5813 ) 5814 5815 def _can_parse_limit_or_offset(self) -> bool: 5816 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 5817 return False 5818 5819 index = self._index 5820 result = bool( 5821 self._try_parse(self._parse_limit, retreat=True) 5822 or self._try_parse(self._parse_offset, retreat=True) 5823 ) 5824 self._retreat(index) 5825 5826 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 5827 if self._next.token_type == TokenType.MATCH_CONDITION: 5828 result = False 5829 5830 return result 5831 5832 def _can_parse_named_window(self) -> bool: 5833 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 5834 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 5835 if not self._match(TokenType.WINDOW, advance=False): 5836 return False 5837 5838 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 5839 if name is None or name.token_type not in self.ID_VAR_TOKENS: 5840 return False 5841 5842 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 5843 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 5844 return False 5845 5846 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 5847 return body is not None and body.token_type == TokenType.L_PAREN 5848 5849 def _parse_limit_by(self) -> list[exp.Expr] | None: 5850 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 5851 5852 def _parse_locks(self) -> list[exp.Lock]: 5853 locks = [] 5854 while True: 5855 update, key = None, None 5856 if self._match_text_seq("FOR", "UPDATE"): 5857 update = True 5858 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 5859 "LOCK", "IN", "SHARE", "MODE" 5860 ): 5861 update = False 5862 elif self._match_text_seq("FOR", "KEY", "SHARE"): 5863 update, key = False, True 5864 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 5865 update, key = True, True 5866 else: 5867 break 5868 5869 expressions = None 5870 if self._match_text_seq("OF"): 5871 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 5872 5873 wait: bool | exp.Expr | None = None 5874 if self._match_text_seq("NOWAIT"): 5875 wait = True 5876 elif self._match_text_seq("WAIT"): 5877 wait = self._parse_primary() 5878 elif self._match_text_seq("SKIP", "LOCKED"): 5879 wait = False 5880 5881 locks.append( 5882 self.expression( 5883 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 5884 ) 5885 ) 5886 5887 return locks 5888 5889 def parse_set_operation( 5890 self, this: exp.Expr | None, consume_pipe: bool = False 5891 ) -> exp.Expr | None: 5892 start = self._index 5893 _, side_token, kind_token = self._parse_join_parts() 5894 5895 side = side_token.text if side_token else None 5896 kind = kind_token.text if kind_token else None 5897 5898 if not self._match_set(self.SET_OPERATIONS): 5899 self._retreat(start) 5900 return None 5901 5902 token_type = self._prev.token_type 5903 5904 if token_type == TokenType.UNION: 5905 operation: type[exp.SetOperation] = exp.Union 5906 elif token_type == TokenType.EXCEPT: 5907 operation = exp.Except 5908 else: 5909 operation = exp.Intersect 5910 5911 comments = self._prev.comments 5912 5913 if self._match(TokenType.DISTINCT): 5914 distinct: bool | None = True 5915 elif self._match(TokenType.ALL): 5916 distinct = False 5917 else: 5918 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 5919 if distinct is None: 5920 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 5921 5922 by_name = ( 5923 self._match_text_seq("BY", "NAME") 5924 or self._match_text_seq("STRICT", "CORRESPONDING") 5925 or None 5926 ) 5927 if self._match_text_seq("CORRESPONDING"): 5928 by_name = True 5929 if not side and not kind: 5930 kind = "INNER" 5931 5932 on_column_list = None 5933 if by_name and self._match_texts(("ON", "BY")): 5934 on_column_list = self._parse_wrapped_csv(self._parse_column) 5935 5936 expression = self._parse_select( 5937 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 5938 ) 5939 5940 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 5941 # in _parse_cte and so that alias pushdown can reach into set operation branches 5942 if isinstance(this, exp.Values): 5943 this = self._values_to_select(this) 5944 if isinstance(expression, exp.Values): 5945 expression = self._values_to_select(expression) 5946 5947 return self.expression( 5948 operation( 5949 this=this, 5950 distinct=distinct, 5951 by_name=by_name, 5952 expression=expression, 5953 side=side, 5954 kind=kind, 5955 on=on_column_list, 5956 ), 5957 comments=comments, 5958 ) 5959 5960 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 5961 while this: 5962 setop = self.parse_set_operation(this) 5963 if not setop: 5964 break 5965 this = setop 5966 5967 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 5968 expression = this.expression 5969 5970 if expression: 5971 for arg in self.SET_OP_MODIFIERS: 5972 expr = expression.args.get(arg) 5973 if expr: 5974 this.set(arg, expr.pop()) 5975 5976 return this 5977 5978 def _parse_expression(self) -> exp.Expr | None: 5979 return self._parse_alias(self._parse_assignment()) 5980 5981 def _parse_assignment(self) -> exp.Expr | None: 5982 this = self._parse_disjunction() 5983 if not this and self._next.token_type in self.ASSIGNMENT: 5984 # This allows us to parse <non-identifier token> := <expr> 5985 this = exp.column( 5986 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 5987 ) 5988 5989 while self._match_set(self.ASSIGNMENT): 5990 if isinstance(this, exp.Column) and len(this.parts) == 1: 5991 this = this.this 5992 5993 comments = self._prev_comments 5994 this = self.expression( 5995 self.ASSIGNMENT[self._prev.token_type]( 5996 this=this, expression=self._parse_assignment() 5997 ), 5998 comments=comments, 5999 ) 6000 6001 return this 6002 6003 def _parse_disjunction(self) -> exp.Expr | None: 6004 this = self._parse_conjunction() 6005 while self._match_set(self.DISJUNCTION): 6006 comments = self._prev_comments 6007 this = self.expression( 6008 self.DISJUNCTION[self._prev.token_type]( 6009 this=this, expression=self._parse_conjunction() 6010 ), 6011 comments=comments, 6012 ) 6013 return this 6014 6015 def _parse_conjunction(self) -> exp.Expr | None: 6016 this = self._parse_equality() 6017 while self._match_set(self.CONJUNCTION): 6018 comments = self._prev_comments 6019 this = self.expression( 6020 self.CONJUNCTION[self._prev.token_type]( 6021 this=this, expression=self._parse_equality() 6022 ), 6023 comments=comments, 6024 ) 6025 return this 6026 6027 def _parse_equality(self) -> exp.Expr | None: 6028 this = self._parse_comparison() 6029 while self._match_set(self.EQUALITY): 6030 comments = self._prev_comments 6031 this = self.expression( 6032 self.EQUALITY[self._prev.token_type]( 6033 this=this, expression=self._parse_comparison() 6034 ), 6035 comments=comments, 6036 ) 6037 return this 6038 6039 def _parse_comparison(self) -> exp.Expr | None: 6040 this = self._parse_range() 6041 while self._match_set(self.COMPARISON): 6042 comments = self._prev_comments 6043 this = self.expression( 6044 self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()), 6045 comments=comments, 6046 ) 6047 return this 6048 6049 def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6050 this = this or self._parse_bitwise() 6051 6052 while True: 6053 negate = self._match(TokenType.NOT) 6054 if self._match_set(self.RANGE_PARSERS): 6055 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6056 if not expression: 6057 return this 6058 6059 this = expression 6060 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6061 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6062 elif self._match(TokenType.NOTNULL): 6063 # Postgres supports ISNULL and NOTNULL for conditions. 6064 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6065 if self.dialect.NORMALIZE_NOT_NULL: 6066 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6067 this = self.expression(exp.Not(this=this)) 6068 else: 6069 this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True)) 6070 else: 6071 if negate: 6072 self._retreat(self._index - 1) 6073 break 6074 6075 if negate: 6076 this = self._negate_range(this) 6077 if self._curr and ( 6078 self._curr.token_type == TokenType.NOT 6079 or self._curr.token_type in self.RANGE_PARSERS 6080 ): 6081 this = self.expression(exp.Paren(this=this)) 6082 6083 return this 6084 6085 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6086 if not this: 6087 return this 6088 6089 expression = this.this if isinstance(this, exp.Escape) else this 6090 if isinstance(expression, (exp.Like, exp.ILike)): 6091 expression.set("negate", True) 6092 return this 6093 6094 return self.expression(exp.Not(this=this)) 6095 6096 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6097 index = self._index - 1 6098 negate = self._match(TokenType.NOT) 6099 6100 if self._match_text_seq("DISTINCT", "FROM"): 6101 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6102 return self.expression(klass(this=this, expression=self._parse_bitwise())) 6103 6104 if self._match(TokenType.JSON): 6105 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6106 6107 if self._match_text_seq("WITH"): 6108 _with = True 6109 elif self._match_text_seq("WITHOUT"): 6110 _with = False 6111 else: 6112 _with = None 6113 6114 unique = self._match(TokenType.UNIQUE) 6115 self._match_text_seq("KEYS") 6116 expression: exp.Expr | None = self.expression( 6117 exp.JSON(this=kind, with_=_with, unique=unique) 6118 ) 6119 else: 6120 expression = self._parse_null() or self._parse_bitwise() 6121 if not expression: 6122 self._retreat(index) 6123 return None 6124 6125 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6126 this = self.expression(exp.Is(this=this, expression=expression, negate=True)) 6127 else: 6128 this = self.expression(exp.Is(this=this, expression=expression)) 6129 this = self.expression(exp.Not(this=this)) if negate else this 6130 6131 return self._parse_column_ops(this) 6132 6133 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6134 unnest = self._parse_unnest(with_alias=False) 6135 if unnest: 6136 this = self.expression(exp.In(this=this, unnest=unnest)) 6137 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6138 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6139 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6140 6141 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6142 this = self.expression( 6143 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6144 ) 6145 else: 6146 this = self.expression(exp.In(this=this, expressions=expressions)) 6147 6148 if matched_l_paren: 6149 self._match_r_paren(this) 6150 elif not self._match(TokenType.R_BRACKET, expression=this): 6151 self.raise_error("Expecting ]") 6152 else: 6153 this = self.expression(exp.In(this=this, field=self._parse_column())) 6154 6155 return this 6156 6157 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6158 symmetric = None 6159 if self._match_text_seq("SYMMETRIC"): 6160 symmetric = True 6161 elif self._match_text_seq("ASYMMETRIC"): 6162 symmetric = False 6163 6164 low = self._parse_bitwise() 6165 self._match(TokenType.AND) 6166 high = self._parse_bitwise() 6167 6168 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6169 6170 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6171 if not self._match(TokenType.ESCAPE): 6172 return this 6173 return self.expression( 6174 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6175 ) 6176 6177 def _parse_interval_span( 6178 self, this: exp.Expr, parse_function_unit: bool = True 6179 ) -> exp.Interval: 6180 # handle day-time format interval span with omitted units: 6181 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6182 interval_span_units_omitted = None 6183 if ( 6184 this 6185 and this.is_string 6186 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6187 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6188 ): 6189 index = self._index 6190 6191 # Var "TO" Var 6192 first_unit = self._parse_var(any_token=True, upper=True) 6193 second_unit = None 6194 if first_unit and self._match_text_seq("TO"): 6195 second_unit = self._parse_var(any_token=True, upper=True) 6196 6197 interval_span_units_omitted = not (first_unit and second_unit) 6198 6199 self._retreat(index) 6200 6201 unit_index = self._index 6202 if interval_span_units_omitted: 6203 unit = None 6204 else: 6205 # Only attempt to parse a unit if the current token can actually be one, so that a 6206 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6207 is_unit = self._curr is not None and ( 6208 self._curr.token_type == TokenType.VAR 6209 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6210 ) 6211 unit = self._parse_function() if parse_function_unit and is_unit else None 6212 if not unit and is_unit: 6213 unit = self._parse_var(any_token=True, upper=True) 6214 6215 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6216 # each INTERVAL expression into this canonical form so it's easy to transpile 6217 if this and this.is_number: 6218 try: 6219 this = exp.Literal.string(this.to_py()) 6220 except ValueError: 6221 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6222 elif this and this.is_string: 6223 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6224 if parts and unit: 6225 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6226 unit = None 6227 self._retreat(unit_index) 6228 6229 if len(parts) == 1: 6230 this = exp.Literal.string(parts[0][0]) 6231 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6232 6233 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6234 unit = self.expression( 6235 exp.IntervalSpan( 6236 this=unit, 6237 expression=self._parse_function() 6238 or self._parse_var(any_token=True, upper=True), 6239 ) 6240 ) 6241 6242 return self.expression(exp.Interval(this=this, unit=unit)) 6243 6244 def _parse_interval( 6245 self, require_interval: bool = True, parse_function_unit: bool = True 6246 ) -> exp.Add | exp.Interval | None: 6247 index = self._index 6248 6249 if not self._match(TokenType.INTERVAL) and require_interval: 6250 return None 6251 6252 if self._match(TokenType.STRING, advance=False): 6253 this = self._parse_primary() 6254 else: 6255 this = self._parse_term() 6256 6257 if not this or ( 6258 isinstance(this, exp.Column) 6259 and not this.table 6260 and not this.this.quoted 6261 and self._curr 6262 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6263 ): 6264 self._retreat(index) 6265 return None 6266 6267 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6268 6269 index = self._index 6270 self._match(TokenType.PLUS) 6271 6272 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6273 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6274 return self.expression( 6275 exp.Add( 6276 this=interval, 6277 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6278 ) 6279 ) 6280 6281 self._retreat(index) 6282 return interval 6283 6284 def _parse_bitwise(self) -> exp.Expr | None: 6285 this = self._parse_term() 6286 6287 while True: 6288 if self._match_set(self.BITWISE): 6289 this = self.expression( 6290 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6291 ) 6292 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6293 this = self.expression( 6294 exp.DPipe( 6295 this=this, 6296 expression=self._parse_term(), 6297 safe=not self.dialect.STRICT_STRING_CONCAT, 6298 ) 6299 ) 6300 elif self._match(TokenType.DQMARK): 6301 this = self.expression( 6302 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6303 ) 6304 elif self._match_pair(TokenType.LT, TokenType.LT): 6305 this = self.expression( 6306 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6307 ) 6308 elif self._match_pair(TokenType.GT, TokenType.GT): 6309 this = self.expression( 6310 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6311 ) 6312 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6313 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6314 else: 6315 break 6316 6317 return this 6318 6319 def _parse_term(self) -> exp.Expr | None: 6320 this = self._parse_factor() 6321 6322 while self._match_set(self.TERM): 6323 klass = self.TERM[self._prev.token_type] 6324 comments = self._prev_comments 6325 expression = self._parse_factor() 6326 6327 this = self.expression(klass(this=this, expression=expression), comments=comments) 6328 6329 if isinstance(this, exp.Collate): 6330 self._normalize_collate(this) 6331 6332 return this 6333 6334 def _normalize_collate(self, collate: exp.Collate) -> None: 6335 expr = collate.expression 6336 6337 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6338 # fallback to Identifier / Var 6339 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6340 ident = expr.this 6341 if isinstance(ident, exp.Identifier): 6342 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6343 6344 def _parse_factor(self) -> exp.Expr | None: 6345 parse_method = self._parse_factor_operand 6346 this = self._parse_at_time_zone(parse_method()) 6347 6348 while self._match_set(self.FACTOR): 6349 klass = self.FACTOR[self._prev.token_type] 6350 comments = self._prev_comments 6351 expression = parse_method() 6352 6353 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6354 self._retreat(self._index - 1) 6355 return this 6356 6357 this = self.expression(klass(this=this, expression=expression), comments=comments) 6358 6359 if isinstance(this, exp.Div): 6360 this.set("typed", self.dialect.TYPED_DIVISION) 6361 this.set("safe", self.dialect.SAFE_DIVISION) 6362 6363 return this 6364 6365 def _parse_factor_operand(self) -> exp.Expr | None: 6366 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6367 6368 def _parse_exponent(self) -> exp.Expr | None: 6369 this = self._parse_unary() 6370 while self._match_set(self.EXPONENT): 6371 comments = self._prev_comments 6372 this = self.expression( 6373 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6374 comments=comments, 6375 ) 6376 return this 6377 6378 def _parse_unary(self) -> exp.Expr | None: 6379 if self._match_set(self.UNARY_PARSERS): 6380 return self.UNARY_PARSERS[self._prev.token_type](self) 6381 return self._parse_type() 6382 6383 def _parse_type( 6384 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6385 ) -> exp.Expr | None: 6386 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6387 return atom 6388 6389 if interval := parse_interval and self._parse_interval(): 6390 return self._parse_column_ops(interval) 6391 6392 index = self._index 6393 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6394 6395 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6396 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6397 if isinstance(data_type, exp.Cast): 6398 # This constructor can contain ops directly after it, for instance struct unnesting: 6399 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6400 return self._parse_column_ops(data_type) 6401 6402 if data_type: 6403 index2 = self._index 6404 this = self._parse_primary() 6405 6406 if isinstance(this, exp.Literal): 6407 literal = this.name 6408 this = self._parse_column_ops(this) 6409 6410 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6411 if parser: 6412 return parser(self, this, data_type) 6413 6414 if ( 6415 self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR 6416 and data_type.is_type(exp.DType.TIMESTAMP) 6417 and TIME_ZONE_RE.search(literal) 6418 ): 6419 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6420 6421 return self.expression(exp.Cast(this=this, to=data_type)) 6422 6423 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6424 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6425 # 6426 # If the index difference here is greater than 1, that means the parser itself must have 6427 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6428 # 6429 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6430 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6431 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6432 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6433 # 6434 # In these cases, we don't really want to return the converted type, but instead retreat 6435 # and try to parse a Column or Identifier in the section below. 6436 if data_type.expressions and index2 - index > 1: 6437 self._retreat(index2) 6438 return self._parse_column_ops(data_type) 6439 6440 self._retreat(index) 6441 6442 if fallback_to_identifier: 6443 return self._parse_id_var() 6444 6445 return self._parse_column() 6446 6447 def _parse_type_size(self) -> exp.DataTypeParam | None: 6448 this = self._parse_type() 6449 if not this: 6450 return None 6451 6452 if isinstance(this, exp.Column) and not this.table: 6453 this = exp.var(this.name.upper()) 6454 6455 return self.expression( 6456 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6457 ) 6458 6459 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6460 type_name = identifier.name 6461 6462 while self._match(TokenType.DOT): 6463 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6464 6465 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6466 6467 def _parse_types( 6468 self, 6469 check_func: bool = False, 6470 schema: bool = False, 6471 allow_identifiers: bool = True, 6472 with_collation: bool = False, 6473 ) -> exp.Expr | None: 6474 index = self._index 6475 this: exp.Expr | None = None 6476 6477 if self._match_set(self.TYPE_TOKENS): 6478 type_token = self._prev.token_type 6479 else: 6480 type_token = None 6481 identifier = allow_identifiers and self._parse_id_var( 6482 any_token=False, tokens=(TokenType.VAR,) 6483 ) 6484 if isinstance(identifier, exp.Identifier): 6485 try: 6486 tokens = self.dialect.tokenize(identifier.name) 6487 except TokenError: 6488 tokens = None 6489 6490 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6491 if len(tokens) > 1: 6492 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6493 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6494 this = self._parse_user_defined_type(identifier) 6495 else: 6496 self._retreat(self._index - 1) 6497 return None 6498 else: 6499 return None 6500 6501 if type_token == TokenType.PSEUDO_TYPE: 6502 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6503 6504 if type_token == TokenType.OBJECT_IDENTIFIER: 6505 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6506 6507 # https://materialize.com/docs/sql/types/map/ 6508 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6509 key_type = self._parse_types( 6510 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6511 ) 6512 if not self._match(TokenType.FARROW): 6513 self._retreat(index) 6514 return None 6515 6516 value_type = self._parse_types( 6517 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6518 ) 6519 if not self._match(TokenType.R_BRACKET): 6520 self._retreat(index) 6521 return None 6522 6523 return exp.DataType( 6524 this=exp.DType.MAP, 6525 expressions=[key_type, value_type], 6526 nested=True, 6527 ) 6528 6529 nested = type_token in self.NESTED_TYPE_TOKENS 6530 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6531 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6532 expressions = None 6533 maybe_func = False 6534 6535 if self._match(TokenType.L_PAREN): 6536 if is_struct: 6537 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6538 elif nested: 6539 expressions = self._parse_csv( 6540 lambda: self._parse_types( 6541 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6542 ) 6543 ) 6544 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6545 this = expressions[0] 6546 this.set("nullable", True) 6547 self._match_r_paren() 6548 return this 6549 elif type_token in self.ENUM_TYPE_TOKENS: 6550 expressions = self._parse_csv(self._parse_equality) 6551 elif type_token == TokenType.JSON: 6552 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6553 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6554 expressions = self._parse_csv(self._parse_json_type_arg) 6555 elif is_aggregate: 6556 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6557 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6558 ) 6559 if not func_or_ident: 6560 return None 6561 expressions = [func_or_ident] 6562 if self._match(TokenType.COMMA): 6563 expressions.extend( 6564 self._parse_csv( 6565 lambda: self._parse_types( 6566 check_func=check_func, 6567 schema=schema, 6568 allow_identifiers=allow_identifiers, 6569 ) 6570 ) 6571 ) 6572 else: 6573 expressions = self._parse_csv(self._parse_type_size) 6574 6575 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6576 if type_token == TokenType.VECTOR and len(expressions) == 2: 6577 expressions = self._parse_vector_expressions(expressions) 6578 6579 if not self._match(TokenType.R_PAREN): 6580 self._retreat(index) 6581 return None 6582 6583 maybe_func = True 6584 6585 values: list[exp.Expr] | None = None 6586 6587 if nested and self._match(TokenType.LT): 6588 if is_struct: 6589 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6590 else: 6591 expressions = self._parse_csv( 6592 lambda: self._parse_types( 6593 check_func=check_func, 6594 schema=schema, 6595 allow_identifiers=allow_identifiers, 6596 with_collation=True, 6597 ) 6598 ) 6599 6600 if not self._match(TokenType.GT): 6601 self.raise_error("Expecting >") 6602 6603 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6604 values = self._parse_csv(self._parse_disjunction) 6605 if not values and is_struct: 6606 values = None 6607 self._retreat(self._index - 1) 6608 else: 6609 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6610 6611 if type_token in self.TIMESTAMPS: 6612 if self._match_text_seq("WITH", "TIME", "ZONE"): 6613 maybe_func = False 6614 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6615 this = exp.DataType(this=tz_type, expressions=expressions) 6616 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6617 maybe_func = False 6618 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6619 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6620 maybe_func = False 6621 elif type_token == TokenType.INTERVAL: 6622 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6623 unit = self._parse_var(upper=True) 6624 if self._match_text_seq("TO"): 6625 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6626 6627 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6628 else: 6629 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6630 elif type_token == TokenType.VOID: 6631 this = exp.DataType(this=exp.DType.NULL) 6632 6633 if maybe_func and check_func: 6634 index2 = self._index 6635 peek = self._parse_string() 6636 6637 if not peek: 6638 self._retreat(index) 6639 return None 6640 6641 self._retreat(index2) 6642 6643 if not this: 6644 assert type_token is not None 6645 if self._match_text_seq("UNSIGNED"): 6646 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6647 if not unsigned_type_token: 6648 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6649 6650 type_token = unsigned_type_token or type_token 6651 6652 # NULLABLE without parentheses can be a column (Presto/Trino) 6653 if type_token == TokenType.NULLABLE and not expressions: 6654 self._retreat(index) 6655 return None 6656 6657 this = exp.DataType( 6658 this=exp.DType[type_token.name], 6659 expressions=expressions, 6660 nested=nested, 6661 ) 6662 6663 # Empty arrays/structs are allowed 6664 if values is not None: 6665 cls = exp.Struct if is_struct else exp.Array 6666 this = exp.cast(cls(expressions=values), this, copy=False) 6667 6668 elif expressions: 6669 this.set("expressions", expressions) 6670 6671 # https://materialize.com/docs/sql/types/list/#type-name 6672 while self._match(TokenType.LIST): 6673 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 6674 6675 index = self._index 6676 6677 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 6678 matched_array = self._match(TokenType.ARRAY) 6679 6680 while self._curr: 6681 datatype_token = self._prev.token_type 6682 matched_l_bracket = self._match(TokenType.L_BRACKET) 6683 6684 if (not matched_l_bracket and not matched_array) or ( 6685 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 6686 ): 6687 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 6688 # not to be confused with the fixed size array parsing 6689 break 6690 6691 matched_array = False 6692 values = self._parse_csv(self._parse_disjunction) or None 6693 if ( 6694 values 6695 and not schema 6696 and ( 6697 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 6698 or datatype_token == TokenType.ARRAY 6699 or not self._match(TokenType.R_BRACKET, advance=False) 6700 ) 6701 ): 6702 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 6703 # ARRAY[1] should retreat and instead be parsed into exp.Array in contrast to INT[x][y] which denotes a fixed-size array data type 6704 self._retreat(index) 6705 break 6706 6707 this = exp.DataType( 6708 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 6709 ) 6710 self._match(TokenType.R_BRACKET) 6711 6712 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 6713 converter = self.TYPE_CONVERTERS.get(this.this) 6714 if converter: 6715 this = converter(t.cast(exp.DataType, this)) 6716 6717 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 6718 this.set("collate", self._parse_identifier() or self._parse_column()) 6719 6720 return this 6721 6722 def _parse_json_type_arg(self) -> exp.Expr | None: 6723 """Parse a single argument to ClickHouse's JSON type.""" 6724 6725 # SKIP col or SKIP REGEXP 'pattern' 6726 if self._match_text_seq("SKIP"): 6727 regexp = self._match(TokenType.RLIKE) 6728 arg = self._parse_column() 6729 if isinstance(arg, exp.Column): 6730 arg = arg.to_dot() 6731 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 6732 6733 param_or_col = self._parse_column() 6734 if not isinstance(param_or_col, exp.Column): 6735 return None 6736 6737 # Parameter: name=value (e.g., max_dynamic_paths=2) 6738 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 6739 param = param_or_col.name 6740 value = self._parse_primary() 6741 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 6742 6743 # Column type hint: col_name Type 6744 col = param_or_col.to_dot() 6745 kind = self._parse_types(check_func=False, allow_identifiers=False) 6746 return self.expression(exp.ColumnDef(this=col, kind=kind)) 6747 6748 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 6749 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 6750 6751 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 6752 index = self._index 6753 6754 if ( 6755 self._curr 6756 and self._next 6757 and self._curr.token_type in self.TYPE_TOKENS 6758 and self._next.token_type in self.TYPE_TOKENS 6759 ): 6760 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 6761 # type token. Without this, the list will be parsed as a type and we'll eventually crash 6762 this = self._parse_id_var() 6763 else: 6764 this = ( 6765 self._parse_type(parse_interval=False, fallback_to_identifier=True) 6766 or self._parse_id_var() 6767 ) 6768 6769 self._match(TokenType.COLON) 6770 6771 if ( 6772 type_required 6773 and not isinstance(this, exp.DataType) 6774 and not self._match_set(self.TYPE_TOKENS, advance=False) 6775 ): 6776 self._retreat(index) 6777 return self._parse_types() 6778 6779 return self._parse_column_def(this) 6780 6781 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 6782 if not self._match_text_seq("AT", "TIME", "ZONE"): 6783 return this 6784 return self._parse_at_time_zone( 6785 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 6786 ) 6787 6788 def _parse_atom(self) -> exp.Expr | None: 6789 if ( 6790 self._curr.token_type in self.IDENTIFIER_TOKENS 6791 and (column := self._parse_column()) is not None 6792 ): 6793 return column 6794 6795 token = self._curr 6796 token_type = token.token_type 6797 6798 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 6799 return None 6800 6801 next_type = self._next.token_type 6802 6803 if ( 6804 next_type in self.COLUMN_OPERATORS 6805 or next_type in self.COLUMN_POSTFIX_TOKENS 6806 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 6807 ): 6808 return None 6809 6810 self._advance() 6811 return primary_parser(self, token) 6812 6813 def _parse_column(self) -> exp.Expr | None: 6814 column: exp.Expr | None = self._parse_column_parts_fast() 6815 if column is None: 6816 this = self._parse_column_reference() 6817 if not this: 6818 this = self._parse_bracket(this) 6819 column = self._parse_column_ops(this) if this else this 6820 6821 if column: 6822 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 6823 column.set("join_mark", self._match(TokenType.JOIN_MARKER)) 6824 if self.COLON_IS_VARIANT_EXTRACT: 6825 column = self._parse_colon_as_variant_extract(column) 6826 6827 return column 6828 6829 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 6830 """Fast path for simple column and dot references (a, a.b, ...). 6831 6832 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 6833 that nothing complex follows. If it does, retreats and returns None so 6834 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 6835 """ 6836 index = self._index 6837 parts: list[exp.Identifier] | None = None 6838 all_comments: list[str] | None = None 6839 6840 while self._match_set(self.IDENTIFIER_TOKENS): 6841 token = self._prev 6842 comments = self._prev_comments 6843 6844 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 6845 self._retreat(index) 6846 return None 6847 6848 has_dot = self._match(TokenType.DOT) 6849 curr_tt = self._curr.token_type 6850 6851 if not has_dot: 6852 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 6853 self._retreat(index) 6854 return None 6855 elif curr_tt not in self.IDENTIFIER_TOKENS: 6856 self._retreat(index) 6857 return None 6858 6859 if parts is None: 6860 parts = [] 6861 6862 if comments: 6863 if all_comments is None: 6864 all_comments = [] 6865 all_comments.extend(comments) 6866 self._prev_comments = [] 6867 6868 parts.append( 6869 self.expression( 6870 exp.Identifier( 6871 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 6872 ), 6873 token, 6874 ) 6875 ) 6876 6877 if not has_dot: 6878 break 6879 6880 if parts is None: 6881 return None 6882 6883 n = len(parts) 6884 6885 if n == 1: 6886 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 6887 elif n == 2: 6888 column = exp.Column(this=parts[1], table=parts[0]) 6889 elif n == 3: 6890 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 6891 else: 6892 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 6893 6894 for i in range(4, n): 6895 column = exp.Dot(this=column, expression=parts[i]) 6896 6897 if all_comments: 6898 column.add_comments(all_comments) 6899 6900 return column 6901 6902 def _parse_column_reference(self) -> exp.Expr | None: 6903 this = self._parse_field() 6904 if ( 6905 not this 6906 and self._match(TokenType.VALUES, advance=False) 6907 and self.VALUES_FOLLOWED_BY_PAREN 6908 and (not self._next or self._next.token_type != TokenType.L_PAREN) 6909 ): 6910 this = self._parse_id_var() 6911 6912 if isinstance(this, exp.Identifier): 6913 # We bubble up comments from the Identifier to the Column 6914 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 6915 6916 return this 6917 6918 def _build_json_extract( 6919 self, 6920 this: exp.Expr | None, 6921 path_parts: list[exp.JSONPathPart], 6922 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 6923 if len(path_parts) > 1: 6924 this = self.expression( 6925 exp.JSONExtract( 6926 this=this, 6927 expression=exp.JSONPath(expressions=path_parts), 6928 variant_extract=True, 6929 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 6930 ) 6931 ) 6932 path_parts = [exp.JSONPathRoot()] 6933 6934 return this, path_parts 6935 6936 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 6937 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 6938 6939 while self._match(TokenType.COLON): 6940 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 6941 this, path_parts = self._build_json_extract(this, path_parts) 6942 6943 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 6944 6945 if key: 6946 quoted = isinstance(key, exp.Identifier) and key.quoted 6947 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 6948 6949 while True: 6950 if self._match(TokenType.DOT): 6951 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 6952 6953 if next_key: 6954 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 6955 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 6956 elif self._match(TokenType.L_BRACKET): 6957 bracket_expr = self._parse_bracket_key_value() 6958 6959 if not self._match(TokenType.R_BRACKET): 6960 self.raise_error("Expected ]") 6961 6962 if bracket_expr: 6963 if bracket_expr.is_string: 6964 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 6965 elif bracket_expr.is_star: 6966 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 6967 elif bracket_expr.is_number: 6968 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 6969 else: 6970 this, path_parts = self._build_json_extract(this, path_parts) 6971 6972 this = self.expression( 6973 exp.Bracket( 6974 this=this, expressions=[bracket_expr], json_access=True 6975 ), 6976 ) 6977 6978 elif self._match(TokenType.DCOLON): 6979 this, path_parts = self._build_json_extract(this, path_parts) 6980 6981 cast_type = self._parse_types() 6982 if cast_type: 6983 this = self.expression(exp.Cast(this=this, to=cast_type)) 6984 else: 6985 self.raise_error("Expected type after '::'") 6986 else: 6987 break 6988 6989 this, _ = self._build_json_extract(this, path_parts) 6990 6991 return this 6992 6993 def _parse_dcolon(self) -> exp.Expr | None: 6994 return self._parse_types() 6995 6996 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 6997 while self._curr.token_type in self.BRACKETS: 6998 this = self._parse_bracket(this) 6999 7000 column_operators = self.COLUMN_OPERATORS 7001 cast_column_operators = self.CAST_COLUMN_OPERATORS 7002 while self._curr: 7003 op_token = self._curr.token_type 7004 7005 if op_token not in column_operators: 7006 break 7007 op = column_operators[op_token] 7008 self._advance() 7009 7010 if op_token in cast_column_operators: 7011 field = self._parse_dcolon() 7012 if not field: 7013 self.raise_error("Expected type") 7014 elif op and self._curr: 7015 field = self._parse_column_reference() or self._parse_bitwise() 7016 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7017 field = self._parse_column_ops(field) 7018 else: 7019 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7020 field = self._parse_field(any_token=True, anonymous_func=True) 7021 7022 # In t.true, t.null we should produce an Identifier node 7023 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7024 field = self.expression( 7025 exp.Identifier(this=self._prev.text), 7026 comments=field.comments, 7027 ) 7028 7029 # Function calls can be qualified, e.g., x.y.FOO() 7030 # This converts the final AST to a series of Dots leading to the function call 7031 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7032 if isinstance(field, (exp.Func, exp.Window)) and this: 7033 this = this.transform( 7034 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7035 ) 7036 7037 if op: 7038 this = op(self, this, field) 7039 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7040 this = self.expression( 7041 exp.Column( 7042 this=field, 7043 table=this.this, 7044 db=this.args.get("table"), 7045 catalog=this.args.get("db"), 7046 ), 7047 comments=this.comments, 7048 ) 7049 elif isinstance(field, exp.Window): 7050 # Move the exp.Dot's to the window's function 7051 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7052 field.set("this", window_func) 7053 this = field 7054 else: 7055 this = self.expression(exp.Dot(this=this, expression=field)) 7056 7057 if field and field.comments: 7058 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7059 7060 this = self._parse_bracket(this) 7061 7062 return this 7063 7064 def _parse_paren(self) -> exp.Expr | None: 7065 if not self._match(TokenType.L_PAREN): 7066 return None 7067 7068 comments = self._prev_comments 7069 query = self._parse_select() 7070 7071 if query: 7072 expressions = [query] 7073 else: 7074 expressions = self._parse_expressions() 7075 7076 this = seq_get(expressions, 0) 7077 7078 if not this and self._match(TokenType.R_PAREN, advance=False): 7079 this = self.expression(exp.Tuple()) 7080 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7081 this = self.expression(exp.Tuple(expressions=expressions)) 7082 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7083 this = self._parse_subquery(this=this, parse_alias=False) 7084 elif isinstance(this, (exp.Subquery, exp.Values)): 7085 this = self._parse_subquery( 7086 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7087 parse_alias=False, 7088 ) 7089 else: 7090 this = self.expression(exp.Paren(this=this)) 7091 7092 if this: 7093 this.add_comments(comments) 7094 7095 self._match_r_paren(expression=this) 7096 7097 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7098 return self._parse_window(this) 7099 7100 return this 7101 7102 def _parse_primary(self) -> exp.Expr | None: 7103 if self._match_set(self.PRIMARY_PARSERS): 7104 token_type = self._prev.token_type 7105 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7106 7107 if token_type == TokenType.STRING: 7108 expressions = [primary] 7109 while self._match(TokenType.STRING, advance=False): 7110 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7111 self.raise_error( 7112 "Adjacent string literals need to be separated by whitespace or comments" 7113 ) 7114 7115 self._advance() 7116 expressions.append(exp.Literal.string(self._prev.text)) 7117 7118 if len(expressions) > 1: 7119 return self.expression( 7120 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7121 ) 7122 7123 return primary 7124 7125 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7126 return exp.Literal.number(f"0.{self._prev.text}") 7127 7128 return self._parse_paren() 7129 7130 def _parse_field( 7131 self, 7132 any_token: bool = False, 7133 tokens: t.Collection[TokenType] | None = None, 7134 anonymous_func: bool = False, 7135 ) -> exp.Expr | None: 7136 if anonymous_func: 7137 field = ( 7138 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7139 or self._parse_primary() 7140 ) 7141 else: 7142 field = self._parse_primary() or self._parse_function( 7143 anonymous=anonymous_func, any_token=any_token 7144 ) 7145 return field or self._parse_id_var(any_token=any_token, tokens=tokens) 7146 7147 def _parse_function( 7148 self, 7149 functions: dict[str, t.Callable] | None = None, 7150 anonymous: bool = False, 7151 optional_parens: bool = True, 7152 any_token: bool = False, 7153 ) -> exp.Expr | None: 7154 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7155 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7156 fn_syntax = False 7157 if ( 7158 self._match(TokenType.L_BRACE, advance=False) 7159 and self._next 7160 and self._next.text.upper() == "FN" 7161 ): 7162 self._advance(2) 7163 fn_syntax = True 7164 7165 func = self._parse_function_call( 7166 functions=functions, 7167 anonymous=anonymous, 7168 optional_parens=optional_parens, 7169 any_token=any_token, 7170 ) 7171 7172 if fn_syntax: 7173 self._match(TokenType.R_BRACE) 7174 7175 return func 7176 7177 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7178 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7179 7180 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7181 args = self._parse_function_args(alias=False) 7182 if not args: 7183 self.raise_error("Expected at least one argument") 7184 7185 # Wrapped so the connector keeps its precedence in the parent context 7186 return exp.Paren(this=connector(*args, copy=False)) 7187 7188 def _parse_function_call( 7189 self, 7190 functions: dict[str, t.Callable] | None = None, 7191 anonymous: bool = False, 7192 optional_parens: bool = True, 7193 any_token: bool = False, 7194 ) -> exp.Expr | None: 7195 if not self._curr: 7196 return None 7197 7198 comments = self._curr.comments 7199 prev = self._prev 7200 token = self._curr 7201 token_type = self._curr.token_type 7202 this: str | exp.Expr = self._curr.text 7203 upper = self._curr.text.upper() 7204 7205 after_dot = prev.token_type == TokenType.DOT 7206 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7207 if ( 7208 optional_parens 7209 and parser 7210 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7211 and not after_dot 7212 ): 7213 self._advance() 7214 return self._parse_window(parser(self)) 7215 7216 if self._next.token_type != TokenType.L_PAREN: 7217 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7218 self._advance() 7219 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7220 7221 return None 7222 7223 if any_token: 7224 if token_type in self.RESERVED_TOKENS: 7225 return None 7226 elif token_type not in self.FUNC_TOKENS: 7227 return None 7228 7229 self._advance(2) 7230 7231 parser = self.FUNCTION_PARSERS.get(upper) 7232 if parser and not anonymous: 7233 result = parser(self) 7234 else: 7235 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7236 7237 if subquery_predicate: 7238 expr = None 7239 if self._curr.token_type in self.SUBQUERY_TOKENS: 7240 expr = self._parse_select() 7241 self._match_r_paren() 7242 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7243 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7244 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7245 self._advance(-1) 7246 expr = self._parse_bitwise() 7247 7248 if expr: 7249 return self.expression(subquery_predicate(this=expr), comments=comments) 7250 7251 if functions is None: 7252 functions = self.FUNCTIONS 7253 7254 function = functions.get(upper) 7255 known_function = function and not anonymous 7256 7257 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7258 args = self._parse_function_args(alias) 7259 7260 post_func_comments = self._curr.comments if self._curr else None 7261 if known_function and post_func_comments: 7262 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7263 # call we'll construct it as exp.Anonymous, even if it's "known" 7264 if any( 7265 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7266 for comment in post_func_comments 7267 ): 7268 known_function = False 7269 7270 if alias and known_function: 7271 args = self._kv_to_prop_eq(args) 7272 7273 if known_function: 7274 func_builder = t.cast(t.Callable, function) 7275 7276 # mypyc compiled functions don't have __code__, so we use 7277 # try/except to check if func_builder accepts 'dialect'. 7278 try: 7279 func = func_builder(args) 7280 except TypeError: 7281 func = func_builder(args, dialect=self.dialect) 7282 7283 func = self.validate_expression(func, args) 7284 if self.dialect.PRESERVE_ORIGINAL_NAMES: 7285 func.meta["name"] = this 7286 7287 result = func 7288 else: 7289 if token_type == TokenType.IDENTIFIER: 7290 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7291 7292 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7293 7294 result = result.update_positions(token) 7295 7296 if isinstance(result, exp.Expr): 7297 result.add_comments(comments) 7298 7299 if parser: 7300 self._match(TokenType.R_PAREN, expression=result) 7301 else: 7302 self._match_r_paren(result) 7303 return self._parse_window(result) 7304 7305 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7306 return expression 7307 7308 def _kv_to_prop_eq( 7309 self, expressions: list[exp.Expr], parse_map: bool = False 7310 ) -> list[exp.Expr]: 7311 transformed = [] 7312 7313 for index, e in enumerate(expressions): 7314 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7315 if isinstance(e, exp.Alias): 7316 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7317 7318 if not isinstance(e, exp.PropertyEQ): 7319 e = self.expression( 7320 exp.PropertyEQ( 7321 this=e.this if parse_map else exp.to_identifier(e.this.name), 7322 expression=e.expression, 7323 ) 7324 ) 7325 7326 if isinstance(e.this, exp.Column): 7327 e.this.replace(e.this.this) 7328 else: 7329 e = self._to_prop_eq(e, index) 7330 7331 transformed.append(e) 7332 7333 return transformed 7334 7335 def _parse_function_properties(self) -> exp.Properties | None: 7336 # Skip the generic `key = value` fallback in _parse_property since this 7337 # runs post-AS where a function body like `name = expr` can be misread 7338 # as a property. 7339 properties = [] 7340 while True: 7341 if self._match_texts(self.PROPERTY_PARSERS): 7342 keyword = self._prev.text.upper() 7343 prop = self.PROPERTY_PARSERS[keyword](self) 7344 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7345 keyword = self._prev.text.upper() 7346 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7347 else: 7348 break 7349 if not prop: 7350 self.raise_error(f"Failed to parse property '{keyword}'") 7351 break 7352 for p in ensure_list(prop): 7353 properties.append(p) 7354 7355 return self.expression(exp.Properties(expressions=properties)) if properties else None 7356 7357 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7358 return self._parse_statement() 7359 7360 def _parse_function_parameter(self) -> exp.Expr | None: 7361 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7362 7363 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7364 this = self._parse_table_parts(schema=True) 7365 7366 if not self._match(TokenType.L_PAREN): 7367 return this 7368 7369 expressions = self._parse_csv(self._parse_function_parameter) 7370 self._match_r_paren() 7371 return self.expression( 7372 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7373 ) 7374 7375 def _parse_macro_overloads( 7376 self, 7377 this: exp.UserDefinedFunction, 7378 first_body: exp.Expr, 7379 first_is_table: bool = False, 7380 ) -> exp.MacroOverloads: 7381 overloads = [ 7382 self.expression( 7383 exp.MacroOverload( 7384 this=first_body, 7385 expressions=this.expressions or None, 7386 is_table=first_is_table, 7387 ) 7388 ) 7389 ] 7390 this.set("expressions", None) 7391 this.set("wrapped", False) 7392 7393 while self._match(TokenType.COMMA): 7394 if not self._match(TokenType.L_PAREN): 7395 break 7396 7397 params = self._parse_csv(self._parse_function_parameter) 7398 self._match_r_paren() 7399 7400 if not self._match(TokenType.ALIAS): 7401 break 7402 7403 is_table = self._match(TokenType.TABLE) 7404 body = self._parse_expression() 7405 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7406 overloads.append(self.expression(macro)) 7407 7408 return self.expression(exp.MacroOverloads(expressions=overloads)) 7409 7410 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7411 literal = self._parse_primary() 7412 if literal: 7413 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7414 7415 return self._identifier_expression(token) 7416 7417 def _parse_session_parameter(self) -> exp.SessionParameter: 7418 kind = None 7419 this = self._parse_id_var() or self._parse_primary() 7420 7421 if this and self._match(TokenType.DOT): 7422 kind = this.name 7423 this = self._parse_var() or self._parse_primary() 7424 7425 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7426 7427 def _parse_lambda_arg(self) -> exp.Expr | None: 7428 return self._parse_id_var() 7429 7430 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7431 next_token_type = self._next.token_type 7432 7433 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7434 if ( 7435 next_token_type in self.LAMBDA_ARG_TERMINATORS 7436 and (atom := self._parse_atom()) is not None 7437 ): 7438 return atom 7439 7440 index = self._index 7441 7442 if self._match(TokenType.L_PAREN): 7443 expressions = t.cast( 7444 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7445 ) 7446 7447 if not self._match(TokenType.R_PAREN): 7448 self._retreat(index) 7449 elif self._match_set(self.LAMBDAS): 7450 return self.LAMBDAS[self._prev.token_type](self, expressions) 7451 else: 7452 self._retreat(index) 7453 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7454 expressions = [self._parse_lambda_arg()] 7455 7456 if self._match_set(self.LAMBDAS): 7457 return self.LAMBDAS[self._prev.token_type](self, expressions) 7458 7459 self._retreat(index) 7460 7461 this: exp.Expr | None 7462 7463 if self._match(TokenType.DISTINCT): 7464 this = self.expression( 7465 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7466 ) 7467 else: 7468 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7469 this = self._parse_select_or_expression(alias=alias) 7470 7471 return self._parse_limit( 7472 self._parse_respect_or_ignore_nulls( 7473 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7474 ) 7475 ) 7476 7477 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7478 index = self._index 7479 if not self._match(TokenType.L_PAREN): 7480 return this 7481 7482 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7483 # expr can be of both types 7484 if self._match_set(self.SELECT_START_TOKENS): 7485 self._retreat(index) 7486 return this 7487 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7488 self._match_r_paren() 7489 return self.expression(exp.Schema(this=this, expressions=args)) 7490 7491 def _parse_field_def(self) -> exp.Expr | None: 7492 return self._parse_column_def(self._parse_field(any_token=True)) 7493 7494 def _parse_column_def( 7495 self, this: exp.Expr | None, computed_column: bool = True 7496 ) -> exp.Expr | None: 7497 # column defs are not really columns, they're identifiers 7498 if isinstance(this, exp.Column): 7499 this = this.this 7500 7501 if not computed_column: 7502 self._match(TokenType.ALIAS) 7503 7504 kind = self._parse_types(schema=True) 7505 7506 if self._match_text_seq("FOR", "ORDINALITY"): 7507 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7508 7509 constraints: list[exp.Expr] = [] 7510 7511 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7512 ("ALIAS", "MATERIALIZED") 7513 ): 7514 # Match storage before _parse_types so STORED is not treated as a data type 7515 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7516 persisted = self._prev.text.upper() == "MATERIALIZED" 7517 expression = self._parse_disjunction() 7518 if not persisted: 7519 if self._match_text_seq("PERSISTED"): 7520 persisted = True 7521 elif self._match_texts(("STORED", "VIRTUAL")): 7522 persisted = self._prev.text.upper() == "STORED" 7523 constraint_kind = exp.ComputedColumnConstraint( 7524 this=expression, 7525 persisted=persisted, 7526 data_type=exp.Var(this="AUTO") 7527 if self._match_text_seq("AUTO") 7528 else self._parse_types(), 7529 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7530 ) 7531 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7532 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7533 in_out_constraint = self.expression( 7534 exp.InOutColumnConstraint( 7535 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7536 ) 7537 ) 7538 constraints.append(in_out_constraint) 7539 kind = self._parse_types() 7540 elif ( 7541 kind 7542 and self._match(TokenType.ALIAS, advance=False) 7543 and ( 7544 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7545 or self._next.token_type == TokenType.L_PAREN 7546 ) 7547 ): 7548 self._advance() 7549 constraints.append( 7550 self.expression( 7551 exp.ColumnConstraint( 7552 kind=exp.ComputedColumnConstraint( 7553 this=self._parse_disjunction(), 7554 persisted=self._match_texts(("STORED", "VIRTUAL")) 7555 and self._prev.text.upper() == "STORED", 7556 ) 7557 ) 7558 ) 7559 ) 7560 7561 while True: 7562 constraint = self._parse_column_constraint() 7563 if not constraint: 7564 break 7565 constraints.append(constraint) 7566 7567 if not kind and not constraints: 7568 return this 7569 7570 position = None 7571 if self._match_texts(("FIRST", "AFTER")): 7572 pos = self._prev.text 7573 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7574 7575 return self.expression( 7576 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7577 ) 7578 7579 def _parse_auto_increment( 7580 self, 7581 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7582 start = None 7583 increment = None 7584 order = None 7585 7586 if self._match(TokenType.L_PAREN, advance=False): 7587 args = self._parse_wrapped_csv(self._parse_bitwise) 7588 start = seq_get(args, 0) 7589 increment = seq_get(args, 1) 7590 7591 # The remaining parts form an unordered bag and any of them can be omitted, in which 7592 # case the engine falls back to its own default, so they're parsed independently. 7593 while True: 7594 if self._match_text_seq("START"): 7595 start = self._parse_bitwise() 7596 elif self._match_text_seq("INCREMENT"): 7597 increment = self._parse_bitwise() 7598 elif self._match_text_seq("ORDER"): 7599 order = True 7600 elif self._match_text_seq("NOORDER"): 7601 order = False 7602 else: 7603 break 7604 7605 if start or increment or order is not None: 7606 return exp.GeneratedAsIdentityColumnConstraint( 7607 start=start, increment=increment, this=False, order=order 7608 ) 7609 7610 return exp.AutoIncrementColumnConstraint() 7611 7612 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7613 if not self._match(TokenType.L_PAREN, advance=False): 7614 return None 7615 7616 return self.expression( 7617 exp.CheckColumnConstraint( 7618 this=self._parse_wrapped(self._parse_assignment), 7619 enforced=self._match_text_seq("ENFORCED"), 7620 ) 7621 ) 7622 7623 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7624 if not self._match_text_seq("REFRESH"): 7625 self._retreat(self._index - 1) 7626 return None 7627 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7628 7629 def _parse_compress(self) -> exp.CompressColumnConstraint: 7630 if self._match(TokenType.L_PAREN, advance=False): 7631 return self.expression( 7632 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7633 ) 7634 7635 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7636 7637 def _parse_generated_as_identity( 7638 self, 7639 ) -> ( 7640 exp.GeneratedAsIdentityColumnConstraint 7641 | exp.ComputedColumnConstraint 7642 | exp.GeneratedAsRowColumnConstraint 7643 ): 7644 if self._match_text_seq("BY", "DEFAULT"): 7645 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7646 this = self.expression( 7647 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7648 ) 7649 else: 7650 self._match_text_seq("ALWAYS") 7651 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7652 7653 self._match(TokenType.ALIAS) 7654 7655 if self._match_text_seq("ROW"): 7656 start = self._match_text_seq("START") 7657 if not start: 7658 self._match(TokenType.END) 7659 hidden = self._match_text_seq("HIDDEN") 7660 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 7661 7662 identity = self._match_text_seq("IDENTITY") 7663 7664 if self._match(TokenType.L_PAREN): 7665 if self._match_text_seq("START", "WITH"): 7666 this.set("start", self._parse_bitwise()) 7667 if self._match_text_seq("INCREMENT", "BY"): 7668 this.set("increment", self._parse_bitwise()) 7669 if self._match_text_seq("MINVALUE"): 7670 this.set("minvalue", self._parse_bitwise()) 7671 if self._match_text_seq("MAXVALUE"): 7672 this.set("maxvalue", self._parse_bitwise()) 7673 7674 if self._match_text_seq("CYCLE"): 7675 this.set("cycle", True) 7676 elif self._match_text_seq("NO", "CYCLE"): 7677 this.set("cycle", False) 7678 7679 if not identity: 7680 this.set("expression", self._parse_range()) 7681 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 7682 args = self._parse_csv(self._parse_bitwise) 7683 this.set("start", seq_get(args, 0)) 7684 this.set("increment", seq_get(args, 1)) 7685 7686 self._match_r_paren() 7687 7688 return this 7689 7690 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 7691 self._match_text_seq("LENGTH") 7692 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 7693 7694 def _parse_not_constraint(self) -> exp.Expr | None: 7695 if self._match_text_seq("NULL"): 7696 return self.expression(exp.NotNullColumnConstraint()) 7697 if self._match_text_seq("CASESPECIFIC"): 7698 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 7699 if self._match_text_seq("FOR", "REPLICATION"): 7700 return self.expression(exp.NotForReplicationColumnConstraint()) 7701 7702 # Unconsume the `NOT` token 7703 self._retreat(self._index - 1) 7704 return None 7705 7706 def _parse_column_constraint(self) -> exp.Expr | None: 7707 this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None 7708 7709 procedure_option_follows = ( 7710 self._match(TokenType.WITH, advance=False) 7711 and self._next 7712 and self._next.text.upper() in self.PROCEDURE_OPTIONS 7713 ) 7714 7715 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 7716 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 7717 if not constraint: 7718 self._retreat(self._index - 1) 7719 return None 7720 7721 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 7722 7723 if self._match_text_seq("CHARACTER", "SET"): 7724 return self.expression( 7725 exp.ColumnConstraint( 7726 this=this, 7727 kind=self.expression( 7728 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 7729 ), 7730 ) 7731 ) 7732 7733 return this 7734 7735 def _parse_constraint(self) -> exp.Expr | None: 7736 if not self._match(TokenType.CONSTRAINT): 7737 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 7738 7739 return self.expression( 7740 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 7741 ) 7742 7743 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 7744 constraints = [] 7745 while True: 7746 constraint = self._parse_unnamed_constraint() or self._parse_function() 7747 if not constraint: 7748 break 7749 constraints.append(constraint) 7750 7751 return constraints 7752 7753 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 7754 index = self._index 7755 7756 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 7757 constraints or self.CONSTRAINT_PARSERS 7758 ): 7759 return None 7760 7761 constraint_key = self._prev.text.upper() 7762 if constraint_key not in self.CONSTRAINT_PARSERS: 7763 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 7764 7765 result = self.CONSTRAINT_PARSERS[constraint_key](self) 7766 if not result: 7767 self._retreat(index) 7768 7769 return result 7770 7771 def _parse_unique_key(self) -> exp.Expr | None: 7772 if ( 7773 self._curr 7774 and self._curr.token_type != TokenType.IDENTIFIER 7775 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 7776 ): 7777 return None 7778 return self._parse_id_var(any_token=False) 7779 7780 def _parse_unique(self) -> exp.UniqueColumnConstraint: 7781 self._match_texts(("KEY", "INDEX")) 7782 return self.expression( 7783 exp.UniqueColumnConstraint( 7784 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 7785 this=self._parse_schema(self._parse_unique_key()), 7786 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 7787 on_conflict=self._parse_on_conflict(), 7788 options=self._parse_key_constraint_options(), 7789 ) 7790 ) 7791 7792 def _parse_key_constraint_options(self) -> list[str]: 7793 options = [] 7794 while True: 7795 if not self._curr: 7796 break 7797 7798 if self._match(TokenType.ON): 7799 action = None 7800 on = self._advance_any() and self._prev.text 7801 7802 if self._match_text_seq("NO", "ACTION"): 7803 action = "NO ACTION" 7804 elif self._match_text_seq("CASCADE"): 7805 action = "CASCADE" 7806 elif self._match_text_seq("RESTRICT"): 7807 action = "RESTRICT" 7808 elif self._match_pair(TokenType.SET, TokenType.NULL): 7809 action = "SET NULL" 7810 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 7811 action = "SET DEFAULT" 7812 else: 7813 self.raise_error("Invalid key constraint") 7814 7815 options.append(f"ON {on} {action}") 7816 else: 7817 var = self._parse_var_from_options( 7818 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 7819 ) 7820 if not var: 7821 break 7822 options.append(var.name) 7823 7824 return options 7825 7826 def _parse_references(self, match: bool = True) -> exp.Reference | None: 7827 if match and not self._match(TokenType.REFERENCES): 7828 return None 7829 7830 expressions: list | None = None 7831 this = self._parse_table(schema=True) 7832 options = self._parse_key_constraint_options() 7833 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 7834 7835 def _parse_foreign_key(self) -> exp.ForeignKey: 7836 expressions = ( 7837 self._parse_wrapped_id_vars() 7838 if not self._match(TokenType.REFERENCES, advance=False) 7839 else None 7840 ) 7841 reference = self._parse_references() 7842 on_options = {} 7843 7844 while self._match(TokenType.ON): 7845 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 7846 self.raise_error("Expected DELETE or UPDATE") 7847 7848 kind = self._prev.text.lower() 7849 7850 if self._match_text_seq("NO", "ACTION"): 7851 action = "NO ACTION" 7852 elif self._match(TokenType.SET): 7853 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 7854 action = "SET " + self._prev.text.upper() 7855 else: 7856 self._advance() 7857 action = self._prev.text.upper() 7858 7859 on_options[kind] = action 7860 7861 return self.expression( 7862 exp.ForeignKey( 7863 expressions=expressions, 7864 reference=reference, 7865 options=self._parse_key_constraint_options(), 7866 **on_options, 7867 ) 7868 ) 7869 7870 def _parse_primary_key_part(self) -> exp.Expr | None: 7871 return self._parse_field() 7872 7873 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 7874 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 7875 self._retreat(self._index - 1) 7876 return None 7877 7878 id_vars = self._parse_wrapped_id_vars() 7879 return self.expression( 7880 exp.PeriodForSystemTimeConstraint( 7881 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 7882 ) 7883 ) 7884 7885 def _parse_primary_key( 7886 self, 7887 wrapped_optional: bool = False, 7888 in_props: bool = False, 7889 named_primary_key: bool = False, 7890 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 7891 desc = ( 7892 self._prev.token_type == TokenType.DESC 7893 if self._match_set((TokenType.ASC, TokenType.DESC)) 7894 else None 7895 ) 7896 7897 this = None 7898 if ( 7899 named_primary_key 7900 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 7901 and self._next 7902 and self._next.token_type == TokenType.L_PAREN 7903 ): 7904 this = self._parse_id_var() 7905 7906 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 7907 return self.expression( 7908 exp.PrimaryKeyColumnConstraint( 7909 desc=desc, options=self._parse_key_constraint_options() 7910 ) 7911 ) 7912 7913 expressions = self._parse_wrapped_csv( 7914 self._parse_primary_key_part, optional=wrapped_optional 7915 ) 7916 7917 return self.expression( 7918 exp.PrimaryKey( 7919 this=this, 7920 expressions=expressions, 7921 include=self._parse_index_params(), 7922 options=self._parse_key_constraint_options(), 7923 ) 7924 ) 7925 7926 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 7927 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 7928 7929 def _parse_odbc_datetime_literal(self) -> exp.Expr: 7930 """ 7931 Parses a datetime column in ODBC format. We parse the column into the corresponding 7932 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 7933 same as we did for `DATE('yyyy-mm-dd')`. 7934 7935 Reference: 7936 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 7937 """ 7938 self._match(TokenType.VAR) 7939 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 7940 expression = self.expression(exp_class(this=self._parse_string())) 7941 if not self._match(TokenType.R_BRACE): 7942 self.raise_error("Expected }") 7943 return expression 7944 7945 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 7946 if not self._match_set(self.BRACKETS): 7947 return this 7948 7949 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 7950 map_token = seq_get(self._tokens, self._index - 2) 7951 parse_map = map_token is not None and map_token.text.upper() == "MAP" 7952 else: 7953 parse_map = False 7954 7955 bracket_kind = self._prev.token_type 7956 if ( 7957 bracket_kind == TokenType.L_BRACE 7958 and self._curr 7959 and self._curr.token_type == TokenType.VAR 7960 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 7961 ): 7962 return self._parse_odbc_datetime_literal() 7963 7964 expressions = self._parse_csv( 7965 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 7966 ) 7967 7968 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 7969 self.raise_error("Expected ]") 7970 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 7971 self.raise_error("Expected }") 7972 7973 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 7974 if bracket_kind == TokenType.L_BRACE: 7975 this = self.expression( 7976 exp.Struct( 7977 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 7978 ) 7979 ) 7980 elif not this: 7981 this = build_array_constructor( 7982 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 7983 ) 7984 else: 7985 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 7986 if constructor_type: 7987 return build_array_constructor( 7988 constructor_type, 7989 args=expressions, 7990 bracket_kind=bracket_kind, 7991 dialect=self.dialect, 7992 ) 7993 7994 expressions = apply_index_offset( 7995 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 7996 ) 7997 this = self.expression( 7998 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 7999 ) 8000 8001 self._add_comments(this) 8002 return self._parse_bracket(this) 8003 8004 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8005 if not self._match(TokenType.COLON): 8006 return this 8007 8008 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8009 self._advance() 8010 end: exp.Expr | None = -exp.Literal.number("1") 8011 else: 8012 end = self._parse_assignment() 8013 step = self._parse_unary() if self._match(TokenType.COLON) else None 8014 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8015 8016 def _parse_case(self) -> exp.Expr | None: 8017 if self._match(TokenType.DOT, advance=False): 8018 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8019 self._retreat(self._index - 1) 8020 return None 8021 8022 ifs = [] 8023 default = None 8024 8025 comments = self._prev_comments 8026 expression = self._parse_disjunction() 8027 8028 while self._match(TokenType.WHEN): 8029 this = self._parse_disjunction() 8030 self._match(TokenType.THEN) 8031 then = self._parse_disjunction() 8032 ifs.append(self.expression(exp.If(this=this, true=then))) 8033 8034 if self._match(TokenType.ELSE): 8035 default = self._parse_disjunction() 8036 8037 if not self._match(TokenType.END): 8038 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8039 default = exp.column("interval") 8040 else: 8041 self.raise_error("Expected END after CASE", self._prev) 8042 8043 return self.expression( 8044 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8045 ) 8046 8047 def _parse_if(self) -> exp.Expr | None: 8048 if self._match(TokenType.L_PAREN): 8049 args = self._parse_csv( 8050 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8051 ) 8052 this = self.validate_expression(exp.If.from_arg_list(args), args) 8053 self._match_r_paren() 8054 else: 8055 index = self._index - 1 8056 8057 if self.NO_PAREN_IF_COMMANDS and index == 0: 8058 return self._parse_as_command(self._prev) 8059 8060 condition = self._parse_disjunction() 8061 8062 if not condition: 8063 self._retreat(index) 8064 return None 8065 8066 self._match(TokenType.THEN) 8067 true = self._parse_disjunction() 8068 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8069 self._match(TokenType.END) 8070 this = self.expression(exp.If(this=condition, true=true, false=false)) 8071 8072 return this 8073 8074 def _parse_next_value_for(self) -> exp.Expr | None: 8075 if not self._match_text_seq("VALUE", "FOR"): 8076 self._retreat(self._index - 1) 8077 return None 8078 8079 return self.expression( 8080 exp.NextValueFor( 8081 this=self._parse_column(), 8082 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8083 ) 8084 ) 8085 8086 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8087 this = self._parse_function() or self._parse_var_or_string(upper=True) 8088 8089 if self._match(TokenType.FROM): 8090 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8091 8092 if not self._match(TokenType.COMMA): 8093 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8094 8095 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8096 8097 def _parse_gap_fill(self) -> exp.GapFill: 8098 self._match(TokenType.TABLE) 8099 this = self._parse_table() 8100 8101 self._match(TokenType.COMMA) 8102 args = [this, *self._parse_csv(self._parse_lambda)] 8103 8104 gap_fill = exp.GapFill.from_arg_list(args) 8105 return self.validate_expression(gap_fill, args) 8106 8107 def _parse_char(self) -> exp.Chr: 8108 return self.expression( 8109 exp.Chr( 8110 expressions=self._parse_csv(self._parse_assignment), 8111 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8112 ) 8113 ) 8114 8115 def _parse_charset_name(self) -> exp.Expr | None: 8116 """ 8117 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8118 for specific name shapes override this. 8119 """ 8120 return self._parse_var( 8121 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8122 ) 8123 8124 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8125 this = self._parse_assignment() 8126 8127 if not self._match(TokenType.ALIAS): 8128 if self._match(TokenType.COMMA): 8129 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8130 8131 self.raise_error("Expected AS after CAST") 8132 8133 fmt = None 8134 to = self._parse_types(with_collation=True) 8135 8136 default = None 8137 if self._match(TokenType.DEFAULT): 8138 default = self._parse_bitwise() 8139 self._match_text_seq("ON", "CONVERSION", "ERROR") 8140 8141 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8142 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8143 fmt = self._parse_at_time_zone(fmt_string) 8144 8145 if not to: 8146 to = exp.DType.UNKNOWN.into_expr() 8147 if to.this in exp.DataType.TEMPORAL_TYPES: 8148 this = self.expression( 8149 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8150 this=this, 8151 format=exp.Literal.string( 8152 format_time( 8153 fmt_string.this if fmt_string else "", 8154 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8155 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8156 ) 8157 ), 8158 safe=safe, 8159 ) 8160 ) 8161 8162 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8163 this.set("zone", fmt.args["zone"]) 8164 return this 8165 elif not to: 8166 self.raise_error("Expected TYPE after CAST") 8167 elif isinstance(to, exp.Identifier): 8168 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8169 elif to.this == exp.DType.CHAR and ( 8170 self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET") 8171 ): 8172 to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string()) 8173 8174 return self.build_cast( 8175 strict=strict, 8176 this=this, 8177 to=to, 8178 format=fmt, 8179 safe=safe, 8180 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8181 default=default, 8182 ) 8183 8184 def _parse_string_agg(self) -> exp.GroupConcat: 8185 if self._match(TokenType.DISTINCT): 8186 args: list[exp.Expr | None] = [ 8187 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8188 ] 8189 if self._match(TokenType.COMMA): 8190 args.extend(self._parse_csv(self._parse_disjunction)) 8191 else: 8192 args = self._parse_csv(self._parse_disjunction) # type: ignore 8193 8194 if self._match_text_seq("ON", "OVERFLOW"): 8195 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8196 if self._match_text_seq("ERROR"): 8197 on_overflow: exp.Expr | None = exp.var("ERROR") 8198 else: 8199 self._match_text_seq("TRUNCATE") 8200 on_overflow = self.expression( 8201 exp.OverflowTruncateBehavior( 8202 this=self._parse_string(), 8203 with_count=( 8204 self._match_text_seq("WITH", "COUNT") 8205 or not self._match_text_seq("WITHOUT", "COUNT") 8206 ), 8207 ) 8208 ) 8209 else: 8210 on_overflow = None 8211 8212 index = self._index 8213 if not self._match(TokenType.R_PAREN) and args: 8214 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8215 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8216 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8217 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8218 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8219 8220 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8221 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8222 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8223 if not self._match_text_seq("WITHIN", "GROUP"): 8224 self._retreat(index) 8225 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8226 8227 # The corresponding match_r_paren will be called in parse_function (caller) 8228 self._match_l_paren() 8229 8230 return self.expression( 8231 exp.GroupConcat( 8232 this=self._parse_order(this=seq_get(args, 0)), 8233 separator=seq_get(args, 1), 8234 on_overflow=on_overflow, 8235 ) 8236 ) 8237 8238 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8239 this = self._parse_bitwise() 8240 8241 if self._match(TokenType.USING): 8242 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8243 elif self._match(TokenType.COMMA): 8244 to = self._parse_types() 8245 else: 8246 to = None 8247 8248 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8249 8250 def _parse_xml_element(self) -> exp.XMLElement: 8251 if self._match_text_seq("EVALNAME"): 8252 evalname = True 8253 this = self._parse_bitwise() 8254 else: 8255 evalname = None 8256 self._match_text_seq("NAME") 8257 this = self._parse_id_var() 8258 8259 return self.expression( 8260 exp.XMLElement( 8261 this=this, 8262 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8263 evalname=evalname, 8264 ) 8265 ) 8266 8267 def _parse_xml_table(self) -> exp.XMLTable: 8268 namespaces = None 8269 passing = None 8270 columns = None 8271 8272 if self._match_text_seq("XMLNAMESPACES", "("): 8273 namespaces = self._parse_xml_namespace() 8274 self._match_text_seq(")", ",") 8275 8276 this = self._parse_string() 8277 8278 if self._match_text_seq("PASSING"): 8279 # The BY VALUE keywords are optional and are provided for semantic clarity 8280 self._match_text_seq("BY", "VALUE") 8281 passing = self._parse_csv(self._parse_column) 8282 8283 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8284 8285 if self._match_text_seq("COLUMNS"): 8286 columns = self._parse_csv(self._parse_field_def) 8287 8288 return self.expression( 8289 exp.XMLTable( 8290 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8291 ) 8292 ) 8293 8294 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8295 namespaces = [] 8296 8297 while True: 8298 if self._match(TokenType.DEFAULT): 8299 uri = self._parse_string() 8300 else: 8301 uri = self._parse_alias(self._parse_string()) 8302 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8303 if not self._match(TokenType.COMMA): 8304 break 8305 8306 return namespaces 8307 8308 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8309 args = self._parse_csv(self._parse_disjunction) 8310 8311 if len(args) < 3: 8312 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8313 8314 return self.expression(exp.DecodeCase(expressions=args)) 8315 8316 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8317 self._match_text_seq("KEY") 8318 key = self._parse_column() 8319 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8320 self._match_text_seq("VALUE") 8321 value = self._parse_bitwise() 8322 8323 if not key and not value: 8324 return None 8325 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8326 8327 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8328 if not this or not self._match_text_seq("FORMAT", "JSON"): 8329 return this 8330 8331 return self.expression(exp.FormatJson(this=this)) 8332 8333 def _parse_on_condition(self) -> exp.OnCondition | None: 8334 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8335 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8336 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8337 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8338 else: 8339 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8340 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8341 8342 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8343 8344 if not empty and not error and not null: 8345 return None 8346 8347 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8348 8349 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8350 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8351 for value in values: 8352 if self._match_text_seq(value, "ON", on): 8353 return f"{value} ON {on}" 8354 8355 index = self._index 8356 if self._match(TokenType.DEFAULT): 8357 default_value = self._parse_bitwise() 8358 if self._match_text_seq("ON", on): 8359 return default_value 8360 8361 self._retreat(index) 8362 8363 return None 8364 8365 @t.overload 8366 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8367 8368 @t.overload 8369 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8370 8371 def _parse_json_object(self, agg=False): 8372 star = self._parse_star() 8373 expressions = ( 8374 [star] 8375 if star 8376 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8377 ) 8378 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8379 8380 unique_keys = None 8381 if self._match_text_seq("WITH", "UNIQUE"): 8382 unique_keys = True 8383 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8384 unique_keys = False 8385 8386 self._match_text_seq("KEYS") 8387 8388 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8389 self._parse_type() 8390 ) 8391 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8392 8393 return self.expression( 8394 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8395 expressions=expressions, 8396 null_handling=null_handling, 8397 unique_keys=unique_keys, 8398 return_type=return_type, 8399 encoding=encoding, 8400 ) 8401 ) 8402 8403 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8404 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8405 if not self._match_text_seq("NESTED"): 8406 this = self._parse_id_var() 8407 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8408 kind = self._parse_types(allow_identifiers=False) 8409 nested = None 8410 else: 8411 this = None 8412 ordinality = None 8413 kind = None 8414 nested = True 8415 8416 format_json = self._match_text_seq("FORMAT", "JSON") 8417 path = self._match_text_seq("PATH") and self._parse_string() 8418 nested_schema = nested and self._parse_json_schema() 8419 8420 return self.expression( 8421 exp.JSONColumnDef( 8422 this=this, 8423 kind=kind, 8424 path=path, 8425 nested_schema=nested_schema, 8426 ordinality=ordinality, 8427 format_json=format_json, 8428 ) 8429 ) 8430 8431 def _parse_json_schema(self) -> exp.JSONSchema: 8432 self._match_text_seq("COLUMNS") 8433 return self.expression( 8434 exp.JSONSchema( 8435 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8436 ) 8437 ) 8438 8439 def _parse_json_table(self) -> exp.JSONTable: 8440 this = self._parse_format_json(self._parse_bitwise()) 8441 path = self._match(TokenType.COMMA) and self._parse_string() 8442 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8443 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8444 schema = self._parse_json_schema() 8445 8446 return exp.JSONTable( 8447 this=this, 8448 schema=schema, 8449 path=path, 8450 error_handling=error_handling, 8451 empty_handling=empty_handling, 8452 ) 8453 8454 def _parse_match_against(self) -> exp.MatchAgainst: 8455 if self._match_text_seq("TABLE"): 8456 # parse SingleStore MATCH(TABLE ...) syntax 8457 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8458 expressions = [] 8459 table = self._parse_table() 8460 if table: 8461 expressions = [table] 8462 else: 8463 expressions = self._parse_csv(self._parse_column) 8464 8465 self._match_text_seq(")", "AGAINST", "(") 8466 8467 this = self._parse_string() 8468 8469 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8470 modifier = "IN NATURAL LANGUAGE MODE" 8471 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8472 modifier = f"{modifier} WITH QUERY EXPANSION" 8473 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8474 modifier = "IN BOOLEAN MODE" 8475 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8476 modifier = "WITH QUERY EXPANSION" 8477 else: 8478 modifier = None 8479 8480 return self.expression( 8481 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8482 ) 8483 8484 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8485 def _parse_open_json(self) -> exp.OpenJSON: 8486 this = self._parse_bitwise() 8487 path = self._match(TokenType.COMMA) and self._parse_string() 8488 8489 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8490 this = self._parse_field(any_token=True) 8491 kind = self._parse_types() 8492 path = self._parse_string() 8493 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8494 8495 return self.expression( 8496 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8497 ) 8498 8499 expressions = None 8500 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8501 self._match_l_paren() 8502 expressions = self._parse_csv(_parse_open_json_column_def) 8503 8504 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8505 8506 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8507 args = self._parse_csv(self._parse_bitwise) 8508 8509 if self._match(TokenType.IN): 8510 return self.expression( 8511 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8512 ) 8513 8514 if haystack_first: 8515 haystack = seq_get(args, 0) 8516 needle = seq_get(args, 1) 8517 else: 8518 haystack = seq_get(args, 1) 8519 needle = seq_get(args, 0) 8520 8521 return self.expression( 8522 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8523 ) 8524 8525 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8526 args = self._parse_csv(self._parse_table) 8527 return exp.JoinHint(this=func_name.upper(), expressions=args) 8528 8529 def _parse_substring(self) -> exp.Substring: 8530 # Postgres supports the form: substring(string [from int] [for int]) 8531 # (despite being undocumented, the reverse order also works) 8532 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8533 8534 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8535 8536 start, length = None, None 8537 8538 while self._curr: 8539 if self._match(TokenType.FROM): 8540 start = self._parse_bitwise() 8541 elif self._match(TokenType.FOR): 8542 if not start: 8543 start = exp.Literal.number(1) 8544 length = self._parse_bitwise() 8545 else: 8546 break 8547 8548 if start: 8549 args.append(start) 8550 if length: 8551 args.append(length) 8552 8553 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8554 8555 def _parse_trim(self) -> exp.Trim: 8556 # https://www.w3resource.com/sql/character-functions/trim.php 8557 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8558 8559 position = None 8560 collation = None 8561 expression = None 8562 8563 if self._match_texts(self.TRIM_TYPES): 8564 position = self._prev.text.upper() 8565 8566 this = self._parse_bitwise() 8567 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8568 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8569 expression = self._parse_bitwise() 8570 8571 if invert_order: 8572 this, expression = expression, this 8573 8574 if self._match(TokenType.COLLATE): 8575 collation = self._parse_bitwise() 8576 8577 return self.expression( 8578 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8579 ) 8580 8581 def _parse_window_clause(self) -> list[exp.Expr] | None: 8582 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8583 8584 def _parse_named_window(self) -> exp.Expr | None: 8585 return self._parse_window(self._parse_id_var(), alias=True) 8586 8587 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8588 if self._curr.token_type == TokenType.VAR: 8589 if self._match_text_seq("IGNORE", "NULLS"): 8590 return self.expression(exp.IgnoreNulls(this=this)) 8591 if self._match_text_seq("RESPECT", "NULLS"): 8592 return self.expression(exp.RespectNulls(this=this)) 8593 return this 8594 8595 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8596 if self._match(TokenType.HAVING): 8597 self._match_texts(("MAX", "MIN")) 8598 max = self._prev.text.upper() != "MIN" 8599 return self.expression( 8600 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8601 ) 8602 8603 return this 8604 8605 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8606 func = this 8607 comments = func.comments if isinstance(func, exp.Expr) else None 8608 8609 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 8610 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 8611 if self._match_text_seq("WITHIN", "GROUP"): 8612 order = self._parse_wrapped(self._parse_order) 8613 this = self.expression(exp.WithinGroup(this=this, expression=order)) 8614 8615 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 8616 self._match(TokenType.WHERE) 8617 this = self.expression( 8618 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 8619 ) 8620 self._match_r_paren() 8621 8622 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 8623 # Some dialects choose to implement and some do not. 8624 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 8625 8626 # There is some code above in _parse_lambda that handles 8627 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 8628 8629 # The below changes handle 8630 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 8631 8632 # Oracle allows both formats 8633 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 8634 # and Snowflake chose to do the same for familiarity 8635 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 8636 if isinstance(this, exp.AggFunc): 8637 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 8638 8639 if ignore_respect and ignore_respect is not this: 8640 ignore_respect.replace(ignore_respect.this) 8641 this = self.expression(ignore_respect.__class__(this=this)) 8642 8643 this = self._parse_respect_or_ignore_nulls(this) 8644 8645 # bigquery select from window x AS (partition by ...) 8646 if alias: 8647 over = None 8648 self._match(TokenType.ALIAS) 8649 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 8650 return this 8651 else: 8652 over = self._prev.text.upper() 8653 8654 if comments and isinstance(func, exp.Expr): 8655 func.pop_comments() 8656 8657 if not self._match(TokenType.L_PAREN): 8658 return self.expression( 8659 exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments 8660 ) 8661 8662 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 8663 8664 first: bool | None = True if self._match(TokenType.FIRST) else None 8665 if self._match_text_seq("LAST"): 8666 first = False 8667 8668 partition, order = self._parse_partition_and_order() 8669 kind = ( 8670 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 8671 ) and self._prev.text 8672 8673 if kind: 8674 self._match(TokenType.BETWEEN) 8675 start = self._parse_window_spec() 8676 8677 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 8678 exclude = ( 8679 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 8680 if self._match_text_seq("EXCLUDE") 8681 else None 8682 ) 8683 8684 spec = self.expression( 8685 exp.WindowSpec( 8686 kind=kind, 8687 start=start["value"], 8688 start_side=start["side"], 8689 end=end.get("value"), 8690 end_side=end.get("side"), 8691 exclude=exclude, 8692 ) 8693 ) 8694 else: 8695 spec = None 8696 8697 self._match_r_paren() 8698 8699 window = self.expression( 8700 exp.Window( 8701 this=this, 8702 partition_by=partition, 8703 order=order, 8704 spec=spec, 8705 alias=window_alias, 8706 over=over, 8707 first=first, 8708 ), 8709 comments=comments, 8710 ) 8711 8712 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 8713 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 8714 return self._parse_window(window, alias=alias) 8715 8716 return window 8717 8718 def _parse_partition_and_order( 8719 self, 8720 ) -> tuple[list[exp.Expr], exp.Expr | None]: 8721 return self._parse_partition_by(), self._parse_order() 8722 8723 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 8724 self._match(TokenType.BETWEEN) 8725 8726 return { 8727 "value": ( 8728 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 8729 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 8730 or self._parse_bitwise() 8731 ), 8732 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 8733 } 8734 8735 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 8736 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 8737 # so this section tries to parse the clause version and if it fails, it treats the token 8738 # as an identifier (alias) 8739 if self._can_parse_limit_or_offset(): 8740 return this 8741 8742 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 8743 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 8744 if self._can_parse_named_window(): 8745 return this 8746 8747 any_token = self._match(TokenType.ALIAS) 8748 comments = self._prev_comments 8749 8750 if explicit and not any_token: 8751 return this 8752 8753 if self._match(TokenType.L_PAREN): 8754 aliases = self.expression( 8755 exp.Aliases( 8756 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 8757 ), 8758 comments=comments, 8759 ) 8760 self._match_r_paren(aliases) 8761 return aliases 8762 8763 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 8764 self.STRING_ALIASES and self._parse_string_as_identifier() 8765 ) 8766 8767 if alias: 8768 comments.extend(alias.pop_comments()) 8769 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 8770 column = this.this 8771 8772 # Moves the comment next to the alias in `expr /* comment */ AS alias` 8773 if not this.comments and column and column.comments: 8774 this.comments = column.pop_comments() 8775 8776 return this 8777 8778 def _parse_id_var( 8779 self, 8780 any_token: bool = True, 8781 tokens: t.Collection[TokenType] | None = None, 8782 ) -> exp.Expr | None: 8783 expression = self._parse_identifier() 8784 if not expression and ( 8785 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 8786 ): 8787 quoted = self._prev.token_type == TokenType.STRING 8788 expression = self._identifier_expression(quoted=quoted) 8789 8790 return expression 8791 8792 def _parse_string(self) -> exp.Expr | None: 8793 if self._match_set(self.STRING_PARSERS): 8794 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 8795 return self._parse_placeholder() 8796 8797 def _parse_string_as_identifier(self) -> exp.Identifier | None: 8798 if not self._match(TokenType.STRING): 8799 return None 8800 output = exp.to_identifier(self._prev.text, quoted=True) 8801 output.update_positions(self._prev) 8802 return output 8803 8804 def _parse_number(self) -> exp.Expr | None: 8805 if self._match_set(self.NUMERIC_PARSERS): 8806 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 8807 return self._parse_placeholder() 8808 8809 def _parse_identifier(self) -> exp.Expr | None: 8810 if self._match(TokenType.IDENTIFIER): 8811 return self._identifier_expression(quoted=True) 8812 return self._parse_placeholder() 8813 8814 def _parse_var( 8815 self, 8816 any_token: bool = False, 8817 tokens: t.Collection[TokenType] | None = None, 8818 upper: bool = False, 8819 ) -> exp.Expr | None: 8820 if ( 8821 (any_token and self._advance_any()) 8822 or self._match(TokenType.VAR) 8823 or (self._match_set(tokens) if tokens else False) 8824 ): 8825 return self.expression( 8826 exp.Var(this=self._prev.text.upper() if upper else self._prev.text) 8827 ) 8828 return self._parse_placeholder() 8829 8830 def _advance_any(self, ignore_reserved: bool = False) -> Token | None: 8831 if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS): 8832 self._advance() 8833 return self._prev 8834 return None 8835 8836 def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None: 8837 return self._parse_string() or self._parse_var(any_token=True, upper=upper) 8838 8839 def _parse_primary_or_var(self) -> exp.Expr | None: 8840 return self._parse_primary() or self._parse_var(any_token=True) 8841 8842 def _parse_null(self) -> exp.Expr | None: 8843 if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): 8844 return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) 8845 return self._parse_placeholder() 8846 8847 def _parse_boolean(self) -> exp.Expr | None: 8848 if self._match(TokenType.TRUE): 8849 return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) 8850 if self._match(TokenType.FALSE): 8851 return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) 8852 return self._parse_placeholder() 8853 8854 def _parse_star(self) -> exp.Expr | None: 8855 if self._match(TokenType.STAR): 8856 return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) 8857 return self._parse_placeholder() 8858 8859 def _parse_parameter(self) -> exp.Parameter: 8860 this = self._parse_identifier() or self._parse_primary_or_var() 8861 return self.expression(exp.Parameter(this=this)) 8862 8863 def _parse_placeholder(self) -> exp.Expr | None: 8864 if self._match_set(self.PLACEHOLDER_PARSERS): 8865 placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) 8866 if placeholder: 8867 return placeholder 8868 self._advance(-1) 8869 return None 8870 8871 def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None: 8872 if not self._match_texts(keywords): 8873 return None 8874 if self._match(TokenType.L_PAREN, advance=False): 8875 return self._parse_wrapped_csv(self._parse_expression) 8876 8877 expression = self._parse_alias(self._parse_disjunction(), explicit=True) 8878 return [expression] if expression else None 8879 8880 def _parse_csv( 8881 self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA 8882 ) -> list[T]: 8883 parse_result = parse_method() 8884 items = [parse_result] if parse_result is not None else [] 8885 8886 while self._match(sep): 8887 if isinstance(parse_result, exp.Expr): 8888 self._add_comments(parse_result) 8889 parse_result = parse_method() 8890 if parse_result is not None: 8891 items.append(parse_result) 8892 8893 return items 8894 8895 def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]: 8896 return self._parse_wrapped_csv(self._parse_id_var, optional=optional) 8897 8898 def _parse_wrapped_csv( 8899 self, 8900 parse_method: t.Callable[[], T | None], 8901 sep: TokenType = TokenType.COMMA, 8902 optional: bool = False, 8903 ) -> list[T]: 8904 return self._parse_wrapped( 8905 lambda: self._parse_csv(parse_method, sep=sep), optional=optional 8906 ) 8907 8908 def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T: 8909 wrapped = self._match(TokenType.L_PAREN) 8910 if not wrapped and not optional: 8911 self.raise_error("Expecting (") 8912 parse_result = parse_method() 8913 if wrapped: 8914 self._match_r_paren() 8915 return parse_result 8916 8917 def _parse_expressions(self) -> list[exp.Expr]: 8918 return self._parse_csv(self._parse_expression) 8919 8920 def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None: 8921 return ( 8922 self._parse_set_operations( 8923 self._parse_alias(self._parse_assignment(), explicit=True) 8924 if alias 8925 else self._parse_assignment() 8926 ) 8927 or self._parse_select() 8928 ) 8929 8930 def _parse_ddl_select(self) -> exp.Expr | None: 8931 return self._parse_query_modifiers( 8932 self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False)) 8933 ) 8934 8935 def _parse_transaction(self) -> exp.Transaction | exp.Command: 8936 this = None 8937 if self._match_texts(self.TRANSACTION_KIND): 8938 this = self._prev.text 8939 8940 self._match_texts(("TRANSACTION", "WORK")) 8941 8942 modes = [] 8943 while True: 8944 mode = [] 8945 while self._match(TokenType.VAR) or self._match(TokenType.NOT): 8946 mode.append(self._prev.text) 8947 8948 if mode: 8949 modes.append(" ".join(mode)) 8950 if not self._match(TokenType.COMMA): 8951 break 8952 8953 return self.expression(exp.Transaction(this=this, modes=modes)) 8954 8955 def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: 8956 chain = None 8957 savepoint = None 8958 is_rollback = self._prev.token_type == TokenType.ROLLBACK 8959 8960 self._match_texts(("TRANSACTION", "WORK")) 8961 8962 if self._match_text_seq("TO"): 8963 self._match_text_seq("SAVEPOINT") 8964 savepoint = self._parse_id_var() 8965 8966 if self._match(TokenType.AND): 8967 chain = not self._match_text_seq("NO") 8968 self._match_text_seq("CHAIN") 8969 8970 if is_rollback: 8971 return self.expression(exp.Rollback(savepoint=savepoint)) 8972 8973 return self.expression(exp.Commit(chain=chain)) 8974 8975 def _parse_refresh(self) -> exp.Refresh | exp.Command: 8976 if self._match_text_seq("EXTERNAL", "TABLE"): 8977 kind = "EXTERNAL TABLE" 8978 elif self._match(TokenType.TABLE): 8979 kind = "TABLE" 8980 elif self._match_text_seq("MATERIALIZED", "VIEW"): 8981 kind = "MATERIALIZED VIEW" 8982 else: 8983 kind = "" 8984 8985 this = self._parse_string() or self._parse_table() 8986 if not kind and not isinstance(this, exp.Literal): 8987 return self._parse_as_command(self._prev) 8988 8989 return self.expression(exp.Refresh(this=this, kind=kind)) 8990 8991 def _parse_column_def_with_exists(self): 8992 start = self._index 8993 self._match(TokenType.COLUMN) 8994 8995 exists_column = self._parse_exists(not_=True) 8996 expression = self._parse_field_def() 8997 8998 if not isinstance(expression, exp.ColumnDef): 8999 self._retreat(start) 9000 return None 9001 9002 expression.set("exists", exists_column) 9003 9004 return expression 9005 9006 def _parse_add_column(self) -> exp.ColumnDef | None: 9007 if not self._prev.text.upper() == "ADD": 9008 return None 9009 9010 return self._parse_column_def_with_exists() 9011 9012 def _parse_drop_column(self) -> exp.Drop | exp.Command | None: 9013 drop = self._parse_drop() if self._match(TokenType.DROP) else None 9014 if drop and not isinstance(drop, exp.Command): 9015 drop.set("kind", drop.args.get("kind", "COLUMN")) 9016 return drop 9017 9018 def _parse_alter_drop_action(self) -> exp.Expr | None: 9019 return self._parse_drop_column() 9020 9021 # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html 9022 def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition: 9023 return self.expression( 9024 exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists) 9025 ) 9026 9027 def _parse_alter_table_add(self) -> list[exp.Expr]: 9028 def _parse_add_alteration() -> exp.Expr | None: 9029 self._match_text_seq("ADD") 9030 if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False): 9031 return self.expression( 9032 exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint)) 9033 ) 9034 9035 column_def = self._parse_add_column() 9036 if isinstance(column_def, exp.ColumnDef): 9037 return column_def 9038 9039 exists = self._parse_exists(not_=True) 9040 if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): 9041 return self.expression( 9042 exp.AddPartition( 9043 exists=exists, 9044 this=self._parse_field(any_token=True), 9045 location=self._match_text_seq("LOCATION", advance=False) 9046 and self._parse_property(), 9047 ) 9048 ) 9049 9050 return None 9051 9052 if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( 9053 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 9054 or self._match_text_seq("COLUMNS") 9055 ): 9056 schema = self._parse_schema() 9057 9058 return ( 9059 ensure_list(schema) 9060 if schema 9061 else self._parse_csv(self._parse_column_def_with_exists) 9062 ) 9063 9064 return self._parse_csv(_parse_add_alteration) 9065 9066 def _parse_alter_table_alter(self) -> exp.Expr | None: 9067 if self._match_texts(self.ALTER_ALTER_PARSERS): 9068 return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) 9069 9070 # Many dialects support the ALTER [COLUMN] syntax, so if there is no 9071 # keyword after ALTER we default to parsing this statement 9072 self._match(TokenType.COLUMN) 9073 exists = self._parse_exists() 9074 column = self._parse_field(any_token=True) 9075 9076 if self._match_pair(TokenType.DROP, TokenType.DEFAULT): 9077 return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None)) 9078 if self._match_pair(TokenType.SET, TokenType.DEFAULT): 9079 return self.expression( 9080 exp.AlterColumn( 9081 this=column, default=self._parse_disjunction(), exists=exists or None 9082 ) 9083 ) 9084 if self._match(TokenType.COMMENT): 9085 return self.expression( 9086 exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None) 9087 ) 9088 if self._match_text_seq("DROP", "NOT", "NULL"): 9089 return self.expression( 9090 exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None) 9091 ) 9092 if self._match_text_seq("SET", "NOT", "NULL"): 9093 return self.expression( 9094 exp.AlterColumn(this=column, allow_null=False, exists=exists or None) 9095 ) 9096 9097 if self._match_text_seq("SET", "VISIBLE"): 9098 return self.expression( 9099 exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None) 9100 ) 9101 if self._match_text_seq("SET", "INVISIBLE"): 9102 return self.expression( 9103 exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None) 9104 ) 9105 9106 self._match_text_seq("SET", "DATA") 9107 self._match_text_seq("TYPE") 9108 return self.expression( 9109 exp.AlterColumn( 9110 this=column, 9111 dtype=self._parse_types(), 9112 collate=self._match(TokenType.COLLATE) and self._parse_term(), 9113 using=self._match(TokenType.USING) and self._parse_disjunction(), 9114 exists=exists or None, 9115 ) 9116 ) 9117 9118 def _parse_alter_diststyle(self) -> exp.AlterDistStyle: 9119 if self._match_texts(("ALL", "EVEN", "AUTO")): 9120 return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper()))) 9121 9122 self._match_text_seq("KEY", "DISTKEY") 9123 return self.expression(exp.AlterDistStyle(this=self._parse_column())) 9124 9125 def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey: 9126 if compound: 9127 self._match_text_seq("SORTKEY") 9128 9129 if self._match(TokenType.L_PAREN, advance=False): 9130 return self.expression( 9131 exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound) 9132 ) 9133 9134 self._match_texts(("AUTO", "NONE")) 9135 return self.expression( 9136 exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound) 9137 ) 9138 9139 def _parse_alter_table_drop(self) -> list[exp.Expr]: 9140 index = self._index - 1 9141 9142 partition_exists = self._parse_exists() 9143 if self._match(TokenType.PARTITION, advance=False): 9144 return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists)) 9145 9146 self._retreat(index) 9147 return self._parse_csv(self._parse_alter_drop_action) 9148 9149 def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None: 9150 if self._match(TokenType.COLUMN) or ( 9151 not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False) 9152 ): 9153 exists = self._parse_exists() 9154 old_column = self._parse_column() 9155 to = self._match_text_seq("TO") 9156 new_column = self._parse_column() 9157 9158 if old_column is None or not to or new_column is None: 9159 return None 9160 9161 return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists)) 9162 9163 self._match_text_seq("TO") 9164 return self.expression(exp.AlterRename(this=self._parse_table(schema=True))) 9165 9166 def _parse_alter_table_set(self) -> exp.AlterSet: 9167 alter_set = self.expression(exp.AlterSet()) 9168 9169 if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( 9170 "TABLE", "PROPERTIES" 9171 ): 9172 alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment)) 9173 elif self._match_text_seq("FILESTREAM_ON", advance=False): 9174 alter_set.set("expressions", [self._parse_assignment()]) 9175 elif self._match_texts(("LOGGED", "UNLOGGED")): 9176 alter_set.set("option", exp.var(self._prev.text.upper())) 9177 elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): 9178 alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) 9179 elif self._match_text_seq("LOCATION"): 9180 alter_set.set("location", self._parse_field()) 9181 elif self._match_text_seq("ACCESS", "METHOD"): 9182 alter_set.set("access_method", self._parse_field()) 9183 elif self._match_text_seq("TABLESPACE"): 9184 alter_set.set("tablespace", self._parse_field()) 9185 elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"): 9186 alter_set.set("file_format", [self._parse_field()]) 9187 elif self._match_text_seq("STAGE_FILE_FORMAT"): 9188 alter_set.set("file_format", self._parse_wrapped_options()) 9189 elif self._match_text_seq("STAGE_COPY_OPTIONS"): 9190 alter_set.set("copy_options", self._parse_wrapped_options()) 9191 elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): 9192 alter_set.set("tag", self._parse_csv(self._parse_assignment)) 9193 else: 9194 if self._match_text_seq("SERDE"): 9195 alter_set.set("serde", self._parse_field()) 9196 9197 properties = self._parse_wrapped(self._parse_properties, optional=True) 9198 alter_set.set("expressions", [properties]) 9199 9200 return alter_set 9201 9202 def _parse_alter_session(self) -> exp.AlterSession: 9203 """Parse ALTER SESSION SET/UNSET statements.""" 9204 if self._match(TokenType.SET): 9205 expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) 9206 return self.expression(exp.AlterSession(expressions=expressions, unset=False)) 9207 9208 self._match_text_seq("UNSET") 9209 expressions = self._parse_csv( 9210 lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True))) 9211 ) 9212 return self.expression(exp.AlterSession(expressions=expressions, unset=True)) 9213 9214 def _parse_alter(self) -> exp.Alter | exp.Command: 9215 start = self._prev 9216 9217 iceberg = self._match_text_seq("ICEBERG") 9218 9219 alter_token = self._match_set(self.ALTERABLES) and self._prev 9220 if not alter_token: 9221 return self._parse_as_command(start) 9222 if iceberg and alter_token.token_type != TokenType.TABLE: 9223 return self._parse_as_command(start) 9224 9225 exists = self._parse_exists() 9226 only = self._match_text_seq("ONLY") 9227 9228 if alter_token.token_type == TokenType.SESSION: 9229 this = None 9230 check = None 9231 cluster = None 9232 else: 9233 this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS) 9234 check = self._match_text_seq("WITH", "CHECK") 9235 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9236 9237 if self._next: 9238 self._advance() 9239 9240 parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None 9241 if parser: 9242 actions = ensure_list(parser(self)) 9243 not_valid = self._match_text_seq("NOT", "VALID") 9244 options = self._parse_csv(self._parse_property) 9245 cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE") 9246 9247 if not self._curr and actions: 9248 return self.expression( 9249 exp.Alter( 9250 this=this, 9251 kind=alter_token.text.upper(), 9252 exists=exists, 9253 actions=actions, 9254 only=only, 9255 options=options, 9256 cluster=cluster, 9257 not_valid=not_valid, 9258 check=check, 9259 cascade=cascade, 9260 iceberg=iceberg, 9261 ) 9262 ) 9263 9264 return self._parse_as_command(start) 9265 9266 def _parse_analyze(self) -> exp.Analyze | exp.Command: 9267 start = self._prev 9268 # https://duckdb.org/docs/sql/statements/analyze 9269 if not self._curr: 9270 return self.expression(exp.Analyze()) 9271 9272 options = [] 9273 while self._match_texts(self.ANALYZE_STYLES): 9274 if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": 9275 options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") 9276 else: 9277 options.append(self._prev.text.upper()) 9278 9279 this: exp.Expr | None = None 9280 inner_expression: exp.Expr | None = None 9281 9282 kind = self._curr.text.upper() if self._curr else None 9283 9284 if self._match(TokenType.TABLE) or self._match(TokenType.INDEX): 9285 this = self._parse_table_parts() 9286 elif self._match_text_seq("TABLES"): 9287 if self._match_set((TokenType.FROM, TokenType.IN)): 9288 kind = f"{kind} {self._prev.text.upper()}" 9289 this = self._parse_table(schema=True, is_db_reference=True) 9290 elif self._match_text_seq("DATABASE"): 9291 this = self._parse_table(schema=True, is_db_reference=True) 9292 elif self._match_text_seq("CLUSTER"): 9293 this = self._parse_table() 9294 # Try matching inner expr keywords before fallback to parse table. 9295 elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9296 kind = None 9297 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9298 else: 9299 # Empty kind https://prestodb.io/docs/current/sql/analyze.html 9300 kind = None 9301 this = self._parse_table_parts() 9302 9303 partition = self._try_parse(self._parse_partition) 9304 if not partition and self._match_texts(self.PARTITION_KEYWORDS): 9305 return self._parse_as_command(start) 9306 9307 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9308 if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( 9309 "WITH", "ASYNC", "MODE" 9310 ): 9311 mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" 9312 else: 9313 mode = None 9314 9315 if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9316 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9317 9318 properties = self._parse_properties() 9319 return self.expression( 9320 exp.Analyze( 9321 kind=kind, 9322 this=this, 9323 mode=mode, 9324 partition=partition, 9325 properties=properties, 9326 expression=inner_expression, 9327 options=options, 9328 ) 9329 ) 9330 9331 # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html 9332 def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: 9333 this = None 9334 kind = self._prev.text.upper() 9335 option = self._prev.text.upper() if self._match_text_seq("DELTA") else None 9336 expressions = [] 9337 9338 if not self._match_text_seq("STATISTICS"): 9339 self.raise_error("Expecting token STATISTICS") 9340 9341 if self._match_text_seq("NOSCAN"): 9342 this = "NOSCAN" 9343 elif self._match(TokenType.FOR): 9344 if self._match_text_seq("ALL", "COLUMNS"): 9345 this = "FOR ALL COLUMNS" 9346 if self._match_text_seq("COLUMNS"): 9347 this = "FOR COLUMNS" 9348 expressions = self._parse_csv(self._parse_column_reference) 9349 elif self._match_text_seq("SAMPLE"): 9350 sample = self._parse_number() 9351 expressions = [ 9352 self.expression( 9353 exp.AnalyzeSample( 9354 sample=sample, 9355 kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None, 9356 ) 9357 ) 9358 ] 9359 9360 return self.expression( 9361 exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions) 9362 ) 9363 9364 # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html 9365 def _parse_analyze_validate(self) -> exp.AnalyzeValidate: 9366 kind = None 9367 this = None 9368 expression: exp.Expr | None = None 9369 if self._match_text_seq("REF", "UPDATE"): 9370 kind = "REF" 9371 this = "UPDATE" 9372 if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): 9373 this = "UPDATE SET DANGLING TO NULL" 9374 elif self._match_text_seq("STRUCTURE"): 9375 kind = "STRUCTURE" 9376 if self._match_text_seq("CASCADE", "FAST"): 9377 this = "CASCADE FAST" 9378 elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( 9379 ("ONLINE", "OFFLINE") 9380 ): 9381 this = f"CASCADE COMPLETE {self._prev.text.upper()}" 9382 expression = self._parse_into() 9383 9384 return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression)) 9385 9386 def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None: 9387 this = self._prev.text.upper() 9388 if self._match_text_seq("COLUMNS"): 9389 return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}")) 9390 return None 9391 9392 def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None: 9393 kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None 9394 if self._match_text_seq("STATISTICS"): 9395 return self.expression(exp.AnalyzeDelete(kind=kind)) 9396 return None 9397 9398 def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None: 9399 if self._match_text_seq("CHAINED", "ROWS"): 9400 return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into())) 9401 return None 9402 9403 # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html 9404 def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: 9405 this = self._prev.text.upper() 9406 expression: exp.Expr | None = None 9407 expressions = [] 9408 update_options = None 9409 9410 if self._match_text_seq("HISTOGRAM", "ON"): 9411 expressions = self._parse_csv(self._parse_column_reference) 9412 with_expressions = [] 9413 while self._match(TokenType.WITH): 9414 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9415 if self._match_texts(("SYNC", "ASYNC")): 9416 if self._match_text_seq("MODE", advance=False): 9417 with_expressions.append(f"{self._prev.text.upper()} MODE") 9418 self._advance() 9419 else: 9420 buckets = self._parse_number() 9421 if self._match_text_seq("BUCKETS"): 9422 with_expressions.append(f"{buckets} BUCKETS") 9423 if with_expressions: 9424 expression = self.expression(exp.AnalyzeWith(expressions=with_expressions)) 9425 9426 if self._match_texts(("MANUAL", "AUTO")) and self._match( 9427 TokenType.UPDATE, advance=False 9428 ): 9429 update_options = self._prev.text.upper() 9430 self._advance() 9431 elif self._match_text_seq("USING", "DATA"): 9432 expression = self.expression(exp.UsingData(this=self._parse_string())) 9433 9434 return self.expression( 9435 exp.AnalyzeHistogram( 9436 this=this, 9437 expressions=expressions, 9438 expression=expression, 9439 update_options=update_options, 9440 ) 9441 ) 9442 9443 def _parse_merge(self) -> exp.Merge: 9444 self._match(TokenType.INTO) 9445 target = self._parse_table() 9446 9447 if target and self._match(TokenType.ALIAS, advance=False): 9448 target.set("alias", self._parse_table_alias()) 9449 9450 self._match(TokenType.USING) 9451 using = self._parse_table() 9452 9453 return self.expression( 9454 exp.Merge( 9455 this=target, 9456 using=using, 9457 on=self._match(TokenType.ON) and self._parse_disjunction(), 9458 using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), 9459 whens=self._parse_when_matched(), 9460 returning=self._parse_returning(), 9461 ) 9462 ) 9463 9464 def _parse_when_matched(self) -> exp.Whens: 9465 whens = [] 9466 9467 while self._match(TokenType.WHEN): 9468 matched = not self._match(TokenType.NOT) 9469 self._match_text_seq("MATCHED") 9470 source = ( 9471 False 9472 if self._match_text_seq("BY", "TARGET") 9473 else self._match_text_seq("BY", "SOURCE") 9474 ) 9475 condition = self._parse_disjunction() if self._match(TokenType.AND) else None 9476 9477 self._match(TokenType.THEN) 9478 9479 if self._match(TokenType.INSERT): 9480 this = self._parse_star() 9481 if this: 9482 then: exp.Expr | None = self.expression(exp.Insert(this=this)) 9483 else: 9484 then = self.expression( 9485 exp.Insert( 9486 this=exp.var("ROW") 9487 if self._match_text_seq("ROW") 9488 else self._parse_value(values=False), 9489 expression=self._match_text_seq("VALUES") and self._parse_value(), 9490 where=self._parse_where(), 9491 ) 9492 ) 9493 elif self._match(TokenType.UPDATE): 9494 expressions = self._parse_star() 9495 if expressions: 9496 then = self.expression(exp.Update(expressions=expressions)) 9497 else: 9498 then = self.expression( 9499 exp.Update( 9500 expressions=self._match(TokenType.SET) 9501 and self._parse_csv(self._parse_equality), 9502 where=self._parse_where(), 9503 ) 9504 ) 9505 elif self._match(TokenType.DELETE): 9506 then = self.expression(exp.Var(this=self._prev.text)) 9507 else: 9508 then = self._parse_var_from_options(self.CONFLICT_ACTIONS) 9509 9510 whens.append( 9511 self.expression( 9512 exp.When(matched=matched, source=source, condition=condition, then=then) 9513 ) 9514 ) 9515 return self.expression(exp.Whens(expressions=whens)) 9516 9517 def _parse_show(self) -> exp.Expr | None: 9518 parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) 9519 if parser: 9520 return parser(self) 9521 return self._parse_as_command(self._prev) 9522 9523 def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None: 9524 index = self._index 9525 9526 if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): 9527 return self._parse_set_transaction(global_=kind == "GLOBAL") 9528 9529 left = self._parse_primary() or self._parse_column() 9530 assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) 9531 9532 if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter): 9533 self._retreat(index) 9534 return None 9535 9536 right = self._parse_statement() or self._parse_id_var() 9537 if isinstance(right, (exp.Column, exp.Identifier)): 9538 right = exp.var(right.name) 9539 9540 this = self.expression(exp.EQ(this=left, expression=right)) 9541 return self.expression(exp.SetItem(this=this, kind=kind)) 9542 9543 def _parse_set_transaction(self, global_: bool = False) -> exp.Expr: 9544 self._match_text_seq("TRANSACTION") 9545 characteristics = self._parse_csv( 9546 lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) 9547 ) 9548 return self.expression( 9549 exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_) 9550 ) 9551 9552 def _parse_set_item(self) -> exp.Expr | None: 9553 parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) 9554 return parser(self) if parser else self._parse_set_item_assignment(kind=None) 9555 9556 def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command: 9557 index = self._index 9558 set_ = self.expression( 9559 exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag) 9560 ) 9561 9562 if self._curr: 9563 self._retreat(index) 9564 return self._parse_as_command(self._prev) 9565 9566 return set_ 9567 9568 def _parse_var_from_options( 9569 self, options: OPTIONS_TYPE, raise_unmatched: bool = True 9570 ) -> exp.Var | None: 9571 start = self._curr 9572 if not start: 9573 return None 9574 9575 option = start.text.upper() 9576 continuations = ( 9577 None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option) 9578 ) 9579 9580 index = self._index 9581 self._advance() 9582 for keywords in continuations or []: 9583 if isinstance(keywords, str): 9584 keywords = (keywords,) 9585 9586 if self._match_text_seq(*keywords): 9587 option = f"{option} {' '.join(keywords)}" 9588 break 9589 else: 9590 if continuations or continuations is None: 9591 if raise_unmatched: 9592 self.raise_error(f"Unknown option {option}") 9593 9594 self._retreat(index) 9595 return None 9596 9597 return exp.var(option) 9598 9599 def _parse_as_command(self, start: Token) -> exp.Command: 9600 while self._curr: 9601 self._advance() 9602 text = self._find_sql(start, self._prev) 9603 size = len(start.text) 9604 self._warn_unsupported() 9605 return exp.Command(this=text[:size], expression=text[size:]) 9606 9607 def _parse_dict_property(self, this: str) -> exp.DictProperty: 9608 settings = [] 9609 9610 self._match_l_paren() 9611 kind = self._parse_id_var() 9612 9613 if self._match(TokenType.L_PAREN): 9614 while True: 9615 key = self._parse_id_var() 9616 value = self._parse_function() or self._parse_primary_or_var() 9617 if not key and value is None: 9618 break 9619 settings.append(self.expression(exp.DictSubProperty(this=key, value=value))) 9620 self._match(TokenType.R_PAREN) 9621 9622 self._match_r_paren() 9623 9624 return self.expression( 9625 exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings) 9626 ) 9627 9628 def _parse_dict_range(self, this: str) -> exp.DictRange: 9629 self._match_l_paren() 9630 has_min = self._match_text_seq("MIN") 9631 if has_min: 9632 min = self._parse_var() or self._parse_primary() 9633 self._match_text_seq("MAX") 9634 max = self._parse_var() or self._parse_primary() 9635 else: 9636 max = self._parse_var() or self._parse_primary() 9637 min = exp.Literal.number(0) 9638 self._match_r_paren() 9639 return self.expression(exp.DictRange(this=this, min=min, max=max)) 9640 9641 def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None: 9642 index = self._index 9643 expression = self._parse_column() 9644 position = self._match(TokenType.COMMA) and self._parse_column() 9645 9646 if not self._match(TokenType.IN): 9647 self._retreat(index - 1) 9648 return None 9649 iterator = self._parse_column() 9650 condition = self._parse_disjunction() if self._match_text_seq("IF") else None 9651 return self.expression( 9652 exp.Comprehension( 9653 this=this, 9654 expression=expression, 9655 position=position, 9656 iterator=iterator, 9657 condition=condition, 9658 ) 9659 ) 9660 9661 def _parse_heredoc(self) -> exp.Heredoc | None: 9662 if self._match(TokenType.HEREDOC_STRING): 9663 return self.expression(exp.Heredoc(this=self._prev.text)) 9664 9665 if not self._match_text_seq("$"): 9666 return None 9667 9668 tags = ["$"] 9669 tag_text = None 9670 9671 if self._is_connected(): 9672 self._advance() 9673 tags.append(self._prev.text.upper()) 9674 else: 9675 self.raise_error("No closing $ found") 9676 9677 if tags[-1] != "$": 9678 if self._is_connected() and self._match_text_seq("$"): 9679 tag_text = tags[-1] 9680 tags.append("$") 9681 else: 9682 self.raise_error("No closing $ found") 9683 9684 heredoc_start = self._curr 9685 9686 while self._curr: 9687 if self._match_text_seq(*tags, advance=False): 9688 this = self._find_sql(heredoc_start, self._prev) 9689 self._advance(len(tags)) 9690 return self.expression(exp.Heredoc(this=this, tag=tag_text)) 9691 9692 self._advance() 9693 9694 self.raise_error(f"No closing {''.join(tags)} found") 9695 return None 9696 9697 def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None: 9698 if not self._curr: 9699 return None 9700 9701 index = self._index 9702 this = [] 9703 while True: 9704 # The current token might be multiple words 9705 curr = self._curr.text.upper() 9706 key = curr.split(" ") 9707 this.append(curr) 9708 9709 self._advance() 9710 result, trie = in_trie(trie, key) 9711 if result == TrieResult.FAILED: 9712 break 9713 9714 if result == TrieResult.EXISTS: 9715 subparser = parsers[" ".join(this)] 9716 return subparser 9717 9718 self._retreat(index) 9719 return None 9720 9721 def _match_l_paren(self, expression: exp.Expr | None = None) -> None: 9722 if not self._match(TokenType.L_PAREN, expression=expression): 9723 self.raise_error("Expecting (") 9724 9725 def _match_r_paren(self, expression: exp.Expr | None = None) -> None: 9726 if not self._match(TokenType.R_PAREN, expression=expression): 9727 self.raise_error("Expecting )") 9728 9729 def _replace_lambda( 9730 self, node: exp.Expr | None, expressions: list[exp.Expr] 9731 ) -> exp.Expr | None: 9732 if not node: 9733 return node 9734 9735 lambda_types = {e.name: e.args.get("to") or False for e in expressions} 9736 9737 for column in node.find_all(exp.Column): 9738 typ = lambda_types.get(column.parts[0].name) 9739 if typ is not None: 9740 dot_or_id = column.to_dot() if column.table else column.this 9741 9742 if typ: 9743 dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ)) 9744 9745 parent = column.parent 9746 9747 while isinstance(parent, exp.Dot): 9748 if not isinstance(parent.parent, exp.Dot): 9749 parent.replace(dot_or_id) 9750 break 9751 parent = parent.parent 9752 else: 9753 if column is node: 9754 node = dot_or_id 9755 else: 9756 column.replace(dot_or_id) 9757 return node 9758 9759 def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr: 9760 start = self._prev 9761 9762 # Not to be confused with TRUNCATE(number, decimals) function call 9763 if self._match(TokenType.L_PAREN): 9764 self._retreat(self._index - 2) 9765 return self._parse_function() 9766 9767 # Clickhouse supports TRUNCATE DATABASE as well 9768 is_database = self._match(TokenType.DATABASE) 9769 9770 self._match(TokenType.TABLE) 9771 9772 exists = self._parse_exists(not_=False) 9773 9774 expressions = self._parse_csv( 9775 lambda: self._parse_table(schema=True, is_db_reference=is_database) 9776 ) 9777 9778 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9779 9780 if self._match_text_seq("RESTART", "IDENTITY"): 9781 identity = "RESTART" 9782 elif self._match_text_seq("CONTINUE", "IDENTITY"): 9783 identity = "CONTINUE" 9784 else: 9785 identity = None 9786 9787 if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): 9788 option = self._prev.text 9789 else: 9790 option = None 9791 9792 partition = self._parse_partition() 9793 9794 # Fallback case 9795 if self._curr: 9796 return self._parse_as_command(start) 9797 9798 return self.expression( 9799 exp.TruncateTable( 9800 expressions=expressions, 9801 is_database=is_database, 9802 exists=exists, 9803 cluster=cluster, 9804 identity=identity, 9805 option=option, 9806 partition=partition, 9807 ) 9808 ) 9809 9810 def _parse_indexed_column(self) -> exp.Expr | None: 9811 return self._parse_ordered(self._parse_opclass) 9812 9813 def _parse_with_operator(self) -> exp.Expr | None: 9814 this = self._parse_indexed_column() 9815 9816 if not self._match(TokenType.WITH): 9817 return this 9818 9819 op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS) 9820 9821 return self.expression(exp.WithOperator(this=this, op=op)) 9822 9823 def _parse_wrapped_options(self) -> list[exp.Expr]: 9824 self._match(TokenType.EQ) 9825 self._match(TokenType.L_PAREN) 9826 9827 opts: list[exp.Expr] = [] 9828 option: exp.Expr | list[exp.Expr] | None 9829 while self._curr and not self._match(TokenType.R_PAREN): 9830 if self._match_text_seq("FORMAT_NAME", "="): 9831 # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL 9832 option = self._parse_format_name() 9833 else: 9834 option = self._parse_property() 9835 9836 if option is None: 9837 self.raise_error("Unable to parse option") 9838 break 9839 9840 opts.extend(ensure_list(option)) 9841 9842 return opts 9843 9844 def _parse_copy_parameters(self) -> list[exp.CopyParameter]: 9845 sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None 9846 9847 options = [] 9848 while self._curr and not self._match(TokenType.R_PAREN, advance=False): 9849 option = self._parse_var(any_token=True) 9850 prev = self._prev.text.upper() 9851 9852 # Different dialects might separate options and values by white space, "=" and "AS" 9853 self._match(TokenType.EQ) 9854 self._match(TokenType.ALIAS) 9855 9856 param = self.expression(exp.CopyParameter(this=option)) 9857 9858 if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( 9859 TokenType.L_PAREN, advance=False 9860 ): 9861 # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options 9862 param.set("expressions", self._parse_wrapped_options()) 9863 elif prev == "FILE_FORMAT": 9864 # T-SQL's external file format case 9865 param.set("expression", self._parse_field()) 9866 elif ( 9867 prev == "FORMAT" 9868 and self._prev.token_type == TokenType.ALIAS 9869 and self._match_texts(("AVRO", "JSON")) 9870 ): 9871 param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) 9872 param.set("expression", self._parse_field()) 9873 else: 9874 param.set("expression", self._parse_unquoted_field() or self._parse_bracket()) 9875 9876 options.append(param) 9877 9878 if sep: 9879 self._match(sep) 9880 9881 return options 9882 9883 def _parse_credentials(self) -> exp.Credentials | None: 9884 expr = self.expression(exp.Credentials()) 9885 9886 if self._match_text_seq("STORAGE_INTEGRATION", "="): 9887 expr.set("storage", self._parse_field()) 9888 if self._match_text_seq("CREDENTIALS"): 9889 # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string> 9890 creds = ( 9891 self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field() 9892 ) 9893 expr.set("credentials", creds) 9894 if self._match_text_seq("ENCRYPTION"): 9895 expr.set("encryption", self._parse_wrapped_options()) 9896 if self._match_text_seq("IAM_ROLE"): 9897 expr.set( 9898 "iam_role", 9899 exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(), 9900 ) 9901 if self._match_text_seq("REGION"): 9902 expr.set("region", self._parse_field()) 9903 9904 return expr 9905 9906 def _parse_file_location(self) -> exp.Expr | None: 9907 return self._parse_field() 9908 9909 def _parse_copy(self) -> exp.Copy | exp.Command: 9910 start = self._prev 9911 9912 self._match(TokenType.INTO) 9913 9914 this = ( 9915 self._parse_select(nested=True, parse_subquery_alias=False) 9916 if self._match(TokenType.L_PAREN, advance=False) 9917 else self._parse_table(schema=True) 9918 ) 9919 9920 kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") 9921 9922 files = self._parse_csv(self._parse_file_location) 9923 if self._match(TokenType.EQ, advance=False): 9924 # Backtrack one token since we've consumed the lhs of a parameter assignment here. 9925 # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter 9926 # list via `_parse_wrapped(..)` below. 9927 self._advance(-1) 9928 files = [] 9929 9930 credentials = self._parse_credentials() 9931 9932 self._match_text_seq("WITH") 9933 9934 params = self._parse_wrapped(self._parse_copy_parameters, optional=True) 9935 9936 # Fallback case 9937 if self._curr: 9938 return self._parse_as_command(start) 9939 9940 return self.expression( 9941 exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params) 9942 ) 9943 9944 def _parse_normalize(self) -> exp.Normalize: 9945 return self.expression( 9946 exp.Normalize( 9947 this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var() 9948 ) 9949 ) 9950 9951 def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor: 9952 args = self._parse_csv(lambda: self._parse_lambda()) 9953 9954 this = seq_get(args, 0) 9955 decimals = seq_get(args, 1) 9956 9957 return expr_type( 9958 this=this, 9959 decimals=decimals, 9960 to=self._parse_var() if self._match_text_seq("TO") else None, 9961 ) 9962 9963 def _parse_star_ops(self) -> exp.Expr | None: 9964 star_token = self._prev 9965 9966 if self._match_text_seq("COLUMNS", "(", advance=False): 9967 this = self._parse_function() 9968 if isinstance(this, exp.Columns): 9969 this.set("unpack", True) 9970 return this 9971 9972 index = self._index 9973 ilike = self._parse_string() if self._match(TokenType.ILIKE) else None 9974 if not ilike: 9975 # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)` 9976 self._retreat(index) 9977 9978 return self.expression( 9979 exp.Star( 9980 ilike=ilike, 9981 except_=self._parse_star_op("EXCEPT", "EXCLUDE"), 9982 replace=self._parse_star_op("REPLACE"), 9983 rename=self._parse_star_op("RENAME"), 9984 ) 9985 ).update_positions(star_token) 9986 9987 def _parse_grant_privilege(self) -> exp.GrantPrivilege | None: 9988 privilege_parts = [] 9989 9990 # Keep consuming consecutive keywords until comma (end of this privilege) or ON 9991 # (end of privilege list) or L_PAREN (start of column list) are met 9992 while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False): 9993 privilege_parts.append(self._curr.text.upper()) 9994 self._advance() 9995 9996 this = exp.var(" ".join(privilege_parts)) 9997 expressions = ( 9998 self._parse_wrapped_csv(self._parse_column) 9999 if self._match(TokenType.L_PAREN, advance=False) 10000 else None 10001 ) 10002 10003 return self.expression(exp.GrantPrivilege(this=this, expressions=expressions)) 10004 10005 def _parse_grant_principal(self) -> exp.GrantPrincipal | None: 10006 kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() 10007 principal = self._parse_id_var() 10008 10009 if not principal: 10010 return None 10011 10012 return self.expression(exp.GrantPrincipal(this=principal, kind=kind)) 10013 10014 def _parse_grant_revoke_common( 10015 self, 10016 ) -> tuple[list | None, str | None, exp.Expr | None]: 10017 privileges = self._parse_csv(self._parse_grant_privilege) 10018 10019 self._match(TokenType.ON) 10020 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 10021 10022 # Attempt to parse the securable e.g. MySQL allows names 10023 # such as "foo.*", "*.*" which are not easily parseable yet 10024 securable = self._try_parse(self._parse_table_parts) 10025 10026 return privileges, kind, securable 10027 10028 def _parse_grant(self) -> exp.Grant | exp.Command: 10029 start = self._prev 10030 10031 privileges, kind, securable = self._parse_grant_revoke_common() 10032 10033 if not securable or not self._match_text_seq("TO"): 10034 return self._parse_as_command(start) 10035 10036 principals = self._parse_csv(self._parse_grant_principal) 10037 10038 grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") 10039 10040 if self._curr: 10041 return self._parse_as_command(start) 10042 10043 return self.expression( 10044 exp.Grant( 10045 privileges=privileges, 10046 kind=kind, 10047 securable=securable, 10048 principals=principals, 10049 grant_option=grant_option, 10050 ) 10051 ) 10052 10053 def _parse_revoke(self) -> exp.Revoke | exp.Command: 10054 start = self._prev 10055 10056 grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") 10057 10058 privileges, kind, securable = self._parse_grant_revoke_common() 10059 10060 if not securable or not self._match_text_seq("FROM"): 10061 return self._parse_as_command(start) 10062 10063 principals = self._parse_csv(self._parse_grant_principal) 10064 10065 cascade = None 10066 if self._match_texts(("CASCADE", "RESTRICT")): 10067 cascade = self._prev.text.upper() 10068 10069 if self._curr: 10070 return self._parse_as_command(start) 10071 10072 return self.expression( 10073 exp.Revoke( 10074 privileges=privileges, 10075 kind=kind, 10076 securable=securable, 10077 principals=principals, 10078 grant_option=grant_option, 10079 cascade=cascade, 10080 ) 10081 ) 10082 10083 def _parse_overlay(self) -> exp.Overlay: 10084 def _parse_overlay_arg(text: str) -> exp.Expr | None: 10085 return ( 10086 self._parse_bitwise() 10087 if self._match(TokenType.COMMA) or self._match_text_seq(text) 10088 else None 10089 ) 10090 10091 return self.expression( 10092 exp.Overlay( 10093 this=self._parse_bitwise(), 10094 expression=_parse_overlay_arg("PLACING"), 10095 from_=_parse_overlay_arg("FROM"), 10096 for_=_parse_overlay_arg("FOR"), 10097 ) 10098 ) 10099 10100 def _parse_format_name(self) -> exp.Property: 10101 # Note: Although not specified in the docs, Snowflake does accept a string/identifier 10102 # for FILE_FORMAT = <format_name> 10103 return self.expression( 10104 exp.Property( 10105 this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts() 10106 ) 10107 ) 10108 10109 def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F: 10110 is_distinct = self._match(TokenType.DISTINCT) 10111 if not is_distinct: 10112 self._match(TokenType.ALL) 10113 10114 args = [self._parse_lambda()] 10115 if self._match(TokenType.COMMA): 10116 args.extend(self._parse_function_args()) 10117 10118 target = seq_get(args, distinct_index) 10119 if is_distinct and target: 10120 args[distinct_index] = self.expression(exp.Distinct(expressions=[target])) 10121 10122 return func.from_arg_list(args) 10123 10124 def _identifier_expression( 10125 self, token: Token | None = None, quoted: bool | None = None 10126 ) -> exp.Identifier: 10127 token = token or self._prev 10128 return self.expression(exp.Identifier(this=token.text, quoted=quoted), token) 10129 10130 def _build_pipe_cte( 10131 self, 10132 query: exp.Query, 10133 expressions: list[exp.Expr], 10134 alias_cte: exp.TableAlias | None = None, 10135 ) -> exp.Select: 10136 new_cte: str | exp.TableAlias | None 10137 if alias_cte: 10138 new_cte = alias_cte 10139 else: 10140 self._pipe_cte_counter += 1 10141 new_cte = f"__tmp{self._pipe_cte_counter}" 10142 10143 with_ = query.args.get("with_") 10144 ctes = with_.pop() if with_ else None 10145 10146 new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) 10147 if ctes: 10148 new_select.set("with_", ctes) 10149 10150 return new_select.with_(new_cte, as_=query, copy=False) 10151 10152 def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: 10153 select = self._parse_select(consume_pipe=False) 10154 if not select: 10155 return query 10156 10157 return self._build_pipe_cte( 10158 query=query.select(*select.expressions, append=False), expressions=[exp.Star()] 10159 ) 10160 10161 def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: 10162 limit = self._parse_limit() 10163 offset = self._parse_offset() 10164 if limit: 10165 curr_limit = query.args.get("limit", limit) 10166 if curr_limit.expression.to_py() >= limit.expression.to_py(): 10167 query.limit(limit, copy=False) 10168 if offset: 10169 curr_offset = query.args.get("offset") 10170 curr_offset = curr_offset.expression.to_py() if curr_offset else 0 10171 query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False) 10172 10173 return query 10174 10175 def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None: 10176 this = self._parse_disjunction() 10177 if self._match_text_seq("GROUP", "AND", advance=False): 10178 return this 10179 10180 this = self._parse_alias(this) 10181 10182 if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): 10183 return self._parse_ordered(lambda: this) 10184 10185 return this 10186 10187 def _parse_pipe_syntax_aggregate_group_order_by( 10188 self, query: exp.Select, group_by_exists: bool = True 10189 ) -> exp.Select: 10190 expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) 10191 aggregates_or_groups, orders = [], [] 10192 for element in expr: 10193 if isinstance(element, exp.Ordered): 10194 this = element.this 10195 if isinstance(this, exp.Alias): 10196 element.set("this", this.args["alias"]) 10197 orders.append(element) 10198 else: 10199 this = element 10200 aggregates_or_groups.append(this) 10201 10202 if group_by_exists: 10203 query.select( 10204 *aggregates_or_groups, *query.expressions, append=False, copy=False 10205 ).group_by( 10206 *[projection.args.get("alias", projection) for projection in aggregates_or_groups], 10207 copy=False, 10208 ) 10209 else: 10210 query.select(*aggregates_or_groups, append=False, copy=False) 10211 10212 if orders: 10213 return query.order_by(*orders, append=False, copy=False) 10214 10215 return query 10216 10217 def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: 10218 self._match_text_seq("AGGREGATE") 10219 query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False) 10220 10221 if self._match(TokenType.GROUP_BY) or ( 10222 self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) 10223 ): 10224 query = self._parse_pipe_syntax_aggregate_group_order_by(query) 10225 10226 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10227 10228 def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None: 10229 first_setop = self.parse_set_operation(this=query) 10230 if not first_setop: 10231 return None 10232 10233 def _parse_and_unwrap_query() -> exp.Expr | None: 10234 expr = self._parse_paren() 10235 return expr.assert_is(exp.Subquery).unnest() if expr else None 10236 10237 first_setop.this.pop() 10238 10239 setops = [ 10240 first_setop.expression.pop().assert_is(exp.Subquery).unnest(), 10241 *self._parse_csv(_parse_and_unwrap_query), 10242 ] 10243 10244 query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10245 with_ = query.args.get("with_") 10246 ctes = with_.pop() if with_ else None 10247 10248 if isinstance(first_setop, exp.Union): 10249 query = query.union(*setops, copy=False, **first_setop.args) 10250 elif isinstance(first_setop, exp.Except): 10251 query = query.except_(*setops, copy=False, **first_setop.args) 10252 else: 10253 query = query.intersect(*setops, copy=False, **first_setop.args) 10254 10255 query.set("with_", ctes) 10256 10257 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10258 10259 def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None: 10260 join = self._parse_join() 10261 if not join: 10262 return None 10263 10264 if isinstance(query, exp.Select): 10265 return query.join(join, copy=False) 10266 10267 return query 10268 10269 def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: 10270 pivots = self._parse_pivots() 10271 if not pivots: 10272 return query 10273 10274 from_ = query.args.get("from_") 10275 if from_: 10276 from_.this.set("pivots", pivots) 10277 10278 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10279 10280 def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: 10281 self._match_text_seq("EXTEND") 10282 query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False) 10283 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10284 10285 def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: 10286 sample = self._parse_table_sample() 10287 10288 with_ = query.args.get("with_") 10289 if with_: 10290 with_.expressions[-1].this.set("sample", sample) 10291 else: 10292 query.set("sample", sample) 10293 10294 return query 10295 10296 def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None: 10297 if isinstance(query, exp.Subquery): 10298 query = exp.select("*").from_(query, copy=False) 10299 10300 if not query.args.get("from_"): 10301 query = exp.select("*").from_(query.subquery(copy=False), copy=False) 10302 10303 while self._match(TokenType.PIPE_GT): 10304 start_index = self._index 10305 start_text = self._curr.text.upper() 10306 parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text) 10307 if not parser: 10308 # The set operators (UNION, etc) and the JOIN operator have a few common starting 10309 # keywords, making it tricky to disambiguate them without lookahead. The approach 10310 # here is to try and parse a set operation and if that fails, then try to parse a 10311 # join operator. If that fails as well, then the operator is not supported. 10312 parsed_query = self._parse_pipe_syntax_set_operator(query) 10313 parsed_query = parsed_query or self._parse_pipe_syntax_join(query) 10314 if not parsed_query: 10315 self._retreat(start_index) 10316 self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.") 10317 break 10318 query = parsed_query 10319 else: 10320 query = parser(self, query) 10321 10322 return query 10323 10324 def _parse_declareitem(self) -> exp.DeclareItem | None: 10325 self._match_texts(("VAR", "VARIABLE")) 10326 10327 vars = self._parse_csv(self._parse_id_var) 10328 if not vars: 10329 return None 10330 10331 self._match(TokenType.ALIAS) 10332 kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types() 10333 default = ( 10334 self._match(TokenType.DEFAULT) or self._match(TokenType.EQ) 10335 ) and self._parse_bitwise() 10336 10337 return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default)) 10338 10339 def _parse_declare(self) -> exp.Declare | exp.Command: 10340 start = self._prev 10341 replace = self._match_text_seq("OR", "REPLACE") 10342 expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) 10343 10344 if not expressions or self._curr: 10345 return self._parse_as_command(start) 10346 10347 return self.expression(exp.Declare(expressions=expressions, replace=replace)) 10348 10349 def build_cast(self, strict: bool, **kwargs) -> exp.Expr: 10350 exp_class = exp.Cast if strict else exp.TryCast 10351 10352 if exp_class == exp.TryCast: 10353 kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING 10354 10355 return self.expression(exp_class(**kwargs)) 10356 10357 def _parse_json_value(self) -> exp.JSONValue: 10358 this = self._parse_bitwise() 10359 self._match(TokenType.COMMA) 10360 path = self._parse_bitwise() 10361 10362 returning = self._match(TokenType.RETURNING) and self._parse_type() 10363 10364 return self.expression( 10365 exp.JSONValue( 10366 this=this, 10367 path=self.dialect.to_json_path(path), 10368 returning=returning, 10369 on_condition=self._parse_on_condition(), 10370 ) 10371 ) 10372 10373 def _parse_group_concat(self) -> exp.Expr | None: 10374 def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr: 10375 if isinstance(node, exp.Distinct) and len(node.expressions) > 1: 10376 concat_exprs = [ 10377 self.expression( 10378 exp.Concat( 10379 expressions=node.expressions, 10380 safe=True, 10381 coalesce=self.dialect.CONCAT_COALESCE, 10382 ) 10383 ) 10384 ] 10385 node.set("expressions", concat_exprs) 10386 return node 10387 if len(exprs) == 1: 10388 return exprs[0] 10389 return self.expression( 10390 exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE) 10391 ) 10392 10393 args = self._parse_csv(self._parse_lambda) 10394 10395 if args: 10396 order = args[-1] if isinstance(args[-1], exp.Order) else None 10397 10398 if order: 10399 # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, 10400 # remove 'expr' from exp.Order and add it back to args 10401 args[-1] = order.this 10402 order.set("this", concat_exprs(order.this, args)) 10403 10404 this = order or concat_exprs(args[0], args) 10405 else: 10406 this = None 10407 10408 separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None 10409 10410 return self.expression(exp.GroupConcat(this=this, separator=separator)) 10411 10412 def _parse_initcap(self) -> exp.Initcap: 10413 expr = exp.Initcap.from_arg_list(self._parse_function_args()) 10414 10415 # attach dialect's default delimiters 10416 if expr.args.get("expression") is None: 10417 expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)) 10418 10419 return expr 10420 10421 def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None: 10422 while True: 10423 if not self._match(TokenType.L_PAREN): 10424 break 10425 10426 op = "" 10427 while self._curr and not self._match(TokenType.R_PAREN): 10428 op += self._curr.text 10429 self._advance() 10430 10431 comments = self._prev_comments 10432 this = self.expression( 10433 exp.Operator(this=this, operator=op, expression=self._parse_bitwise()), 10434 comments=comments, 10435 ) 10436 10437 if not self._match(TokenType.OPERATOR): 10438 break 10439 10440 return this
51def build_var_map(args: BuilderArgs) -> exp.StarMap | exp.VarMap: 52 if len(args) == 1 and args[0].is_star: 53 return exp.StarMap(this=args[0]) 54 55 keys: list[ExpOrStr] = [] 56 values: list[ExpOrStr] = [] 57 for i in range(0, len(args), 2): 58 keys.append(args[i]) 59 values.append(args[i + 1]) 60 61 return exp.VarMap(keys=exp.array(*keys, copy=False), values=exp.array(*values, copy=False))
69def binary_range_parser( 70 expr_type: Type[exp.Expr], reverse_args: bool = False 71) -> t.Callable[[Parser, exp.Expr | None], exp.Expr | None]: 72 def _parse_binary_range(self: Parser, this: exp.Expr | None) -> exp.Expr | None: 73 expression = self._parse_bitwise() 74 if reverse_args: 75 this, expression = expression, this 76 return self._parse_escape(self.expression(expr_type(this=this, expression=expression))) 77 78 return _parse_binary_range
81def build_logarithm(args: BuilderArgs, dialect: Dialect) -> exp.Func: 82 # Default argument order is base, expression 83 this = seq_get(args, 0) 84 expression = seq_get(args, 1) 85 86 if expression: 87 if not dialect.LOG_BASE_FIRST: 88 this, expression = expression, this 89 return exp.Log(this=this, expression=expression) 90 91 return (exp.Ln if dialect.parser_class.LOG_DEFAULTS_TO_LN else exp.Log)(this=this)
111def build_extract_json_with_path( 112 expr_type: Type[E], 113) -> t.Callable[[BuilderArgs, Dialect], E]: 114 def _builder(args: BuilderArgs, dialect: Dialect) -> E: 115 expression = expr_type( 116 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 117 ) 118 if len(args) > 2 and expr_type is exp.JSONExtract: 119 expression.set("expressions", args[2:]) 120 if expr_type is exp.JSONExtractScalar: 121 expression.set("scalar_only", dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY) 122 123 return expression 124 125 return _builder
128def build_mod(args: BuilderArgs) -> exp.Mod: 129 this = seq_get(args, 0) 130 expression = seq_get(args, 1) 131 132 # Wrap the operands if they are binary nodes, e.g. MOD(a + 1, 7) -> (a + 1) % 7 133 this = exp.Paren(this=this) if isinstance(this, exp.Binary) else this 134 expression = exp.Paren(this=expression) if isinstance(expression, exp.Binary) else expression 135 136 return exp.Mod(this=this, expression=expression)
148def build_array_constructor( 149 exp_class: Type[E], args: list[t.Any], bracket_kind: TokenType, dialect: Dialect 150) -> exp.Expr: 151 array_exp = exp_class(expressions=args) 152 153 if exp_class == exp.Array and dialect.HAS_DISTINCT_ARRAY_CONSTRUCTORS: 154 array_exp.set("bracket_notation", bracket_kind == TokenType.L_BRACKET) 155 156 return array_exp
159def build_convert_timezone( 160 args: BuilderArgs, default_source_tz: str | None = None 161) -> exp.ConvertTimezone | exp.Anonymous: 162 if len(args) == 2: 163 source_tz = exp.Literal.string(default_source_tz) if default_source_tz else None 164 return exp.ConvertTimezone( 165 source_tz=source_tz, target_tz=seq_get(args, 0), timestamp=seq_get(args, 1) 166 ) 167 168 return exp.ConvertTimezone.from_arg_list(args)
171def build_trim(args: BuilderArgs, is_left: bool = True, reverse_args: bool = False) -> exp.Trim: 172 this, expression = seq_get(args, 0), seq_get(args, 1) 173 174 if expression and reverse_args: 175 this, expression = expression, this 176 177 return exp.Trim(this=this, expression=expression, position="LEADING" if is_left else "TRAILING")
194def build_array_append(args: BuilderArgs, dialect: Dialect) -> exp.ArrayAppend: 195 """ 196 Builds ArrayAppend with NULL propagation semantics based on the dialect configuration. 197 198 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 199 Others (DuckDB, PostgreSQL) create a new single-element array instead. 200 201 Args: 202 args: Function arguments [array, element] 203 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 204 205 Returns: 206 ArrayAppend expression with appropriate null_propagation flag 207 """ 208 return exp.ArrayAppend( 209 this=seq_get(args, 0), 210 expression=seq_get(args, 1), 211 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 212 )
Builds ArrayAppend with NULL propagation semantics based on the dialect configuration.
Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. Others (DuckDB, PostgreSQL) create a new single-element array instead.
Arguments:
- args: Function arguments [array, element]
- dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:
ArrayAppend expression with appropriate null_propagation flag
215def build_array_prepend(args: BuilderArgs, dialect: Dialect) -> exp.ArrayPrepend: 216 """ 217 Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration. 218 219 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 220 Others (DuckDB, PostgreSQL) create a new single-element array instead. 221 222 Args: 223 args: Function arguments [array, element] 224 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 225 226 Returns: 227 ArrayPrepend expression with appropriate null_propagation flag 228 """ 229 return exp.ArrayPrepend( 230 this=seq_get(args, 0), 231 expression=seq_get(args, 1), 232 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 233 )
Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration.
Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. Others (DuckDB, PostgreSQL) create a new single-element array instead.
Arguments:
- args: Function arguments [array, element]
- dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:
ArrayPrepend expression with appropriate null_propagation flag
236def build_array_concat(args: BuilderArgs, dialect: Dialect) -> exp.ArrayConcat: 237 """ 238 Builds ArrayConcat with NULL propagation semantics based on the dialect configuration. 239 240 Some dialects (Redshift, Snowflake) return NULL when any input array is NULL. 241 Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation. 242 243 Args: 244 args: Function arguments [array1, array2, ...] (variadic) 245 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 246 247 Returns: 248 ArrayConcat expression with appropriate null_propagation flag 249 """ 250 return exp.ArrayConcat( 251 this=seq_get(args, 0), 252 expressions=args[1:], 253 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 254 )
Builds ArrayConcat with NULL propagation semantics based on the dialect configuration.
Some dialects (Redshift, Snowflake) return NULL when any input array is NULL. Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation.
Arguments:
- args: Function arguments [array1, array2, ...] (variadic)
- dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:
ArrayConcat expression with appropriate null_propagation flag
257def build_array_remove(args: BuilderArgs, dialect: Dialect) -> exp.ArrayRemove: 258 """ 259 Builds ArrayRemove with NULL propagation semantics based on the dialect configuration. 260 261 Some dialects (Snowflake) return NULL when the removal value is NULL. 262 Others (DuckDB) may return empty array due to NULL comparison semantics. 263 264 Args: 265 args: Function arguments [array, value_to_remove] 266 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 267 268 Returns: 269 ArrayRemove expression with appropriate null_propagation flag 270 """ 271 return exp.ArrayRemove( 272 this=seq_get(args, 0), 273 expression=seq_get(args, 1), 274 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 275 )
Builds ArrayRemove with NULL propagation semantics based on the dialect configuration.
Some dialects (Snowflake) return NULL when the removal value is NULL. Others (DuckDB) may return empty array due to NULL comparison semantics.
Arguments:
- args: Function arguments [array, value_to_remove]
- dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:
ArrayRemove expression with appropriate null_propagation flag
306def build_json_extract_scalar( 307 self: Parser, this: exp.Expr, path: exp.Expr 308) -> exp.JSONExtractScalar: 309 return self.expression( 310 exp.JSONExtractScalar( 311 this=this, 312 expression=self.dialect.to_json_path(path), 313 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 314 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 315 ) 316 )
336class Parser: 337 """ 338 Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree. 339 340 Args: 341 error_level: The desired error level. 342 Default: ErrorLevel.IMMEDIATE 343 error_message_context: The amount of context to capture from a query string when displaying 344 the error message (in number of characters). 345 Default: 100 346 max_errors: Maximum number of error messages to include in a raised ParseError. 347 This is only relevant if error_level is ErrorLevel.RAISE. 348 Default: 3 349 max_nodes: Maximum number of AST nodes to prevent memory exhaustion. 350 Set to -1 (default) to disable the check. 351 """ 352 353 __slots__ = ( 354 "error_level", 355 "error_message_context", 356 "max_errors", 357 "max_nodes", 358 "dialect", 359 "sql", 360 "errors", 361 "_tokens", 362 "_index", 363 "_curr", 364 "_next", 365 "_prev", 366 "_prev_comments", 367 "_pipe_cte_counter", 368 "_chunks", 369 "_chunk_index", 370 "_tokens_size", 371 "_node_count", 372 ) 373 374 FUNCTIONS: t.ClassVar[dict[str, t.Callable]] = { 375 **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()}, 376 **dict.fromkeys(("COALESCE", "IFNULL", "NVL"), build_coalesce), 377 "ARRAY": lambda args, dialect: exp.Array(expressions=args), 378 "ARRAYAGG": lambda args, dialect: exp.ArrayAgg( 379 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 380 ), 381 "ARRAY_AGG": lambda args, dialect: exp.ArrayAgg( 382 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 383 ), 384 "ARRAY_APPEND": build_array_append, 385 "ARRAY_CAT": build_array_concat, 386 "ARRAY_CONCAT": build_array_concat, 387 "ARRAY_INTERSECT": lambda args: exp.ArrayIntersect(expressions=args), 388 "ARRAY_INTERSECTION": lambda args: exp.ArrayIntersect(expressions=args), 389 "ARRAY_PREPEND": build_array_prepend, 390 "ARRAY_REMOVE": build_array_remove, 391 "COUNT": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True), 392 "CONCAT": lambda args, dialect: exp.Concat( 393 expressions=args, 394 safe=not dialect.STRICT_STRING_CONCAT, 395 coalesce=dialect.CONCAT_COALESCE, 396 ), 397 "CONCAT_WS": lambda args, dialect: exp.ConcatWs( 398 expressions=args, 399 safe=not dialect.STRICT_STRING_CONCAT, 400 coalesce=dialect.CONCAT_WS_COALESCE, 401 ), 402 "CONVERT_TIMEZONE": build_convert_timezone, 403 "DATE_TO_DATE_STR": lambda args: exp.Cast( 404 this=seq_get(args, 0), 405 to=exp.DataType(this=exp.DType.TEXT), 406 ), 407 "GENERATE_DATE_ARRAY": lambda args: exp.GenerateDateArray( 408 start=seq_get(args, 0), 409 end=seq_get(args, 1), 410 step=seq_get(args, 2) or exp.Interval(this=exp.Literal.string(1), unit=exp.var("DAY")), 411 ), 412 "GENERATE_UUID": lambda args, dialect: exp.Uuid( 413 is_string=dialect.UUID_IS_STRING_TYPE or None 414 ), 415 "GLOB": lambda args: exp.Glob(this=seq_get(args, 1), expression=seq_get(args, 0)), 416 "GREATEST": lambda args, dialect: exp.Greatest( 417 this=seq_get(args, 0), 418 expressions=args[1:], 419 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 420 ), 421 "LEAST": lambda args, dialect: exp.Least( 422 this=seq_get(args, 0), 423 expressions=args[1:], 424 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 425 ), 426 "HEX": build_hex, 427 "JSON_EXTRACT": build_extract_json_with_path(exp.JSONExtract), 428 "JSON_EXTRACT_SCALAR": build_extract_json_with_path(exp.JSONExtractScalar), 429 "JSON_EXTRACT_PATH_TEXT": build_extract_json_with_path(exp.JSONExtractScalar), 430 "JSON_KEYS": lambda args, dialect: exp.JSONKeys( 431 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 432 ), 433 "LIKE": build_like, 434 "LOG": build_logarithm, 435 "LOG2": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)), 436 "LOG10": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)), 437 "LOWER": build_lower, 438 "LPAD": lambda args: build_pad(args), 439 "LEFTPAD": lambda args: build_pad(args), 440 "LTRIM": lambda args: build_trim(args), 441 "MOD": build_mod, 442 "RIGHTPAD": lambda args: build_pad(args, is_left=False), 443 "RPAD": lambda args: build_pad(args, is_left=False), 444 "RTRIM": lambda args: build_trim(args, is_left=False), 445 "SCOPE_RESOLUTION": lambda args: ( 446 exp.ScopeResolution(expression=seq_get(args, 0)) 447 if len(args) != 2 448 else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1)) 449 ), 450 "STRPOS": exp.StrPosition.from_arg_list, 451 "CHARINDEX": lambda args: build_locate_strposition(args), 452 "INSTR": exp.StrPosition.from_arg_list, 453 "LOCATE": lambda args: build_locate_strposition(args), 454 "TIME_TO_TIME_STR": lambda args: exp.Cast( 455 this=seq_get(args, 0), 456 to=exp.DataType(this=exp.DType.TEXT), 457 ), 458 "TO_HEX": build_hex, 459 "TS_OR_DS_TO_DATE_STR": lambda args: exp.Substring( 460 this=exp.Cast( 461 this=seq_get(args, 0), 462 to=exp.DataType(this=exp.DType.TEXT), 463 ), 464 start=exp.Literal.number(1), 465 length=exp.Literal.number(10), 466 ), 467 "UNNEST": lambda args: exp.Unnest(expressions=ensure_list(seq_get(args, 0))), 468 "UPPER": build_upper, 469 "UUID": lambda args, dialect: exp.Uuid(is_string=dialect.UUID_IS_STRING_TYPE or None), 470 "UUID_STRING": lambda args, dialect: exp.Uuid( 471 this=seq_get(args, 0), 472 name=seq_get(args, 1), 473 is_string=dialect.UUID_IS_STRING_TYPE or None, 474 ), 475 "VAR_MAP": build_var_map, 476 } 477 478 NO_PAREN_FUNCTIONS: t.ClassVar[dict] = { 479 TokenType.CURRENT_DATE: exp.CurrentDate, 480 TokenType.CURRENT_DATETIME: exp.CurrentDate, 481 TokenType.CURRENT_TIME: exp.CurrentTime, 482 TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp, 483 TokenType.CURRENT_USER: exp.CurrentUser, 484 TokenType.CURRENT_ROLE: exp.CurrentRole, 485 } 486 487 STRUCT_TYPE_TOKENS: t.ClassVar = { 488 TokenType.NESTED, 489 TokenType.OBJECT, 490 TokenType.STRUCT, 491 TokenType.UNION, 492 } 493 494 NESTED_TYPE_TOKENS: t.ClassVar = { 495 TokenType.ARRAY, 496 TokenType.LIST, 497 TokenType.LOWCARDINALITY, 498 TokenType.MAP, 499 TokenType.NULLABLE, 500 TokenType.RANGE, 501 *STRUCT_TYPE_TOKENS, 502 } 503 504 ENUM_TYPE_TOKENS: t.ClassVar = { 505 TokenType.DYNAMIC, 506 TokenType.ENUM, 507 TokenType.ENUM8, 508 TokenType.ENUM16, 509 } 510 511 AGGREGATE_TYPE_TOKENS: t.ClassVar = { 512 TokenType.AGGREGATEFUNCTION, 513 TokenType.SIMPLEAGGREGATEFUNCTION, 514 } 515 516 TYPE_TOKENS: t.ClassVar = { 517 TokenType.BIT, 518 TokenType.BOOLEAN, 519 TokenType.TINYINT, 520 TokenType.UTINYINT, 521 TokenType.SMALLINT, 522 TokenType.USMALLINT, 523 TokenType.INT, 524 TokenType.UINT, 525 TokenType.BIGINT, 526 TokenType.UBIGINT, 527 TokenType.BIGNUM, 528 TokenType.INT128, 529 TokenType.UINT128, 530 TokenType.INT256, 531 TokenType.UINT256, 532 TokenType.MEDIUMINT, 533 TokenType.UMEDIUMINT, 534 TokenType.FIXEDSTRING, 535 TokenType.FLOAT, 536 TokenType.DOUBLE, 537 TokenType.UDOUBLE, 538 TokenType.CHAR, 539 TokenType.NCHAR, 540 TokenType.VARCHAR, 541 TokenType.NVARCHAR, 542 TokenType.BPCHAR, 543 TokenType.TEXT, 544 TokenType.MEDIUMTEXT, 545 TokenType.LONGTEXT, 546 TokenType.BLOB, 547 TokenType.MEDIUMBLOB, 548 TokenType.LONGBLOB, 549 TokenType.BINARY, 550 TokenType.VARBINARY, 551 TokenType.JSON, 552 TokenType.JSONB, 553 TokenType.INTERVAL, 554 TokenType.TINYBLOB, 555 TokenType.TINYTEXT, 556 TokenType.TIME, 557 TokenType.TIMETZ, 558 TokenType.TIME_NS, 559 TokenType.TIMESTAMP, 560 TokenType.TIMESTAMP_S, 561 TokenType.TIMESTAMP_MS, 562 TokenType.TIMESTAMP_NS, 563 TokenType.TIMESTAMPTZ, 564 TokenType.TIMESTAMPLTZ, 565 TokenType.TIMESTAMPNTZ, 566 TokenType.DATETIME, 567 TokenType.DATETIME2, 568 TokenType.DATETIME64, 569 TokenType.SMALLDATETIME, 570 TokenType.DATE, 571 TokenType.DATE32, 572 TokenType.INT4RANGE, 573 TokenType.INT4MULTIRANGE, 574 TokenType.INT8RANGE, 575 TokenType.INT8MULTIRANGE, 576 TokenType.NUMRANGE, 577 TokenType.NUMMULTIRANGE, 578 TokenType.TSRANGE, 579 TokenType.TSMULTIRANGE, 580 TokenType.TSTZRANGE, 581 TokenType.TSTZMULTIRANGE, 582 TokenType.DATERANGE, 583 TokenType.DATEMULTIRANGE, 584 TokenType.DECIMAL, 585 TokenType.DECIMAL32, 586 TokenType.DECIMAL64, 587 TokenType.DECIMAL128, 588 TokenType.DECIMAL256, 589 TokenType.DECFLOAT, 590 TokenType.UDECIMAL, 591 TokenType.BIGDECIMAL, 592 TokenType.UUID, 593 TokenType.GEOGRAPHY, 594 TokenType.GEOGRAPHYPOINT, 595 TokenType.GEOMETRY, 596 TokenType.POINT, 597 TokenType.RING, 598 TokenType.LINESTRING, 599 TokenType.MULTILINESTRING, 600 TokenType.POLYGON, 601 TokenType.MULTIPOLYGON, 602 TokenType.HLLSKETCH, 603 TokenType.HSTORE, 604 TokenType.PSEUDO_TYPE, 605 TokenType.SUPER, 606 TokenType.SERIAL, 607 TokenType.SMALLSERIAL, 608 TokenType.BIGSERIAL, 609 TokenType.XML, 610 TokenType.YEAR, 611 TokenType.USERDEFINED, 612 TokenType.MONEY, 613 TokenType.SMALLMONEY, 614 TokenType.ROWVERSION, 615 TokenType.IMAGE, 616 TokenType.VARIANT, 617 TokenType.VECTOR, 618 TokenType.VOID, 619 TokenType.OBJECT, 620 TokenType.OBJECT_IDENTIFIER, 621 TokenType.INET, 622 TokenType.IPADDRESS, 623 TokenType.IPPREFIX, 624 TokenType.IPV4, 625 TokenType.IPV6, 626 TokenType.UNKNOWN, 627 TokenType.NOTHING, 628 TokenType.NULL, 629 TokenType.NAME, 630 TokenType.TDIGEST, 631 TokenType.DYNAMIC, 632 *ENUM_TYPE_TOKENS, 633 *NESTED_TYPE_TOKENS, 634 *AGGREGATE_TYPE_TOKENS, 635 } 636 637 SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = { 638 TokenType.BIGINT: TokenType.UBIGINT, 639 TokenType.INT: TokenType.UINT, 640 TokenType.MEDIUMINT: TokenType.UMEDIUMINT, 641 TokenType.SMALLINT: TokenType.USMALLINT, 642 TokenType.TINYINT: TokenType.UTINYINT, 643 TokenType.DECIMAL: TokenType.UDECIMAL, 644 TokenType.DOUBLE: TokenType.UDOUBLE, 645 } 646 647 SUBQUERY_PREDICATES: t.ClassVar = { 648 TokenType.ANY: exp.Any, 649 TokenType.ALL: exp.All, 650 TokenType.EXISTS: exp.Exists, 651 TokenType.SOME: exp.Any, 652 } 653 654 SUBQUERY_TOKENS: t.ClassVar = { 655 TokenType.SELECT, 656 TokenType.WITH, 657 TokenType.FROM, 658 } 659 660 RESERVED_TOKENS: t.ClassVar = { 661 *Tokenizer.SINGLE_TOKENS.values(), 662 TokenType.SELECT, 663 } - {TokenType.IDENTIFIER} 664 665 # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers, 666 # string literals), so they must never be treated as keywords when matching by text 667 TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset( 668 { 669 TokenType.BIT_STRING, 670 TokenType.BYTE_STRING, 671 TokenType.HEREDOC_STRING, 672 TokenType.HEX_STRING, 673 TokenType.IDENTIFIER, 674 TokenType.NATIONAL_STRING, 675 TokenType.RAW_STRING, 676 TokenType.STRING, 677 TokenType.UNICODE_STRING, 678 } 679 ) 680 681 DB_CREATABLES: t.ClassVar = { 682 TokenType.DATABASE, 683 TokenType.DICTIONARY, 684 TokenType.FILE_FORMAT, 685 TokenType.MODEL, 686 TokenType.NAMESPACE, 687 TokenType.SCHEMA, 688 TokenType.SEMANTIC_VIEW, 689 TokenType.SEQUENCE, 690 TokenType.SINK, 691 TokenType.SOURCE, 692 TokenType.STAGE, 693 TokenType.STORAGE_INTEGRATION, 694 TokenType.STREAMLIT, 695 TokenType.TABLE, 696 TokenType.TAG, 697 TokenType.VIEW, 698 TokenType.WAREHOUSE, 699 } 700 701 CREATABLES: t.ClassVar = { 702 TokenType.COLUMN, 703 TokenType.CONSTRAINT, 704 TokenType.FOREIGN_KEY, 705 TokenType.FUNCTION, 706 TokenType.INDEX, 707 TokenType.PROCEDURE, 708 TokenType.TRIGGER, 709 TokenType.TYPE, 710 *DB_CREATABLES, 711 } 712 713 TRIGGER_EVENTS: t.ClassVar = { 714 TokenType.INSERT, 715 TokenType.UPDATE, 716 TokenType.DELETE, 717 TokenType.TRUNCATE, 718 } 719 720 ALTERABLES: t.ClassVar = { 721 TokenType.INDEX, 722 TokenType.TABLE, 723 TokenType.VIEW, 724 TokenType.SESSION, 725 } 726 727 # Tokens that can represent identifiers 728 ID_VAR_TOKENS: t.ClassVar[set] = { 729 TokenType.ALL, 730 TokenType.ANALYZE, 731 TokenType.ATTACH, 732 TokenType.VAR, 733 TokenType.ANTI, 734 TokenType.APPLY, 735 TokenType.ASC, 736 TokenType.ASOF, 737 TokenType.AUTO_INCREMENT, 738 TokenType.BEGIN, 739 TokenType.BPCHAR, 740 TokenType.CACHE, 741 TokenType.CASE, 742 TokenType.COLLATE, 743 TokenType.COMMAND, 744 TokenType.COMMENT, 745 TokenType.COMMIT, 746 TokenType.CONSTRAINT, 747 TokenType.COPY, 748 TokenType.CUBE, 749 TokenType.CURRENT_SCHEMA, 750 TokenType.DECLARE, 751 TokenType.DEFAULT, 752 TokenType.DELETE, 753 TokenType.DESC, 754 TokenType.DESCRIBE, 755 TokenType.DETACH, 756 TokenType.DICTIONARY, 757 TokenType.DIV, 758 TokenType.END, 759 TokenType.EXECUTE, 760 TokenType.EXPORT, 761 TokenType.ESCAPE, 762 TokenType.FALSE, 763 TokenType.FIRST, 764 TokenType.FILE, 765 TokenType.FILTER, 766 TokenType.FINAL, 767 TokenType.FORMAT, 768 TokenType.FULL, 769 TokenType.GET, 770 TokenType.IDENTIFIER, 771 TokenType.INOUT, 772 TokenType.IS, 773 TokenType.ISNULL, 774 TokenType.INTERVAL, 775 TokenType.KEEP, 776 TokenType.KILL, 777 TokenType.LEFT, 778 TokenType.LIMIT, 779 TokenType.LOAD, 780 TokenType.LOCK, 781 TokenType.MATCH, 782 TokenType.MERGE, 783 TokenType.NATURAL, 784 TokenType.NEXT, 785 TokenType.OFFSET, 786 TokenType.OPERATOR, 787 TokenType.ORDINALITY, 788 TokenType.OUT, 789 TokenType.OVER, 790 TokenType.OVERLAPS, 791 TokenType.OVERWRITE, 792 TokenType.PARTITION, 793 TokenType.PERCENT, 794 TokenType.PIVOT, 795 TokenType.PROJECTION, 796 TokenType.PRAGMA, 797 TokenType.PUT, 798 TokenType.RANGE, 799 TokenType.RECURSIVE, 800 TokenType.REFERENCES, 801 TokenType.REFRESH, 802 TokenType.RENAME, 803 TokenType.REPLACE, 804 TokenType.RIGHT, 805 TokenType.ROLLUP, 806 TokenType.ROW, 807 TokenType.ROWS, 808 TokenType.SEMI, 809 TokenType.SET, 810 TokenType.SETTINGS, 811 TokenType.SHOW, 812 TokenType.STREAM, 813 TokenType.STREAMLIT, 814 TokenType.TEMPORARY, 815 TokenType.TOP, 816 TokenType.TRUE, 817 TokenType.TRUNCATE, 818 TokenType.UNIQUE, 819 TokenType.UNNEST, 820 TokenType.UNPIVOT, 821 TokenType.UPDATE, 822 TokenType.USE, 823 TokenType.VOLATILE, 824 TokenType.WINDOW, 825 TokenType.CURRENT_CATALOG, 826 TokenType.LOCALTIME, 827 TokenType.LOCALTIMESTAMP, 828 TokenType.SESSION_USER, 829 TokenType.STRAIGHT_JOIN, 830 *ALTERABLES, 831 *CREATABLES, 832 *SUBQUERY_PREDICATES, 833 *TYPE_TOKENS, 834 *NO_PAREN_FUNCTIONS, 835 } - {TokenType.UNION} 836 837 TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - { 838 TokenType.ANTI, 839 TokenType.ASOF, 840 TokenType.FULL, 841 TokenType.LEFT, 842 TokenType.LOCK, 843 TokenType.NATURAL, 844 TokenType.RIGHT, 845 TokenType.SEMI, 846 TokenType.WINDOW, 847 } 848 849 ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS 850 851 COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS 852 853 ARRAY_CONSTRUCTORS: t.ClassVar = { 854 "ARRAY": exp.Array, 855 "LIST": exp.List, 856 } 857 858 COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS} 859 860 UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET} 861 862 TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"} 863 864 # Tokens that indicate a simple column reference 865 IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER}) 866 867 BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE}) 868 869 # Postfix tokens that prevent the bare column fast path 870 COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 871 { 872 TokenType.L_PAREN, 873 TokenType.L_BRACKET, 874 TokenType.L_BRACE, 875 TokenType.COLON, 876 TokenType.JOIN_MARKER, 877 } 878 ) 879 880 TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 881 { 882 TokenType.L_PAREN, 883 TokenType.L_BRACKET, 884 TokenType.L_BRACE, 885 TokenType.PIVOT, 886 TokenType.UNPIVOT, 887 TokenType.TABLE_SAMPLE, 888 } 889 ) 890 891 FUNC_TOKENS: t.ClassVar = { 892 TokenType.COLLATE, 893 TokenType.COMMAND, 894 TokenType.CURRENT_DATE, 895 TokenType.CURRENT_DATETIME, 896 TokenType.CURRENT_SCHEMA, 897 TokenType.CURRENT_TIMESTAMP, 898 TokenType.CURRENT_TIME, 899 TokenType.CURRENT_USER, 900 TokenType.CURRENT_CATALOG, 901 TokenType.DECLARE, 902 TokenType.FILTER, 903 TokenType.FIRST, 904 TokenType.FORMAT, 905 TokenType.GET, 906 TokenType.GLOB, 907 TokenType.IDENTIFIER, 908 TokenType.INDEX, 909 TokenType.ISNULL, 910 TokenType.ILIKE, 911 TokenType.INSERT, 912 TokenType.LIKE, 913 TokenType.LOCALTIME, 914 TokenType.LOCALTIMESTAMP, 915 TokenType.MERGE, 916 TokenType.NEXT, 917 TokenType.OFFSET, 918 TokenType.PRIMARY_KEY, 919 TokenType.RANGE, 920 TokenType.REPLACE, 921 TokenType.RLIKE, 922 TokenType.ROW, 923 TokenType.SESSION_USER, 924 TokenType.UNNEST, 925 TokenType.VAR, 926 TokenType.LEFT, 927 TokenType.RIGHT, 928 TokenType.SEQUENCE, 929 TokenType.DATE, 930 TokenType.DATETIME, 931 TokenType.TABLE, 932 TokenType.TIMESTAMP, 933 TokenType.TIMESTAMPTZ, 934 TokenType.TRUNCATE, 935 TokenType.UTC_DATE, 936 TokenType.UTC_TIME, 937 TokenType.UTC_TIMESTAMP, 938 TokenType.WINDOW, 939 TokenType.XOR, 940 *TYPE_TOKENS, 941 *SUBQUERY_PREDICATES, 942 } 943 944 CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 945 TokenType.AND: exp.And, 946 } 947 948 ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 949 TokenType.COLON_EQ: exp.PropertyEQ, 950 } 951 952 DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 953 TokenType.OR: exp.Or, 954 } 955 956 EQUALITY: t.ClassVar = { 957 TokenType.EQ: exp.EQ, 958 TokenType.NEQ: exp.NEQ, 959 TokenType.NULLSAFE_EQ: exp.NullSafeEQ, 960 } 961 962 COMPARISON: t.ClassVar = { 963 TokenType.GT: exp.GT, 964 TokenType.GTE: exp.GTE, 965 TokenType.LT: exp.LT, 966 TokenType.LTE: exp.LTE, 967 } 968 969 BITWISE: t.ClassVar = { 970 TokenType.AMP: exp.BitwiseAnd, 971 TokenType.CARET: exp.BitwiseXor, 972 TokenType.PIPE: exp.BitwiseOr, 973 } 974 975 TERM: t.ClassVar = { 976 TokenType.DASH: exp.Sub, 977 TokenType.PLUS: exp.Add, 978 TokenType.MOD: exp.Mod, 979 TokenType.COLLATE: exp.Collate, 980 } 981 982 FACTOR: t.ClassVar = { 983 TokenType.DIV: exp.IntDiv, 984 TokenType.LR_ARROW: exp.Distance, 985 TokenType.LLRR_ARROW: exp.DistanceNd, 986 TokenType.SLASH: exp.Div, 987 TokenType.STAR: exp.Mul, 988 } 989 990 EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {} 991 992 TIMES: t.ClassVar = { 993 TokenType.TIME, 994 TokenType.TIMETZ, 995 } 996 997 TIMESTAMPS: t.ClassVar = { 998 TokenType.TIMESTAMP, 999 TokenType.TIMESTAMPNTZ, 1000 TokenType.TIMESTAMPTZ, 1001 TokenType.TIMESTAMPLTZ, 1002 *TIMES, 1003 } 1004 1005 SET_OPERATIONS: t.ClassVar = { 1006 TokenType.UNION, 1007 TokenType.INTERSECT, 1008 TokenType.EXCEPT, 1009 } 1010 1011 JOIN_METHODS: t.ClassVar = { 1012 TokenType.ASOF, 1013 TokenType.NATURAL, 1014 TokenType.POSITIONAL, 1015 } 1016 1017 JOIN_SIDES: t.ClassVar = { 1018 TokenType.LEFT, 1019 TokenType.RIGHT, 1020 TokenType.FULL, 1021 } 1022 1023 JOIN_KINDS: t.ClassVar = { 1024 TokenType.ANTI, 1025 TokenType.CROSS, 1026 TokenType.INNER, 1027 TokenType.OUTER, 1028 TokenType.SEMI, 1029 TokenType.STRAIGHT_JOIN, 1030 } 1031 1032 JOIN_HINTS: t.ClassVar[set[str]] = set() 1033 1034 # Tokens that unambiguously end a table reference on the fast path 1035 TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset( 1036 { 1037 TokenType.COMMA, 1038 TokenType.GROUP_BY, 1039 TokenType.HAVING, 1040 TokenType.JOIN, 1041 TokenType.LIMIT, 1042 TokenType.ON, 1043 TokenType.ORDER_BY, 1044 TokenType.R_PAREN, 1045 TokenType.SEMICOLON, 1046 TokenType.SENTINEL, 1047 TokenType.WHERE, 1048 *SET_OPERATIONS, 1049 *JOIN_KINDS, 1050 *JOIN_METHODS, 1051 *JOIN_SIDES, 1052 } 1053 ) 1054 1055 LAMBDAS: t.ClassVar = { 1056 TokenType.ARROW: lambda self, expressions: self.expression( 1057 exp.Lambda( 1058 this=self._replace_lambda( 1059 self._parse_disjunction(), 1060 expressions, 1061 ), 1062 expressions=expressions, 1063 ) 1064 ), 1065 TokenType.FARROW: lambda self, expressions: self.expression( 1066 exp.Kwarg( 1067 this=exp.var(expressions[0].name), 1068 expression=self._parse_disjunction() or self._parse_select(), 1069 ) 1070 ), 1071 } 1072 1073 # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake 1074 TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False 1075 1076 LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN}) 1077 1078 COLUMN_OPERATORS: t.ClassVar = { 1079 TokenType.DOT: None, 1080 TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)), 1081 TokenType.DCOLON: lambda self, this, to: self.build_cast( 1082 strict=self.STRICT_CAST, this=this, to=to 1083 ), 1084 TokenType.ARROW: lambda self, this, path: self.expression( 1085 exp.JSONExtract( 1086 this=this, 1087 expression=self.dialect.to_json_path(path), 1088 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1089 ) 1090 ), 1091 TokenType.DARROW: lambda self, this, path: self.expression( 1092 exp.JSONExtractScalar( 1093 this=this, 1094 expression=self.dialect.to_json_path(path), 1095 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1096 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 1097 ) 1098 ), 1099 TokenType.HASH_ARROW: lambda self, this, path: self.expression( 1100 exp.JSONBExtract(this=this, expression=path) 1101 ), 1102 TokenType.DHASH_ARROW: lambda self, this, path: self.expression( 1103 exp.JSONBExtractScalar(this=this, expression=path) 1104 ), 1105 TokenType.PLACEHOLDER: lambda self, this, key: self.expression( 1106 exp.JSONBContains(this=this, expression=key) 1107 ), 1108 } 1109 1110 # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator" 1111 # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs). 1112 JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {} 1113 1114 CAST_COLUMN_OPERATORS: t.ClassVar = { 1115 TokenType.DOTCOLON, 1116 TokenType.DCOLON, 1117 } 1118 1119 EXPRESSION_PARSERS: t.ClassVar = { 1120 exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY), 1121 exp.Column: lambda self: self._parse_column(), 1122 exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()), 1123 exp.Condition: lambda self: self._parse_disjunction(), 1124 exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True), 1125 exp.Expr: lambda self: self._parse_expression(), 1126 exp.From: lambda self: self._parse_from(joins=True), 1127 exp.GrantPrincipal: lambda self: self._parse_grant_principal(), 1128 exp.GrantPrivilege: lambda self: self._parse_grant_privilege(), 1129 exp.Group: lambda self: self._parse_group(), 1130 exp.Having: lambda self: self._parse_having(), 1131 exp.Hint: lambda self: self._parse_hint_body(), 1132 exp.Identifier: lambda self: self._parse_id_var(), 1133 exp.Join: lambda self: self._parse_join(), 1134 exp.Lambda: lambda self: self._parse_lambda(), 1135 exp.Lateral: lambda self: self._parse_lateral(), 1136 exp.Limit: lambda self: self._parse_limit(), 1137 exp.Offset: lambda self: self._parse_offset(), 1138 exp.Order: lambda self: self._parse_order(), 1139 exp.Ordered: lambda self: self._parse_ordered(), 1140 exp.Properties: lambda self: self._parse_properties(), 1141 exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(), 1142 exp.Qualify: lambda self: self._parse_qualify(), 1143 exp.Returning: lambda self: self._parse_returning(), 1144 exp.Select: lambda self: self._parse_select(), 1145 exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY), 1146 exp.Table: lambda self: self._parse_table_parts(), 1147 exp.TableAlias: lambda self: self._parse_table_alias(), 1148 exp.Tuple: lambda self: self._parse_value(values=False), 1149 exp.Whens: lambda self: self._parse_when_matched(), 1150 exp.Where: lambda self: self._parse_where(), 1151 exp.Window: lambda self: self._parse_named_window(), 1152 exp.With: lambda self: self._parse_with(), 1153 } 1154 1155 STATEMENT_PARSERS: t.ClassVar = { 1156 TokenType.ALTER: lambda self: self._parse_alter(), 1157 TokenType.ANALYZE: lambda self: self._parse_analyze(), 1158 TokenType.BEGIN: lambda self: self._parse_transaction(), 1159 TokenType.CACHE: lambda self: self._parse_cache(), 1160 TokenType.COMMENT: lambda self: self._parse_comment(), 1161 TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(), 1162 TokenType.COPY: lambda self: self._parse_copy(), 1163 TokenType.CREATE: lambda self: self._parse_create(), 1164 TokenType.DECLARE: lambda self: self._parse_declare(), 1165 TokenType.DELETE: lambda self: self._parse_delete(), 1166 TokenType.DESC: lambda self: self._parse_describe(), 1167 TokenType.DESCRIBE: lambda self: self._parse_describe(), 1168 TokenType.DROP: lambda self: self._parse_drop(), 1169 TokenType.GRANT: lambda self: self._parse_grant(), 1170 TokenType.REVOKE: lambda self: self._parse_revoke(), 1171 TokenType.INSERT: lambda self: self._parse_insert(), 1172 TokenType.KILL: lambda self: self._parse_kill(), 1173 TokenType.LOAD: lambda self: self._parse_load(), 1174 TokenType.MERGE: lambda self: self._parse_merge(), 1175 TokenType.PIVOT: lambda self: self._parse_simplified_pivot(), 1176 TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())), 1177 TokenType.REFRESH: lambda self: self._parse_refresh(), 1178 TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(), 1179 TokenType.SET: lambda self: self._parse_set(), 1180 TokenType.TRUNCATE: lambda self: self._parse_truncate_table(), 1181 TokenType.UNCACHE: lambda self: self._parse_uncache(), 1182 TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True), 1183 TokenType.UPDATE: lambda self: self._parse_update(), 1184 TokenType.USE: lambda self: self._parse_use(), 1185 TokenType.SEMICOLON: lambda self: exp.Semicolon(), 1186 } 1187 1188 UNARY_PARSERS: t.ClassVar = { 1189 TokenType.PLUS: lambda self: self._parse_unary(), # Unary + is handled as a no-op 1190 TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())), 1191 TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())), 1192 TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())), 1193 TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())), 1194 TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())), 1195 } 1196 1197 STRING_PARSERS: t.ClassVar = { 1198 TokenType.HEREDOC_STRING: lambda self, token: self.expression( 1199 exp.RawString(this=token.text), token 1200 ), 1201 TokenType.NATIONAL_STRING: lambda self, token: self.expression( 1202 exp.National(this=token.text), token 1203 ), 1204 TokenType.RAW_STRING: lambda self, token: self.expression( 1205 exp.RawString(this=token.text), token 1206 ), 1207 TokenType.STRING: lambda self, token: self.expression( 1208 exp.Literal(this=token.text, is_string=True), token 1209 ), 1210 TokenType.UNICODE_STRING: lambda self, token: self.expression( 1211 exp.UnicodeString( 1212 this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string() 1213 ), 1214 token, 1215 ), 1216 } 1217 1218 NUMERIC_PARSERS: t.ClassVar = { 1219 TokenType.BIT_STRING: lambda self, token: self.expression( 1220 exp.BitString(this=token.text), token 1221 ), 1222 TokenType.BYTE_STRING: lambda self, token: self.expression( 1223 exp.ByteString( 1224 this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None 1225 ), 1226 token, 1227 ), 1228 TokenType.HEX_STRING: lambda self, token: self.expression( 1229 exp.HexString( 1230 this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None 1231 ), 1232 token, 1233 ), 1234 TokenType.NUMBER: lambda self, token: self.expression( 1235 exp.Literal(this=token.text, is_string=False), token 1236 ), 1237 } 1238 1239 PRIMARY_PARSERS: t.ClassVar = { 1240 **STRING_PARSERS, 1241 **NUMERIC_PARSERS, 1242 TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token), 1243 TokenType.NULL: lambda self, _: self.expression(exp.Null()), 1244 TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)), 1245 TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)), 1246 TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(), 1247 TokenType.STAR: lambda self, _: self._parse_star_ops(), 1248 } 1249 1250 PLACEHOLDER_PARSERS: t.ClassVar = { 1251 TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()), 1252 TokenType.PARAMETER: lambda self: self._parse_parameter(), 1253 TokenType.COLON: lambda self: ( 1254 self.expression(exp.Placeholder(this=self._prev.text)) 1255 if self._match_set(self.COLON_PLACEHOLDER_TOKENS) 1256 else None 1257 ), 1258 } 1259 1260 RANGE_PARSERS: t.ClassVar = { 1261 TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll), 1262 TokenType.BETWEEN: lambda self, this: self._parse_between(this), 1263 TokenType.GLOB: binary_range_parser(exp.Glob), 1264 TokenType.ILIKE: binary_range_parser(exp.ILike), 1265 TokenType.IN: lambda self, this: self._parse_in(this), 1266 TokenType.IRLIKE: binary_range_parser(exp.RegexpILike), 1267 TokenType.IS: lambda self, this: self._parse_is(this), 1268 TokenType.LIKE: binary_range_parser(exp.Like), 1269 TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy), 1270 TokenType.OVERLAPS: binary_range_parser(exp.Overlaps), 1271 TokenType.RLIKE: binary_range_parser(exp.RegexpLike), 1272 TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo), 1273 TokenType.FOR: lambda self, this: self._parse_comprehension(this), 1274 TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys), 1275 TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys), 1276 TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath), 1277 TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists), 1278 TokenType.ADJACENT: binary_range_parser(exp.Adjacent), 1279 TokenType.OPERATOR: lambda self, this: self._parse_operator(this), 1280 TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft), 1281 TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight), 1282 } 1283 1284 PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = { 1285 "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query), 1286 "AS": lambda self, query: self._build_pipe_cte( 1287 query, [exp.Star()], self._parse_table_alias() 1288 ), 1289 "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False), 1290 "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query), 1291 "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query), 1292 "ORDER BY": lambda self, query: query.order_by( 1293 self._parse_order(), append=False, copy=False 1294 ), 1295 "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1296 "SELECT": lambda self, query: self._parse_pipe_syntax_select(query), 1297 "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query), 1298 "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1299 "WHERE": lambda self, query: query.where(self._parse_where(), copy=False), 1300 } 1301 1302 PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1303 "ALLOWED_VALUES": lambda self: self.expression( 1304 exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary)) 1305 ), 1306 "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty), 1307 "AUTO": lambda self: self._parse_auto_property(), 1308 "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty), 1309 "BACKUP": lambda self: self.expression( 1310 exp.BackupProperty(this=self._parse_var(any_token=True)) 1311 ), 1312 "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(), 1313 "CALLED": lambda self: self._parse_called_on_null_input_property(), 1314 "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs), 1315 "CHECKSUM": lambda self: self._parse_checksum(), 1316 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1317 "CLUSTERED": lambda self: self._parse_clustered_by(), 1318 "COLLATE": lambda self, **kwargs: self._parse_property_assignment( 1319 exp.CollateProperty, **kwargs 1320 ), 1321 "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty), 1322 "CONTAINS": lambda self: self._parse_contains_property(), 1323 "COPY": lambda self: self._parse_copy_property(), 1324 "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs), 1325 "DATA_DELETION": lambda self: self._parse_data_deletion_property(), 1326 "DEFINER": lambda self: self._parse_definer(), 1327 "DETERMINISTIC": lambda self: self.expression( 1328 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1329 ), 1330 "DISTRIBUTED": lambda self: self._parse_distributed_property(), 1331 "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty), 1332 "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()), 1333 "DISTKEY": lambda self: self._parse_distkey(), 1334 "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty), 1335 "EMPTY": lambda self: self.expression(exp.EmptyProperty()), 1336 "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty), 1337 "ENVIRONMENT": lambda self: self.expression( 1338 exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment)) 1339 ), 1340 "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty), 1341 "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty), 1342 "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()), 1343 "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs), 1344 "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1345 "FREESPACE": lambda self: self._parse_freespace(), 1346 "GLOBAL": lambda self: self.expression(exp.GlobalProperty()), 1347 "HEAP": lambda self: self.expression(exp.HeapProperty()), 1348 "ICEBERG": lambda self: self.expression(exp.IcebergProperty()), 1349 "IMMUTABLE": lambda self: self.expression( 1350 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1351 ), 1352 "INHERITS": lambda self: self.expression( 1353 exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table)) 1354 ), 1355 "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())), 1356 "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs), 1357 "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty), 1358 "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"), 1359 "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"), 1360 "LIKE": lambda self: self._parse_create_like(), 1361 "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty), 1362 "LOCK": lambda self: self._parse_locking(), 1363 "LOCKING": lambda self: self._parse_locking(), 1364 "LOG": lambda self, **kwargs: self._parse_log(**kwargs), 1365 "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()), 1366 "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs), 1367 "MODIFIES": lambda self: self._parse_modifies_property(), 1368 "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)), 1369 "NO": lambda self: self._parse_no_property(), 1370 "ON": lambda self: self._parse_on_property(), 1371 "ORDER BY": lambda self: self._parse_order(skip_order_token=True), 1372 "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())), 1373 "PARTITION": lambda self: self._parse_partitioned_of(), 1374 "PARTITION BY": lambda self: self._parse_partitioned_by(), 1375 "PARTITIONED BY": lambda self: self._parse_partitioned_by(), 1376 "PARTITIONED_BY": lambda self: self._parse_partitioned_by(), 1377 "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True), 1378 "RANGE": lambda self: self._parse_dict_range(this="RANGE"), 1379 "READS": lambda self: self._parse_reads_property(), 1380 "REMOTE": lambda self: self._parse_remote_with_connection(), 1381 "RETURNS": lambda self: self._parse_returns(), 1382 "STRICT": lambda self: self.expression(exp.StrictProperty()), 1383 "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()), 1384 "ROW": lambda self: self._parse_row(), 1385 "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty), 1386 "SAMPLE": lambda self: self.expression( 1387 exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise()) 1388 ), 1389 "SECURE": lambda self: self.expression(exp.SecureProperty()), 1390 "SECURITY": lambda self: self._parse_sql_security(), 1391 "SQL SECURITY": lambda self: self._parse_sql_security(), 1392 "SET": lambda self: self.expression(exp.SetProperty(multi=False)), 1393 "SETTINGS": lambda self: self._parse_settings_property(), 1394 "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty), 1395 "SORTKEY": lambda self: self._parse_sortkey(), 1396 "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"), 1397 "STABLE": lambda self: self.expression( 1398 exp.StabilityProperty(this=exp.Literal.string("STABLE")) 1399 ), 1400 "STORED": lambda self: self._parse_stored(), 1401 "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(), 1402 "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(), 1403 "TEMP": lambda self: self.expression(exp.TemporaryProperty()), 1404 "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()), 1405 "TO": lambda self: self._parse_to_table(), 1406 "TRANSIENT": lambda self: self.expression(exp.TransientProperty()), 1407 "TRANSFORM": lambda self: self.expression( 1408 exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression)) 1409 ), 1410 "TTL": lambda self: self._parse_ttl(), 1411 "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1412 "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()), 1413 "VOLATILE": lambda self: self._parse_volatile_property(), 1414 "WITH": lambda self: self._parse_with_property(), 1415 } 1416 1417 CONSTRAINT_PARSERS: t.ClassVar = { 1418 "AUTOINCREMENT": lambda self: self._parse_auto_increment(), 1419 "AUTO_INCREMENT": lambda self: self._parse_auto_increment(), 1420 "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)), 1421 "CHECK": lambda self: self._parse_check_constraint(), 1422 "COLLATE": lambda self: self.expression( 1423 exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column()) 1424 ), 1425 "COMMENT": lambda self: self.expression( 1426 exp.CommentColumnConstraint(this=self._parse_string()) 1427 ), 1428 "COMPRESS": lambda self: self._parse_compress(), 1429 "CLUSTERED": lambda self: self.expression( 1430 exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1431 ), 1432 "NONCLUSTERED": lambda self: self.expression( 1433 exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1434 ), 1435 "DEFAULT": lambda self: self.expression( 1436 exp.DefaultColumnConstraint(this=self._parse_bitwise()) 1437 ), 1438 "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())), 1439 "EPHEMERAL": lambda self: self.expression( 1440 exp.EphemeralColumnConstraint(this=self._parse_bitwise()) 1441 ), 1442 "EXCLUDE": lambda self: self.expression( 1443 exp.ExcludeColumnConstraint(this=self._parse_index_params()) 1444 ), 1445 "FOREIGN KEY": lambda self: self._parse_foreign_key(), 1446 "FORMAT": lambda self: self.expression( 1447 exp.DateFormatColumnConstraint(this=self._parse_var_or_string()) 1448 ), 1449 "GENERATED": lambda self: self._parse_generated_as_identity(), 1450 "IDENTITY": lambda self: self._parse_auto_increment(), 1451 "INLINE": lambda self: self._parse_inline(), 1452 "LIKE": lambda self: self._parse_create_like(), 1453 "NOT": lambda self: self._parse_not_constraint(), 1454 "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)), 1455 "ON": lambda self: ( 1456 ( 1457 self._match(TokenType.UPDATE) 1458 and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function())) 1459 ) 1460 or self.expression(exp.OnProperty(this=self._parse_id_var())) 1461 ), 1462 "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())), 1463 "PERIOD": lambda self: self._parse_period_for_system_time(), 1464 "PRIMARY KEY": lambda self: self._parse_primary_key(), 1465 "REFERENCES": lambda self: self._parse_references(match=False), 1466 "TITLE": lambda self: self.expression( 1467 exp.TitleColumnConstraint(this=self._parse_var_or_string()) 1468 ), 1469 "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])), 1470 "UNIQUE": lambda self: self._parse_unique(), 1471 "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()), 1472 "WITH": lambda self: self.expression( 1473 exp.Properties(expressions=self._parse_wrapped_properties()) 1474 ), 1475 "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1476 "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1477 } 1478 1479 def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None: 1480 if not self._match(TokenType.L_PAREN, advance=False): 1481 # Partitioning by bucket or truncate follows the syntax: 1482 # PARTITION BY (BUCKET(..) | TRUNCATE(..)) 1483 # If we don't have parenthesis after each keyword, we should instead parse this as an identifier 1484 self._retreat(self._index - 1) 1485 return None 1486 1487 klass = ( 1488 exp.PartitionedByBucket 1489 if self._prev.text.upper() == "BUCKET" 1490 else exp.PartitionByTruncate 1491 ) 1492 1493 args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column()) 1494 this, expression = seq_get(args, 0), seq_get(args, 1) 1495 1496 if isinstance(this, exp.Literal): 1497 # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order 1498 # - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)` 1499 # - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)` 1500 # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)` 1501 # 1502 # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning 1503 # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties 1504 this, expression = expression, this 1505 1506 return self.expression(klass(this=this, expression=expression)) 1507 1508 ALTER_PARSERS: t.ClassVar = { 1509 "ADD": lambda self: self._parse_alter_table_add(), 1510 "AS": lambda self: self._parse_select(), 1511 "ALTER": lambda self: self._parse_alter_table_alter(), 1512 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1513 "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())), 1514 "DROP": lambda self: self._parse_alter_table_drop(), 1515 "RENAME": lambda self: self._parse_alter_table_rename(), 1516 "SET": lambda self: self._parse_alter_table_set(), 1517 "SWAP": lambda self: self.expression( 1518 exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True)) 1519 ), 1520 } 1521 1522 ALTER_ALTER_PARSERS: t.ClassVar = { 1523 "DISTKEY": lambda self: self._parse_alter_diststyle(), 1524 "DISTSTYLE": lambda self: self._parse_alter_diststyle(), 1525 "SORTKEY": lambda self: self._parse_alter_sortkey(), 1526 "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True), 1527 } 1528 1529 SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = { 1530 "CHECK", 1531 "EXCLUDE", 1532 "FOREIGN KEY", 1533 "LIKE", 1534 "PERIOD", 1535 "PRIMARY KEY", 1536 "UNIQUE", 1537 "BUCKET", 1538 "TRUNCATE", 1539 } 1540 1541 NO_PAREN_FUNCTION_PARSERS: t.ClassVar = { 1542 "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())), 1543 "CASE": lambda self: self._parse_case(), 1544 "CONNECT_BY_ROOT": lambda self: self.expression( 1545 exp.ConnectByRoot(this=self._parse_column()) 1546 ), 1547 "IF": lambda self: self._parse_if(), 1548 } 1549 1550 INVALID_FUNC_NAME_TOKENS: t.ClassVar = { 1551 TokenType.IDENTIFIER, 1552 TokenType.STRING, 1553 } 1554 1555 FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"} 1556 1557 KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice) 1558 1559 FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1560 **{ 1561 name: lambda self: self._parse_distinct_arg_function(exp.ArgMax) 1562 for name in exp.ArgMax.sql_names() 1563 }, 1564 **{ 1565 name: lambda self: self._parse_distinct_arg_function(exp.ArgMin) 1566 for name in exp.ArgMin.sql_names() 1567 }, 1568 "CAST": lambda self: self._parse_cast(self.STRICT_CAST), 1569 "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil), 1570 "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST), 1571 "CHAR": lambda self: self._parse_char(), 1572 "CHR": lambda self: self._parse_char(), 1573 "DECODE": lambda self: self._parse_decode(), 1574 "EXTRACT": lambda self: self._parse_extract(), 1575 "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor), 1576 "GAP_FILL": lambda self: self._parse_gap_fill(), 1577 "INITCAP": lambda self: self._parse_initcap(), 1578 "JSON_OBJECT": lambda self: self._parse_json_object(), 1579 "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True), 1580 "JSON_TABLE": lambda self: self._parse_json_table(), 1581 "MATCH": lambda self: self._parse_match_against(), 1582 "NORMALIZE": lambda self: self._parse_normalize(), 1583 "OPENJSON": lambda self: self._parse_open_json(), 1584 "OVERLAY": lambda self: self._parse_overlay(), 1585 "POSITION": lambda self: self._parse_position(), 1586 "SAFE_CAST": lambda self: self._parse_cast(False, safe=True), 1587 "STRING_AGG": lambda self: self._parse_string_agg(), 1588 "SUBSTRING": lambda self: self._parse_substring(), 1589 "TRIM": lambda self: self._parse_trim(), 1590 "TRY_CAST": lambda self: self._parse_cast(False, safe=True), 1591 "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True), 1592 "XMLELEMENT": lambda self: self._parse_xml_element(), 1593 "XMLTABLE": lambda self: self._parse_xml_table(), 1594 } 1595 1596 QUERY_MODIFIER_PARSERS: t.ClassVar = { 1597 TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()), 1598 TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()), 1599 TokenType.WHERE: lambda self: ("where", self._parse_where()), 1600 TokenType.GROUP_BY: lambda self: ("group", self._parse_group()), 1601 TokenType.HAVING: lambda self: ("having", self._parse_having()), 1602 TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()), 1603 TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()), 1604 TokenType.ORDER_BY: lambda self: ("order", self._parse_order()), 1605 TokenType.LIMIT: lambda self: ("limit", self._parse_limit()), 1606 TokenType.FETCH: lambda self: ("limit", self._parse_limit()), 1607 TokenType.OFFSET: lambda self: ("offset", self._parse_offset()), 1608 TokenType.FOR: lambda self: ("locks", self._parse_locks()), 1609 TokenType.LOCK: lambda self: ("locks", self._parse_locks()), 1610 TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1611 TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1612 TokenType.CLUSTER_BY: lambda self: ( 1613 "cluster", 1614 self._parse_cluster(), 1615 ), 1616 TokenType.DISTRIBUTE_BY: lambda self: ( 1617 "distribute", 1618 self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY), 1619 ), 1620 TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)), 1621 TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)), 1622 } 1623 QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS) 1624 1625 SET_PARSERS: t.ClassVar = { 1626 "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"), 1627 "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"), 1628 "SESSION": lambda self: self._parse_set_item_assignment("SESSION"), 1629 "TRANSACTION": lambda self: self._parse_set_transaction(), 1630 } 1631 1632 SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {} 1633 1634 TYPE_LITERAL_PARSERS: t.ClassVar = { 1635 exp.DType.JSON: lambda self, this, _: self.expression(exp.ParseJSON(this=this)), 1636 } 1637 1638 TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {} 1639 1640 DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN} 1641 1642 PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE} 1643 1644 TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"} 1645 TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = { 1646 "ISOLATION": ( 1647 ("LEVEL", "REPEATABLE", "READ"), 1648 ("LEVEL", "READ", "COMMITTED"), 1649 ("LEVEL", "READ", "UNCOMITTED"), 1650 ("LEVEL", "SERIALIZABLE"), 1651 ), 1652 "READ": ("WRITE", "ONLY"), 1653 } 1654 1655 CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = { 1656 **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()), 1657 "DO": ("NOTHING", "UPDATE"), 1658 } 1659 1660 TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = { 1661 "INSTEAD": (("OF",),), 1662 "BEFORE": tuple(), 1663 "AFTER": tuple(), 1664 } 1665 1666 TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = { 1667 "NOT": (("DEFERRABLE",),), 1668 "DEFERRABLE": tuple(), 1669 } 1670 1671 CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = { 1672 "SCALE": ("EXTEND", "NOEXTEND"), 1673 "SHARD": ("EXTEND", "NOEXTEND"), 1674 "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"), 1675 **dict.fromkeys( 1676 ( 1677 "SESSION", 1678 "GLOBAL", 1679 "KEEP", 1680 "NOKEEP", 1681 "ORDER", 1682 "NOORDER", 1683 "NOCACHE", 1684 "CYCLE", 1685 "NOCYCLE", 1686 "NOMINVALUE", 1687 "NOMAXVALUE", 1688 "NOSCALE", 1689 "NOSHARD", 1690 ), 1691 tuple(), 1692 ), 1693 } 1694 1695 ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")} 1696 1697 USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1698 ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple() 1699 ) 1700 1701 CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",)) 1702 1703 SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1704 "TYPE": ("EVOLUTION",), 1705 **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()), 1706 } 1707 1708 PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {} 1709 1710 EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1711 ("CALLER", "SELF", "OWNER"), tuple() 1712 ) 1713 1714 KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1715 "NOT": ("ENFORCED",), 1716 "MATCH": ( 1717 "FULL", 1718 "PARTIAL", 1719 "SIMPLE", 1720 ), 1721 "INITIALLY": ("DEFERRED", "IMMEDIATE"), 1722 "USING": ( 1723 "BTREE", 1724 "HASH", 1725 ), 1726 **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()), 1727 } 1728 1729 WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1730 "NO": ("OTHERS",), 1731 "CURRENT": ("ROW",), 1732 **dict.fromkeys(("GROUP", "TIES"), tuple()), 1733 } 1734 1735 INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"} 1736 1737 CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"} 1738 # Time travel clause prefixes, mapped to whether they pin a timestamp or a version 1739 VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = { 1740 ("FOR", "SYSTEM_TIME"): "TIMESTAMP", 1741 ("FOR", "SYSTEM", "TIME"): "TIMESTAMP", 1742 ("FOR", "TIMESTAMP"): "TIMESTAMP", 1743 ("FOR", "VERSION"): "VERSION", 1744 ("TIMESTAMP", "AS", "OF"): "TIMESTAMP", 1745 ("VERSION", "AS", "OF"): "VERSION", 1746 } 1747 1748 HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"} 1749 HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"} 1750 1751 OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"} 1752 1753 OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN} 1754 1755 TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE} 1756 1757 VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"} 1758 1759 WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS} 1760 WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER} 1761 WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"} 1762 1763 JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS} 1764 1765 FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT} 1766 1767 ADD_CONSTRAINT_TOKENS: t.ClassVar = { 1768 TokenType.CONSTRAINT, 1769 TokenType.FOREIGN_KEY, 1770 TokenType.INDEX, 1771 TokenType.KEY, 1772 TokenType.PRIMARY_KEY, 1773 TokenType.UNIQUE, 1774 } 1775 1776 DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT} 1777 1778 UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS 1779 1780 SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT} 1781 1782 COPY_INTO_VARLEN_OPTIONS: t.ClassVar = { 1783 "FILE_FORMAT", 1784 "COPY_OPTIONS", 1785 "FORMAT_OPTIONS", 1786 "CREDENTIAL", 1787 } 1788 1789 IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"} 1790 1791 ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {} 1792 1793 ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"} 1794 1795 PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN} 1796 1797 # The style options for the DESCRIBE statement 1798 DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"} 1799 1800 SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"} 1801 1802 # The style options for the ANALYZE statement 1803 ANALYZE_STYLES: t.ClassVar = { 1804 "BUFFER_USAGE_LIMIT", 1805 "FULL", 1806 "LOCAL", 1807 "NO_WRITE_TO_BINLOG", 1808 "SAMPLE", 1809 "SKIP_LOCKED", 1810 "VERBOSE", 1811 } 1812 1813 ANALYZE_EXPRESSION_PARSERS: t.ClassVar = { 1814 "ALL": lambda self: self._parse_analyze_columns(), 1815 "COMPUTE": lambda self: self._parse_analyze_statistics(), 1816 "DELETE": lambda self: self._parse_analyze_delete(), 1817 "DROP": lambda self: self._parse_analyze_histogram(), 1818 "ESTIMATE": lambda self: self._parse_analyze_statistics(), 1819 "LIST": lambda self: self._parse_analyze_list(), 1820 "PREDICATE": lambda self: self._parse_analyze_columns(), 1821 "UPDATE": lambda self: self._parse_analyze_histogram(), 1822 "VALIDATE": lambda self: self._parse_analyze_validate(), 1823 } 1824 1825 PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"} 1826 1827 AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET) 1828 1829 OPERATION_MODIFIERS: t.ClassVar[set[str]] = set() 1830 1831 RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"} 1832 1833 SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"} 1834 1835 MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values) 1836 1837 STRICT_CAST: t.ClassVar = True 1838 1839 PREFIXED_PIVOT_COLUMNS: t.ClassVar = False 1840 IDENTIFY_PIVOT_STRINGS: t.ClassVar = False 1841 # Whether an UNPIVOT outputs its value column(s) before the name column 1842 UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False 1843 # Controls when an aggregation's name is included in a pivoted column's name: 1844 # "agg_name_if_aliased" - only for aggregations that carry an explicit alias 1845 # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations 1846 # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only) 1847 PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased" 1848 1849 LOG_DEFAULTS_TO_LN: t.ClassVar = False 1850 1851 # Whether the table sample clause expects CSV syntax 1852 TABLESAMPLE_CSV: t.ClassVar = False 1853 1854 # The default method used for table sampling 1855 DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None 1856 1857 # Whether the SET command needs a delimiter (e.g. "=") for assignments 1858 SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True 1859 1860 # Whether the TRIM function expects the characters to trim as its first argument 1861 TRIM_PATTERN_FIRST: t.ClassVar = False 1862 1863 # Whether string aliases are supported `SELECT COUNT(*) 'count'` 1864 STRING_ALIASES: t.ClassVar = False 1865 1866 # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand) 1867 MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True 1868 SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset"} 1869 1870 # Whether to parse IF statements that aren't followed by a left parenthesis as commands 1871 NO_PAREN_IF_COMMANDS: t.ClassVar = True 1872 1873 # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres) 1874 JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False 1875 1876 # Whether the `:` operator is used to extract a value from a VARIANT column 1877 COLON_IS_VARIANT_EXTRACT: t.ClassVar = False 1878 1879 # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged 1880 # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks) 1881 COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True 1882 1883 # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause. 1884 # If this is True and '(' is not found, the keyword will be treated as an identifier 1885 VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True 1886 1887 # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift) 1888 SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False 1889 1890 # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS 1891 INTERVAL_SPANS: t.ClassVar = True 1892 1893 # Whether a PARTITION clause can follow a table reference 1894 SUPPORTS_PARTITION_SELECTION: t.ClassVar = False 1895 1896 # Whether the `name AS expr` schema/column constraint requires parentheses around `expr` 1897 WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True 1898 1899 # Whether the 'AS' keyword is optional in the CTE definition syntax 1900 OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True 1901 1902 # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword 1903 ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True 1904 1905 # Whether Alter statements are allowed to contain Partition specifications 1906 ALTER_TABLE_PARTITIONS: t.ClassVar = False 1907 1908 # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree. 1909 # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is 1910 # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such 1911 # as BigQuery, where all joins have the same precedence. 1912 JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False 1913 1914 # Whether TIMESTAMP <literal> can produce a zone-aware timestamp 1915 ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False 1916 1917 # Whether map literals support arbitrary expressions as keys. 1918 # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB). 1919 # When False, keys are typically restricted to identifiers. 1920 MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False 1921 1922 # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this 1923 # is true for Snowflake but not for BigQuery which can also process strings 1924 JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False 1925 1926 # Dialects like Databricks support JOINS without join criteria 1927 # Adding an ON TRUE, makes transpilation semantically correct for other dialects 1928 ADD_JOIN_ON_TRUE: t.ClassVar = False 1929 1930 # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]' 1931 # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND` 1932 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False 1933 1934 # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them 1935 # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation. 1936 ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False 1937 1938 SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS) 1939 SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS) 1940 1941 def __init__( 1942 self, 1943 error_level: ErrorLevel | None = None, 1944 error_message_context: int = 100, 1945 max_errors: int = 3, 1946 max_nodes: int = -1, 1947 dialect: DialectType = None, 1948 ): 1949 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 1950 self.error_message_context: int = error_message_context 1951 self.max_errors: int = max_errors 1952 self.max_nodes: int = max_nodes 1953 self.dialect: t.Any = _resolve_dialect(dialect) 1954 self.sql: str = "" 1955 self.errors: list[ParseError] = [] 1956 self._tokens: list[Token] = [] 1957 self._tokens_size: i64 = 0 1958 self._index: i64 = 0 1959 self._curr: Token = SENTINEL_NONE 1960 self._next: Token = SENTINEL_NONE 1961 self._prev: Token = SENTINEL_NONE 1962 self._prev_comments: list[str] = [] 1963 self._pipe_cte_counter: int = 0 1964 self._chunks: list[list[Token]] = [] 1965 self._chunk_index: i64 = 0 1966 self._node_count: int = 0 1967 1968 def reset(self) -> None: 1969 self.sql = "" 1970 self.errors = [] 1971 self._tokens = [] 1972 self._tokens_size = 0 1973 self._index = 0 1974 self._curr = SENTINEL_NONE 1975 self._next = SENTINEL_NONE 1976 self._prev = SENTINEL_NONE 1977 self._prev_comments = [] 1978 self._pipe_cte_counter = 0 1979 self._chunks = [] 1980 self._chunk_index = 0 1981 self._node_count = 0 1982 1983 def _advance(self, times: i64 = 1) -> None: 1984 index = self._index + times 1985 self._index = index 1986 tokens = self._tokens 1987 size = self._tokens_size 1988 self._curr = tokens[index] if index < size else SENTINEL_NONE 1989 self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE 1990 1991 if index > 0: 1992 prev = tokens[index - 1] 1993 self._prev = prev 1994 self._prev_comments = prev.comments 1995 else: 1996 self._prev = SENTINEL_NONE 1997 self._prev_comments = [] 1998 1999 def _advance_chunk(self) -> None: 2000 self._index = -1 2001 self._tokens = self._chunks[self._chunk_index] 2002 self._tokens_size = i64(len(self._tokens)) 2003 self._chunk_index += 1 2004 self._advance() 2005 2006 def _retreat(self, index: i64) -> None: 2007 if index != self._index: 2008 self._advance(index - self._index) 2009 2010 def _add_comments(self, expression: exp.Expr | None) -> None: 2011 if expression and self._prev_comments: 2012 expression.add_comments(self._prev_comments) 2013 self._prev_comments = [] 2014 2015 def _match( 2016 self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None 2017 ) -> bool: 2018 if self._curr.token_type == token_type: 2019 if advance: 2020 self._advance() 2021 self._add_comments(expression) 2022 return True 2023 return False 2024 2025 def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool: 2026 if self._curr.token_type in types: 2027 if advance: 2028 self._advance() 2029 return True 2030 return False 2031 2032 def _match_pair( 2033 self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True 2034 ) -> bool: 2035 if self._curr.token_type == token_type_a and self._next.token_type == token_type_b: 2036 if advance: 2037 self._advance(2) 2038 return True 2039 return False 2040 2041 def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool: 2042 if ( 2043 self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS 2044 and self._curr.text.upper() in texts 2045 ): 2046 if advance: 2047 self._advance() 2048 return True 2049 return False 2050 2051 def _match_text_seq(self, *texts: str, advance: bool = True) -> bool: 2052 index = self._index 2053 excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS 2054 for text in texts: 2055 if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text: 2056 self._advance() 2057 else: 2058 self._retreat(index) 2059 return False 2060 2061 if not advance: 2062 self._retreat(index) 2063 2064 return True 2065 2066 def _is_connected(self) -> bool: 2067 prev = self._prev 2068 curr = self._curr 2069 return bool(prev and curr and prev.end + 1 == curr.start) 2070 2071 def _find_sql(self, start: Token, end: Token) -> str: 2072 return self.sql[start.start : end.end + 1] 2073 2074 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2075 token = token or self._curr or self._prev or Token.string("") 2076 formatted_sql, start_context, highlight, end_context = highlight_sql( 2077 sql=self.sql, 2078 positions=[(token.start, token.end)], 2079 context_length=self.error_message_context, 2080 ) 2081 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2082 2083 error = ParseError.new( 2084 formatted_message, 2085 description=message, 2086 line=token.line, 2087 col=token.col, 2088 start_context=start_context, 2089 highlight=highlight, 2090 end_context=end_context, 2091 ) 2092 2093 if self.error_level == ErrorLevel.IMMEDIATE: 2094 raise error 2095 2096 self.errors.append(error) 2097 2098 def validate_expression(self, expression: E, args: list | None = None) -> E: 2099 if self.max_nodes > -1: 2100 self._node_count += 1 2101 if self._node_count > self.max_nodes: 2102 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2103 if self.error_level != ErrorLevel.IGNORE: 2104 for error_message in expression.error_messages(args): 2105 self.raise_error(error_message) 2106 return expression 2107 2108 def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None: 2109 index = self._index 2110 error_level = self.error_level 2111 this: T | None = None 2112 2113 self.error_level = ErrorLevel.IMMEDIATE 2114 try: 2115 this = parse_method() 2116 except ParseError: 2117 this = None 2118 finally: 2119 if not this or retreat: 2120 self._retreat(index) 2121 self.error_level = error_level 2122 2123 return this 2124 2125 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2126 """ 2127 Parses a list of tokens and returns a list of syntax trees, one tree 2128 per parsed SQL statement. 2129 2130 Args: 2131 raw_tokens: The list of tokens. 2132 sql: The original SQL string. 2133 2134 Returns: 2135 The list of the produced syntax trees. 2136 """ 2137 return self._parse( 2138 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2139 ) 2140 2141 def parse_into( 2142 self, 2143 expression_types: exp.IntoType, 2144 raw_tokens: list[Token], 2145 sql: str | None = None, 2146 ) -> list[exp.Expr | None]: 2147 """ 2148 Parses a list of tokens into a given Expr type. If a collection of Expr 2149 types is given instead, this method will try to parse the token list into each one 2150 of them, stopping at the first for which the parsing succeeds. 2151 2152 Args: 2153 expression_types: The expression type(s) to try and parse the token list into. 2154 raw_tokens: The list of tokens. 2155 sql: The original SQL string, used to produce helpful debug messages. 2156 2157 Returns: 2158 The target Expr. 2159 """ 2160 errors = [] 2161 for expression_type in ensure_list(expression_types): 2162 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2163 if not parser: 2164 raise TypeError(f"No parser registered for {expression_type}") 2165 2166 try: 2167 return self._parse(parser, raw_tokens, sql) 2168 except ParseError as e: 2169 e.errors[0]["into_expression"] = expression_type 2170 errors.append(e) 2171 2172 raise ParseError( 2173 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2174 errors=merge_errors(errors), 2175 ) from errors[-1] 2176 2177 def check_errors(self) -> None: 2178 """Logs or raises any found errors, depending on the chosen error level setting.""" 2179 if self.error_level == ErrorLevel.WARN: 2180 for error in self.errors: 2181 logger.error(str(error)) 2182 elif self.error_level == ErrorLevel.RAISE and self.errors: 2183 raise ParseError( 2184 concat_messages(self.errors, self.max_errors), 2185 errors=merge_errors(self.errors), 2186 ) 2187 2188 def expression( 2189 self, 2190 instance: E, 2191 token: Token | None = None, 2192 comments: list[str] | None = None, 2193 ) -> E: 2194 if token: 2195 instance.update_positions(token) 2196 instance.add_comments(comments) if comments else self._add_comments(instance) 2197 if not instance.is_primitive: 2198 instance = self.validate_expression(instance) 2199 return instance 2200 2201 def _parse_batch_statements( 2202 self, 2203 parse_method: t.Callable[[Parser], exp.Expr | None], 2204 sep_first_statement: bool = True, 2205 ) -> list[exp.Expr | None]: 2206 expressions = [] 2207 2208 # Chunkification binds if/while statements with the first statement of the body 2209 if sep_first_statement: 2210 self._match(TokenType.BEGIN) 2211 expressions.append(parse_method(self)) 2212 2213 chunks_length = len(self._chunks) 2214 while self._chunk_index < chunks_length: 2215 self._advance_chunk() 2216 2217 if self._match(TokenType.ELSE, advance=False): 2218 return expressions 2219 2220 if expressions and not self._next and self._match(TokenType.END): 2221 expressions.append(exp.EndStatement()) 2222 continue 2223 2224 expressions.append(parse_method(self)) 2225 2226 if self._index < self._tokens_size: 2227 self.raise_error("Invalid expression / Unexpected token") 2228 2229 self.check_errors() 2230 2231 return expressions 2232 2233 def _parse( 2234 self, 2235 parse_method: t.Callable[[Parser], exp.Expr | None], 2236 raw_tokens: list[Token], 2237 sql: str | None = None, 2238 ) -> list[exp.Expr | None]: 2239 self.reset() 2240 self.sql = sql or "" 2241 2242 total = len(raw_tokens) 2243 chunks: list[list[Token]] = [[]] 2244 2245 for i, token in enumerate(raw_tokens): 2246 if token.token_type == TokenType.SEMICOLON: 2247 if token.comments: 2248 chunks.append([token]) 2249 2250 if i < total - 1: 2251 chunks.append([]) 2252 else: 2253 chunks[-1].append(token) 2254 2255 self._chunks = chunks 2256 2257 return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False) 2258 2259 def _warn_unsupported(self) -> None: 2260 if self._tokens_size <= 1: 2261 return 2262 2263 # We use _find_sql because self.sql may comprise multiple chunks, and we're only 2264 # interested in emitting a warning for the one being currently processed. 2265 sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context] 2266 2267 logger.warning( 2268 f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'." 2269 ) 2270 2271 def _parse_command(self) -> exp.Command: 2272 self._warn_unsupported() 2273 comments = self._prev_comments 2274 return self.expression( 2275 exp.Command(this=self._prev.text.upper(), expression=self._parse_string()), 2276 comments=comments, 2277 ) 2278 2279 def _parse_comment(self, allow_exists: bool = True) -> exp.Expr: 2280 start = self._prev 2281 exists = self._parse_exists() if allow_exists else None 2282 2283 self._match(TokenType.ON) 2284 2285 materialized = self._match_text_seq("MATERIALIZED") 2286 kind = self._match_set(self.CREATABLES) and self._prev 2287 if not kind: 2288 return self._parse_as_command(start) 2289 2290 if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2291 this = self._parse_user_defined_function(kind=kind.token_type) 2292 elif kind.token_type == TokenType.TABLE: 2293 this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS) 2294 elif kind.token_type == TokenType.COLUMN: 2295 this = self._parse_column() 2296 else: 2297 this = self._parse_table_parts(schema=True) 2298 2299 self._match(TokenType.IS) 2300 2301 return self.expression( 2302 exp.Comment( 2303 this=this, 2304 kind=kind.text, 2305 expression=self._parse_string(), 2306 exists=exists, 2307 materialized=materialized, 2308 ) 2309 ) 2310 2311 def _parse_to_table( 2312 self, 2313 ) -> exp.ToTableProperty: 2314 table = self._parse_table_parts(schema=True) 2315 return self.expression(exp.ToTableProperty(this=table)) 2316 2317 # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl 2318 def _parse_ttl(self) -> exp.Expr: 2319 def _parse_ttl_action() -> exp.Expr | None: 2320 this = self._parse_bitwise() 2321 2322 if self._match_text_seq("DELETE"): 2323 return self.expression(exp.MergeTreeTTLAction(this=this, delete=True)) 2324 if self._match_text_seq("RECOMPRESS"): 2325 return self.expression( 2326 exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise()) 2327 ) 2328 if self._match_text_seq("TO", "DISK"): 2329 return self.expression( 2330 exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string()) 2331 ) 2332 if self._match_text_seq("TO", "VOLUME"): 2333 return self.expression( 2334 exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string()) 2335 ) 2336 2337 return this 2338 2339 expressions = self._parse_csv(_parse_ttl_action) 2340 where = self._parse_where() 2341 group = self._parse_group() 2342 2343 aggregates = None 2344 if group and self._match(TokenType.SET): 2345 aggregates = self._parse_csv(self._parse_set_item) 2346 2347 return self.expression( 2348 exp.MergeTreeTTL( 2349 expressions=expressions, where=where, group=group, aggregates=aggregates 2350 ) 2351 ) 2352 2353 def _parse_condition(self) -> exp.Expr | None: 2354 return self._parse_wrapped(parse_method=self._parse_expression, optional=True) 2355 2356 def _parse_block(self) -> exp.Block: 2357 return self.expression( 2358 exp.Block( 2359 expressions=self._parse_batch_statements( 2360 parse_method=lambda self: self._parse_statement() 2361 ) 2362 ) 2363 ) 2364 2365 def _parse_whileblock(self) -> exp.WhileBlock: 2366 return self.expression( 2367 exp.WhileBlock(this=self._parse_condition(), body=self._parse_block()) 2368 ) 2369 2370 def _parse_statement(self) -> exp.Expr | None: 2371 if not self._curr: 2372 return None 2373 2374 if self._match_set(self.STATEMENT_PARSERS): 2375 comments = self._prev_comments 2376 stmt = self.STATEMENT_PARSERS[self._prev.token_type](self) 2377 stmt.add_comments(comments, prepend=True) 2378 return stmt 2379 2380 if self._match_set(self.dialect.tokenizer_class.COMMANDS): 2381 return self._parse_command() 2382 2383 if self._match_text_seq("WHILE"): 2384 return self._parse_whileblock() 2385 2386 expression = self._parse_expression() 2387 expression = self._parse_set_operations(expression) if expression else self._parse_select() 2388 2389 if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False): 2390 expression = self._parse_pipe_syntax_query(expression) 2391 2392 return self._parse_query_modifiers(expression) 2393 2394 def _parse_drop(self, exists: bool = False) -> exp.Drop | exp.Command: 2395 start = self._prev 2396 temporary = self._match(TokenType.TEMPORARY) 2397 materialized = self._match_text_seq("MATERIALIZED") 2398 iceberg = self._match_text_seq("ICEBERG") 2399 2400 kind = self._match_set(self.CREATABLES) and self._prev.text.upper() 2401 if not kind or (iceberg and kind and kind != "TABLE"): 2402 return self._parse_as_command(start) 2403 2404 concurrently = self._match_text_seq("CONCURRENTLY") 2405 if_exists = exists or self._parse_exists() 2406 2407 if kind == "COLUMN": 2408 this = self._parse_column() 2409 else: 2410 this = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA") 2411 2412 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 2413 2414 if self._match(TokenType.L_PAREN, advance=False): 2415 expressions = self._parse_wrapped_csv(self._parse_types) 2416 else: 2417 expressions = None 2418 2419 cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper() 2420 2421 return self.expression( 2422 exp.Drop( 2423 exists=if_exists, 2424 this=this, 2425 expressions=expressions, 2426 kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind, 2427 temporary=temporary, 2428 materialized=materialized, 2429 cascade=cascade_or_restrict == "CASCADE", 2430 restrict=cascade_or_restrict == "RESTRICT", 2431 constraints=self._match_text_seq("CONSTRAINTS"), 2432 purge=self._match_text_seq("PURGE"), 2433 cluster=cluster, 2434 concurrently=concurrently, 2435 sync=self._match_text_seq("SYNC"), 2436 iceberg=iceberg, 2437 force=self._match_text_seq("FORCE"), 2438 ) 2439 ) 2440 2441 def _parse_exists(self, not_: bool = False) -> bool | None: 2442 return ( 2443 self._match_text_seq("IF") 2444 and (not not_ or self._match(TokenType.NOT)) 2445 and self._match(TokenType.EXISTS) 2446 ) 2447 2448 def _parse_create(self) -> exp.Create | exp.Command: 2449 # Note: this can't be None because we've matched a statement parser 2450 start = self._prev 2451 2452 replace = ( 2453 start.token_type == TokenType.REPLACE 2454 or self._match_pair(TokenType.OR, TokenType.REPLACE) 2455 or self._match_pair(TokenType.OR, TokenType.ALTER) 2456 ) 2457 refresh = self._match_pair(TokenType.OR, TokenType.REFRESH) 2458 2459 unique = self._match(TokenType.UNIQUE) 2460 2461 if self._match_text_seq("CLUSTERED", "COLUMNSTORE"): 2462 clustered = True 2463 elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq( 2464 "COLUMNSTORE" 2465 ): 2466 clustered = False 2467 else: 2468 clustered = None 2469 2470 if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False): 2471 self._advance() 2472 2473 properties = None 2474 create_token = self._match_set(self.CREATABLES) and self._prev 2475 2476 if not create_token: 2477 # exp.Properties.Location.POST_CREATE 2478 properties = self._parse_properties() 2479 create_token = self._match_set(self.CREATABLES) and self._prev 2480 2481 if not properties or not create_token: 2482 return self._parse_as_command(start) 2483 2484 create_token_type = t.cast(Token, create_token).token_type 2485 2486 concurrently = self._match_text_seq("CONCURRENTLY") 2487 exists = self._parse_exists(not_=True) 2488 this = None 2489 expression: exp.Expr | None = None 2490 indexes = None 2491 no_schema_binding = None 2492 begin = None 2493 clone = None 2494 2495 def extend_props(temp_props: exp.Properties | None) -> None: 2496 nonlocal properties 2497 if properties and temp_props: 2498 properties.expressions.extend(temp_props.expressions) 2499 elif temp_props: 2500 properties = temp_props 2501 2502 if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2503 this = self._parse_user_defined_function(kind=create_token_type) 2504 2505 # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature) 2506 extend_props(self._parse_properties()) 2507 2508 expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None 2509 2510 if ( 2511 not expression 2512 and create_token_type == TokenType.FUNCTION 2513 and isinstance(this, exp.UserDefinedFunction) 2514 and this.args.get("wrapped") 2515 ): 2516 pre_table_index = self._index 2517 is_table = self._match(TokenType.TABLE) 2518 2519 expression = self._parse_expression() 2520 overload_mode = bool( 2521 expression 2522 and self._curr.token_type == TokenType.COMMA 2523 and self._next.token_type == TokenType.L_PAREN 2524 ) 2525 if not overload_mode: 2526 self._retreat(pre_table_index) 2527 is_table = False 2528 expression = None 2529 else: 2530 is_table = False 2531 overload_mode = False 2532 2533 extend_props(self._parse_function_properties()) 2534 2535 if not expression: 2536 if self._match(TokenType.COMMAND): 2537 expression = self._parse_as_command(self._prev) 2538 else: 2539 begin = self._match(TokenType.BEGIN) 2540 return_ = self._match_text_seq("RETURN") 2541 2542 if self._match(TokenType.STRING, advance=False): 2543 # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property 2544 # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement 2545 expression = self._parse_string() 2546 extend_props(self._parse_properties()) 2547 else: 2548 expression = ( 2549 self._parse_user_defined_function_expression() 2550 if create_token_type == TokenType.FUNCTION 2551 else self._parse_block() 2552 ) 2553 2554 if return_: 2555 expression = self.expression(exp.Return(this=expression)) 2556 2557 if overload_mode and expression: 2558 expression = self._parse_macro_overloads( 2559 t.cast(exp.UserDefinedFunction, this), expression, is_table 2560 ) 2561 elif create_token_type == TokenType.INDEX: 2562 # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c) 2563 if not self._match(TokenType.ON): 2564 index = self._parse_id_var() 2565 anonymous = False 2566 else: 2567 index = None 2568 anonymous = True 2569 2570 this = self._parse_index(index=index, anonymous=anonymous) 2571 elif ( 2572 create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER) 2573 ) or create_token_type == TokenType.TRIGGER: 2574 if is_constraint := (create_token_type == TokenType.CONSTRAINT): 2575 create_token = self._prev 2576 2577 trigger_name = self._parse_id_var() 2578 if not trigger_name: 2579 return self._parse_as_command(start) 2580 2581 timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False) 2582 timing = timing_var.this if timing_var else None 2583 if not timing: 2584 return self._parse_as_command(start) 2585 2586 events = self._parse_trigger_events() 2587 if not self._match(TokenType.ON): 2588 self.raise_error("Expected ON in trigger definition") 2589 2590 table = self._parse_table_parts() 2591 referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None 2592 deferrable, initially = self._parse_trigger_deferrable() 2593 referencing = self._parse_trigger_referencing() 2594 for_each = self._parse_trigger_for_each() 2595 when = self._match_text_seq("WHEN") and self._parse_wrapped( 2596 self._parse_disjunction, optional=True 2597 ) 2598 execute = self._parse_trigger_execute() 2599 2600 if execute is None: 2601 return self._parse_as_command(start) 2602 2603 trigger_props = self.expression( 2604 exp.TriggerProperties( 2605 table=table, 2606 timing=timing, 2607 events=events, 2608 execute=execute, 2609 constraint=is_constraint, 2610 referenced_table=referenced_table, 2611 deferrable=deferrable, 2612 initially=initially, 2613 referencing=referencing, 2614 for_each=for_each, 2615 when=when, 2616 ) 2617 ) 2618 2619 this = trigger_name 2620 extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else [])) 2621 elif create_token_type == TokenType.TYPE: 2622 this = self._parse_table_parts(schema=True) 2623 if not this or not self._match(TokenType.ALIAS): 2624 return self._parse_as_command(start) 2625 2626 if self._match(TokenType.ENUM): 2627 expression = exp.DataType( 2628 this=exp.DType.ENUM, 2629 expressions=self._parse_wrapped_csv(self._parse_string), 2630 ) 2631 elif self._match(TokenType.L_PAREN, advance=False): 2632 expression = self._parse_schema() 2633 else: 2634 return self._parse_as_command(start) 2635 elif create_token_type in self.DB_CREATABLES: 2636 table_parts = self._parse_table_parts( 2637 schema=True, is_db_reference=create_token_type == TokenType.SCHEMA 2638 ) 2639 2640 # exp.Properties.Location.POST_NAME 2641 self._match(TokenType.COMMA) 2642 extend_props(self._parse_properties(before=True)) 2643 2644 this = self._parse_schema(this=table_parts) 2645 2646 # exp.Properties.Location.POST_SCHEMA and POST_WITH 2647 extend_props(self._parse_properties()) 2648 2649 has_alias = self._match(TokenType.ALIAS) 2650 if not self._match_set(self.DDL_SELECT_TOKENS, advance=False): 2651 # exp.Properties.Location.POST_ALIAS 2652 extend_props(self._parse_properties()) 2653 2654 if create_token_type == TokenType.SEQUENCE: 2655 expression = self._parse_types() 2656 props = self._parse_properties() 2657 if props: 2658 sequence_props = exp.SequenceProperties() 2659 options = [] 2660 for prop in props: 2661 if isinstance(prop, exp.SequenceProperties): 2662 for arg, value in prop.args.items(): 2663 if arg == "options": 2664 options.extend(value) 2665 else: 2666 sequence_props.set(arg, value) 2667 prop.pop() 2668 2669 if options: 2670 sequence_props.set("options", options) 2671 2672 props.append("expressions", sequence_props) 2673 extend_props(props) 2674 else: 2675 expression = self._parse_ddl_select() 2676 2677 # Some dialects also support using a table as an alias instead of a SELECT. 2678 # Here we fallback to this as an alternative. 2679 if not expression and has_alias: 2680 expression = self._try_parse(self._parse_table_parts) 2681 2682 if create_token_type == TokenType.TABLE: 2683 # exp.Properties.Location.POST_EXPRESSION 2684 extend_props(self._parse_properties()) 2685 2686 indexes = [] 2687 while True: 2688 index = self._parse_index() 2689 2690 # exp.Properties.Location.POST_INDEX 2691 extend_props(self._parse_properties()) 2692 if not index: 2693 break 2694 else: 2695 self._match(TokenType.COMMA) 2696 indexes.append(index) 2697 elif create_token_type == TokenType.VIEW: 2698 if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"): 2699 no_schema_binding = True 2700 elif create_token_type in (TokenType.SINK, TokenType.SOURCE): 2701 extend_props(self._parse_properties()) 2702 2703 shallow = self._match_text_seq("SHALLOW") 2704 2705 if self._match_texts(self.CLONE_KEYWORDS): 2706 copy = self._prev.text.lower() == "copy" 2707 clone = self.expression( 2708 exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy) 2709 ) 2710 2711 if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False): 2712 return self._parse_as_command(start) 2713 2714 create_kind_text = create_token.text.upper() 2715 return self.expression( 2716 exp.Create( 2717 this=this, 2718 kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text, 2719 replace=replace, 2720 refresh=refresh, 2721 unique=unique, 2722 expression=expression, 2723 exists=exists, 2724 properties=properties, 2725 indexes=indexes, 2726 no_schema_binding=no_schema_binding, 2727 begin=begin, 2728 clone=clone, 2729 concurrently=concurrently, 2730 clustered=clustered, 2731 ) 2732 ) 2733 2734 def _parse_sequence_properties(self) -> exp.SequenceProperties | None: 2735 seq = exp.SequenceProperties() 2736 2737 options = [] 2738 index = self._index 2739 2740 while self._curr: 2741 self._match(TokenType.COMMA) 2742 if self._match_text_seq("INCREMENT"): 2743 self._match_text_seq("BY") 2744 self._match_text_seq("=") 2745 seq.set("increment", self._parse_term()) 2746 elif self._match_text_seq("MINVALUE"): 2747 seq.set("minvalue", self._parse_term()) 2748 elif self._match_text_seq("MAXVALUE"): 2749 seq.set("maxvalue", self._parse_term()) 2750 elif self._match_text_seq("START"): 2751 self._match_text_seq("WITH") 2752 self._match_text_seq("=") 2753 seq.set("start", self._parse_term()) 2754 elif self._match_text_seq("CACHE"): 2755 # T-SQL allows empty CACHE which is initialized dynamically 2756 seq.set("cache", self._parse_number() or True) 2757 elif self._match_text_seq("OWNED", "BY"): 2758 # "OWNED BY NONE" is the default 2759 seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column()) 2760 else: 2761 opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False) 2762 if opt: 2763 options.append(opt) 2764 else: 2765 break 2766 2767 seq.set("options", options if options else None) 2768 return None if self._index == index else seq 2769 2770 def _parse_trigger_events(self) -> list[exp.TriggerEvent]: 2771 events = [] 2772 2773 while True: 2774 event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper() 2775 2776 if not event_type: 2777 self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)") 2778 2779 columns = ( 2780 self._parse_csv(self._parse_column) 2781 if event_type == "UPDATE" and self._match_text_seq("OF") 2782 else None 2783 ) 2784 2785 events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns))) 2786 2787 if not self._match(TokenType.OR): 2788 break 2789 2790 return events 2791 2792 def _parse_trigger_deferrable( 2793 self, 2794 ) -> tuple[str | None, str | None]: 2795 deferrable_var = self._parse_var_from_options( 2796 self.TRIGGER_DEFERRABLE, raise_unmatched=False 2797 ) 2798 deferrable = deferrable_var.this if deferrable_var else None 2799 2800 initially = None 2801 if deferrable and self._match_text_seq("INITIALLY"): 2802 initially = ( 2803 self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None 2804 ) 2805 2806 return deferrable, initially 2807 2808 def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None: 2809 if not self._match_text_seq(keyword): 2810 return None 2811 if not self._match_text_seq("TABLE"): 2812 self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause") 2813 self._match_text_seq("AS") 2814 return self._parse_id_var() 2815 2816 def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None: 2817 if not self._match_text_seq("REFERENCING"): 2818 return None 2819 2820 old_alias = None 2821 new_alias = None 2822 2823 while True: 2824 if alias := self._parse_trigger_referencing_clause("OLD"): 2825 if old_alias is not None: 2826 self.raise_error("Duplicate OLD clause in REFERENCING") 2827 old_alias = alias 2828 elif alias := self._parse_trigger_referencing_clause("NEW"): 2829 if new_alias is not None: 2830 self.raise_error("Duplicate NEW clause in REFERENCING") 2831 new_alias = alias 2832 else: 2833 break 2834 2835 if old_alias is None and new_alias is None: 2836 self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE") 2837 2838 return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias)) 2839 2840 def _parse_trigger_for_each(self) -> str | None: 2841 if not self._match_text_seq("FOR", "EACH"): 2842 return None 2843 2844 return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None 2845 2846 def _parse_trigger_execute(self) -> exp.TriggerExecute | None: 2847 if not self._match(TokenType.EXECUTE): 2848 return None 2849 2850 if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)): 2851 self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE") 2852 2853 func_call = self._parse_column() 2854 return self.expression(exp.TriggerExecute(this=func_call)) 2855 2856 def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None: 2857 # only used for teradata currently 2858 self._match(TokenType.COMMA) 2859 2860 kwargs = { 2861 "no": self._match_text_seq("NO"), 2862 "dual": self._match_text_seq("DUAL"), 2863 "before": self._match_text_seq("BEFORE"), 2864 "default": self._match_text_seq("DEFAULT"), 2865 "local": (self._match_text_seq("LOCAL") and "LOCAL") 2866 or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"), 2867 "after": self._match_text_seq("AFTER"), 2868 "minimum": self._match_texts(("MIN", "MINIMUM")), 2869 "maximum": self._match_texts(("MAX", "MAXIMUM")), 2870 } 2871 2872 if self._match_texts(self.PROPERTY_PARSERS): 2873 parser = self.PROPERTY_PARSERS[self._prev.text.upper()] 2874 try: 2875 return parser(self, **{k: v for k, v in kwargs.items() if v}) 2876 except TypeError: 2877 self.raise_error(f"Cannot parse property '{self._prev.text}'") 2878 2879 if self._match_text_seq("CHARACTER", "SET"): 2880 return self._parse_character_set(default=bool(kwargs["default"])) 2881 2882 return None 2883 2884 def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]: 2885 return self._parse_wrapped_csv(self._parse_property) 2886 2887 def _parse_property(self) -> exp.Expr | list[exp.Expr] | None: 2888 if self._match_texts(self.PROPERTY_PARSERS): 2889 return self.PROPERTY_PARSERS[self._prev.text.upper()](self) 2890 2891 if self._match_text_seq("CHARACTER", "SET"): 2892 return self._parse_character_set() 2893 2894 if self._match(TokenType.DEFAULT): 2895 if self._match_texts(self.PROPERTY_PARSERS): 2896 return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True) 2897 2898 if self._match_text_seq("CHARACTER", "SET"): 2899 return self._parse_character_set(default=True) 2900 2901 if self._match_text_seq("COMPOUND", "SORTKEY"): 2902 return self._parse_sortkey(compound=True) 2903 2904 if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"): 2905 return self.expression(exp.ParameterStyleProperty(this="PANDAS")) 2906 2907 index = self._index 2908 2909 seq_props = self._parse_sequence_properties() 2910 if seq_props: 2911 return seq_props 2912 2913 self._retreat(index) 2914 return self._parse_key_value_property() 2915 2916 def _parse_key_value_property( 2917 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 2918 ) -> exp.Property | None: 2919 index = self._index 2920 key = self._parse_column() 2921 2922 if not self._match(TokenType.EQ): 2923 self._retreat(index) 2924 return None 2925 2926 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 2927 if isinstance(key, exp.Column): 2928 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 2929 2930 value = ( 2931 parse_value() 2932 if parse_value 2933 else self._parse_bitwise() or self._parse_var(any_token=True) 2934 ) 2935 2936 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 2937 if isinstance(value, exp.Column): 2938 value = exp.var(value.name) 2939 2940 return self.expression(exp.Property(this=key, value=value)) 2941 2942 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 2943 if self._match_text_seq("BY"): 2944 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 2945 2946 self._match(TokenType.ALIAS) 2947 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 2948 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 2949 2950 return self.expression( 2951 exp.FileFormatProperty( 2952 this=( 2953 self.expression( 2954 exp.InputOutputFormat( 2955 input_format=input_format, output_format=output_format 2956 ) 2957 ) 2958 if input_format or output_format 2959 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 2960 ), 2961 hive_format=True, 2962 ) 2963 ) 2964 2965 def _parse_unquoted_field(self) -> exp.Expr | None: 2966 field = self._parse_field() 2967 if isinstance(field, exp.Identifier) and not field.quoted: 2968 field = exp.var(field) 2969 2970 return field 2971 2972 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 2973 self._match(TokenType.EQ) 2974 self._match(TokenType.ALIAS) 2975 2976 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 2977 2978 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 2979 properties = [] 2980 while True: 2981 if before: 2982 prop = self._parse_property_before() 2983 else: 2984 prop = self._parse_property() 2985 if not prop: 2986 break 2987 for p in ensure_list(prop): 2988 properties.append(p) 2989 2990 if properties: 2991 return self.expression(exp.Properties(expressions=properties)) 2992 2993 return None 2994 2995 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 2996 return self.expression( 2997 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 2998 ) 2999 3000 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3001 return self.expression( 3002 exp.SqlSecurityProperty( 3003 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3004 ) 3005 ) 3006 3007 def _parse_settings_property(self) -> exp.SettingsProperty: 3008 return self.expression( 3009 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3010 ) 3011 3012 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3013 if not self._match_text_seq("ON", "NULL", "INPUT"): 3014 self._retreat(self._index - 1) 3015 return None 3016 3017 return self.expression(exp.CalledOnNullInputProperty()) 3018 3019 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3020 if self._index >= 2: 3021 pre_volatile_token = self._tokens[self._index - 2] 3022 else: 3023 pre_volatile_token = None 3024 3025 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3026 return exp.VolatileProperty() 3027 3028 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3029 3030 def _parse_retention_period(self) -> exp.Var: 3031 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3032 number = self._parse_number() 3033 number_str = f"{number} " if number else "" 3034 unit = self._parse_var(any_token=True) 3035 return exp.var(f"{number_str}{unit}") 3036 3037 def _parse_system_versioning_property( 3038 self, with_: bool = False 3039 ) -> exp.WithSystemVersioningProperty: 3040 self._match(TokenType.EQ) 3041 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3042 3043 if self._match_text_seq("OFF"): 3044 prop.set("on", False) 3045 return prop 3046 3047 self._match(TokenType.ON) 3048 if self._match(TokenType.L_PAREN): 3049 while self._curr and not self._match(TokenType.R_PAREN): 3050 if self._match_text_seq("HISTORY_TABLE", "="): 3051 prop.set("this", self._parse_table_parts()) 3052 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3053 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3054 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3055 prop.set("retention_period", self._parse_retention_period()) 3056 3057 self._match(TokenType.COMMA) 3058 3059 return prop 3060 3061 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3062 self._match(TokenType.EQ) 3063 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3064 prop = self.expression(exp.DataDeletionProperty(on=on)) 3065 3066 if self._match(TokenType.L_PAREN): 3067 while self._curr and not self._match(TokenType.R_PAREN): 3068 if self._match_text_seq("FILTER_COLUMN", "="): 3069 prop.set("filter_column", self._parse_column()) 3070 elif self._match_text_seq("RETENTION_PERIOD", "="): 3071 prop.set("retention_period", self._parse_retention_period()) 3072 3073 self._match(TokenType.COMMA) 3074 3075 return prop 3076 3077 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3078 kind = "HASH" 3079 expressions: list[exp.Expr] | None = None 3080 if self._match_text_seq("BY", "HASH"): 3081 expressions = self._parse_wrapped_csv(self._parse_id_var) 3082 elif self._match_text_seq("BY", "RANDOM"): 3083 kind = "RANDOM" 3084 3085 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3086 buckets: exp.Expr | None = None 3087 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3088 buckets = self._parse_number() 3089 3090 return self.expression( 3091 exp.DistributedByProperty( 3092 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3093 ) 3094 ) 3095 3096 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3097 self._match_text_seq("KEY") 3098 expressions = self._parse_wrapped_id_vars() 3099 return self.expression(expr_type(expressions=expressions)) 3100 3101 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3102 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3103 prop = self._parse_system_versioning_property(with_=True) 3104 self._match_r_paren() 3105 return prop 3106 3107 if self._match(TokenType.L_PAREN, advance=False): 3108 result: list[exp.Expr] = [] 3109 for i in self._parse_wrapped_properties(): 3110 result.extend(i) if isinstance(i, list) else result.append(i) 3111 return result 3112 3113 if self._match_text_seq("JOURNAL"): 3114 return self._parse_withjournaltable() 3115 3116 if self._match_texts(self.VIEW_ATTRIBUTES): 3117 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3118 3119 if self._match_text_seq("DATA"): 3120 return self._parse_withdata(no=False) 3121 elif self._match_text_seq("NO", "DATA"): 3122 return self._parse_withdata(no=True) 3123 3124 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3125 return self._parse_serde_properties(with_=True) 3126 3127 if self._match(TokenType.SCHEMA): 3128 return self.expression( 3129 exp.WithSchemaBindingProperty( 3130 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3131 ) 3132 ) 3133 3134 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3135 return self.expression( 3136 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3137 ) 3138 3139 if not self._next: 3140 return None 3141 3142 return self._parse_withisolatedloading() 3143 3144 def _parse_procedure_option(self) -> exp.Expr | None: 3145 if self._match_text_seq("EXECUTE", "AS"): 3146 return self.expression( 3147 exp.ExecuteAsProperty( 3148 this=self._parse_var_from_options( 3149 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3150 ) 3151 or self._parse_string() 3152 ) 3153 ) 3154 3155 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3156 3157 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3158 def _parse_definer(self) -> exp.DefinerProperty | None: 3159 self._match(TokenType.EQ) 3160 3161 user = self._parse_id_var() 3162 self._match(TokenType.PARAMETER) 3163 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3164 3165 if not user or not host: 3166 return None 3167 3168 return exp.DefinerProperty(this=f"{user}@{host}") 3169 3170 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3171 self._match(TokenType.TABLE) 3172 self._match(TokenType.EQ) 3173 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3174 3175 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3176 return self.expression(exp.LogProperty(no=no)) 3177 3178 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3179 return self.expression(exp.JournalProperty(**kwargs)) 3180 3181 def _parse_checksum(self) -> exp.ChecksumProperty: 3182 self._match(TokenType.EQ) 3183 3184 on = None 3185 if self._match(TokenType.ON): 3186 on = True 3187 elif self._match_text_seq("OFF"): 3188 on = False 3189 3190 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3191 3192 def _parse_cluster(self) -> exp.Cluster: 3193 self._match(TokenType.CLUSTER_BY) 3194 return self.expression( 3195 exp.Cluster( 3196 expressions=self._parse_csv(self._parse_column), 3197 ) 3198 ) 3199 3200 def _parse_cluster_property(self) -> exp.ClusterProperty: 3201 return self.expression( 3202 exp.ClusterProperty( 3203 expressions=self._parse_wrapped_csv(self._parse_column), 3204 ) 3205 ) 3206 3207 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3208 self._match_text_seq("BY") 3209 3210 self._match_l_paren() 3211 expressions = self._parse_csv(self._parse_column) 3212 self._match_r_paren() 3213 3214 if self._match_text_seq("SORTED", "BY"): 3215 self._match_l_paren() 3216 sorted_by = self._parse_csv(self._parse_ordered) 3217 self._match_r_paren() 3218 else: 3219 sorted_by = None 3220 3221 self._match(TokenType.INTO) 3222 buckets = self._parse_number() 3223 self._match_text_seq("BUCKETS") 3224 3225 return self.expression( 3226 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3227 ) 3228 3229 def _parse_copy_property(self) -> exp.CopyGrantsProperty | None: 3230 if not self._match_text_seq("GRANTS"): 3231 self._retreat(self._index - 1) 3232 return None 3233 3234 return self.expression(exp.CopyGrantsProperty()) 3235 3236 def _parse_freespace(self) -> exp.FreespaceProperty: 3237 self._match(TokenType.EQ) 3238 return self.expression( 3239 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3240 ) 3241 3242 def _parse_mergeblockratio( 3243 self, no: bool = False, default: bool = False 3244 ) -> exp.MergeBlockRatioProperty: 3245 if self._match(TokenType.EQ): 3246 return self.expression( 3247 exp.MergeBlockRatioProperty( 3248 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3249 ) 3250 ) 3251 3252 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3253 3254 def _parse_datablocksize( 3255 self, 3256 default: bool | None = None, 3257 minimum: bool | None = None, 3258 maximum: bool | None = None, 3259 ) -> exp.DataBlocksizeProperty: 3260 self._match(TokenType.EQ) 3261 size = self._parse_number() 3262 3263 units = None 3264 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3265 units = self._prev.text 3266 3267 return self.expression( 3268 exp.DataBlocksizeProperty( 3269 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3270 ) 3271 ) 3272 3273 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3274 self._match(TokenType.EQ) 3275 always = self._match_text_seq("ALWAYS") 3276 manual = self._match_text_seq("MANUAL") 3277 never = self._match_text_seq("NEVER") 3278 default = self._match_text_seq("DEFAULT") 3279 3280 autotemp = None 3281 if self._match_text_seq("AUTOTEMP"): 3282 autotemp = self._parse_schema() 3283 3284 return self.expression( 3285 exp.BlockCompressionProperty( 3286 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3287 ) 3288 ) 3289 3290 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3291 index = self._index 3292 no = self._match_text_seq("NO") 3293 concurrent = self._match_text_seq("CONCURRENT") 3294 3295 if not self._match_text_seq("ISOLATED", "LOADING"): 3296 self._retreat(index) 3297 return None 3298 3299 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3300 return self.expression( 3301 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3302 ) 3303 3304 def _parse_locking(self) -> exp.LockingProperty: 3305 if self._match(TokenType.TABLE): 3306 kind = "TABLE" 3307 elif self._match(TokenType.VIEW): 3308 kind = "VIEW" 3309 elif self._match(TokenType.ROW): 3310 kind = "ROW" 3311 elif self._match_text_seq("DATABASE"): 3312 kind = "DATABASE" 3313 else: 3314 kind = None 3315 3316 if kind in ("DATABASE", "TABLE", "VIEW"): 3317 this = self._parse_table_parts() 3318 else: 3319 this = None 3320 3321 if self._match(TokenType.FOR): 3322 for_or_in = "FOR" 3323 elif self._match(TokenType.IN): 3324 for_or_in = "IN" 3325 else: 3326 for_or_in = None 3327 3328 if self._match_text_seq("ACCESS"): 3329 lock_type = "ACCESS" 3330 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3331 lock_type = "EXCLUSIVE" 3332 elif self._match_text_seq("SHARE"): 3333 lock_type = "SHARE" 3334 elif self._match_text_seq("READ"): 3335 lock_type = "READ" 3336 elif self._match_text_seq("WRITE"): 3337 lock_type = "WRITE" 3338 elif self._match_text_seq("CHECKSUM"): 3339 lock_type = "CHECKSUM" 3340 else: 3341 lock_type = None 3342 3343 override = self._match_text_seq("OVERRIDE") 3344 3345 return self.expression( 3346 exp.LockingProperty( 3347 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3348 ) 3349 ) 3350 3351 def _parse_partition_by(self) -> list[exp.Expr]: 3352 if self._match(TokenType.PARTITION_BY): 3353 return self._parse_csv(self._parse_disjunction) 3354 return [] 3355 3356 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3357 def _parse_partition_bound_expr() -> exp.Expr | None: 3358 if self._match_text_seq("MINVALUE"): 3359 return exp.var("MINVALUE") 3360 if self._match_text_seq("MAXVALUE"): 3361 return exp.var("MAXVALUE") 3362 return self._parse_bitwise() 3363 3364 this: exp.Expr | list[exp.Expr] | None = None 3365 expression = None 3366 from_expressions = None 3367 to_expressions = None 3368 3369 if self._match(TokenType.IN): 3370 this = self._parse_wrapped_csv(self._parse_bitwise) 3371 elif self._match(TokenType.FROM): 3372 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3373 self._match_text_seq("TO") 3374 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3375 elif self._match_text_seq("WITH", "(", "MODULUS"): 3376 this = self._parse_number() 3377 self._match_text_seq(",", "REMAINDER") 3378 expression = self._parse_number() 3379 self._match_r_paren() 3380 else: 3381 self.raise_error("Failed to parse partition bound spec.") 3382 3383 return self.expression( 3384 exp.PartitionBoundSpec( 3385 this=this, 3386 expression=expression, 3387 from_expressions=from_expressions, 3388 to_expressions=to_expressions, 3389 ) 3390 ) 3391 3392 # https://www.postgresql.org/docs/current/sql-createtable.html 3393 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3394 if not self._match_text_seq("OF"): 3395 self._retreat(self._index - 1) 3396 return None 3397 3398 this = self._parse_table(schema=True) 3399 3400 if self._match(TokenType.DEFAULT): 3401 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3402 elif self._match_text_seq("FOR", "VALUES"): 3403 expression = self._parse_partition_bound_spec() 3404 else: 3405 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3406 3407 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3408 3409 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3410 self._match(TokenType.EQ) 3411 return self.expression( 3412 exp.PartitionedByProperty( 3413 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3414 ) 3415 ) 3416 3417 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3418 if self._match_text_seq("AND", "STATISTICS"): 3419 statistics = True 3420 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3421 statistics = False 3422 else: 3423 statistics = None 3424 3425 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3426 3427 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3428 if self._match_text_seq("SQL"): 3429 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3430 return None 3431 3432 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3433 if self._match_text_seq("SQL", "DATA"): 3434 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3435 return None 3436 3437 def _parse_no_property(self) -> exp.Expr | None: 3438 if self._match_text_seq("PRIMARY", "INDEX"): 3439 return exp.NoPrimaryIndexProperty() 3440 if self._match_text_seq("SQL"): 3441 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3442 return None 3443 3444 def _parse_on_property(self) -> exp.Expr | None: 3445 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3446 return exp.OnCommitProperty() 3447 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3448 return exp.OnCommitProperty(delete=True) 3449 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3450 3451 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3452 if self._match_text_seq("SQL", "DATA"): 3453 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3454 return None 3455 3456 def _parse_distkey(self) -> exp.DistKeyProperty: 3457 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3458 3459 def _parse_create_like(self) -> exp.LikeProperty | None: 3460 table = self._parse_table(schema=True) 3461 3462 options = [] 3463 while self._match_texts(("INCLUDING", "EXCLUDING")): 3464 this = self._prev.text.upper() 3465 3466 id_var = self._parse_id_var() 3467 if not id_var: 3468 return None 3469 3470 options.append( 3471 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3472 ) 3473 3474 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3475 3476 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3477 return self.expression( 3478 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3479 ) 3480 3481 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3482 self._match(TokenType.EQ) 3483 return self.expression( 3484 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3485 ) 3486 3487 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3488 self._match_text_seq("WITH", "CONNECTION") 3489 return self.expression( 3490 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3491 ) 3492 3493 def _parse_returns(self) -> exp.ReturnsProperty: 3494 value: exp.Expr | None 3495 null = None 3496 is_table = self._match(TokenType.TABLE) 3497 3498 if is_table: 3499 if self._match(TokenType.LT): 3500 value = self.expression( 3501 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3502 ) 3503 if not self._match(TokenType.GT): 3504 self.raise_error("Expecting >") 3505 else: 3506 value = self._parse_schema(exp.var("TABLE")) 3507 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3508 null = True 3509 value = None 3510 else: 3511 value = self._parse_types() 3512 3513 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3514 3515 def _parse_describe(self) -> exp.Describe: 3516 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3517 style: str | None = ( 3518 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3519 ) 3520 if self._match(TokenType.DOT): 3521 style = None 3522 self._retreat(self._index - 2) 3523 3524 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3525 3526 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3527 this = self._parse_statement() 3528 else: 3529 this = self._parse_table(schema=True) 3530 3531 properties = self._parse_properties() 3532 expressions = properties.expressions if properties else None 3533 partition = self._parse_partition() 3534 return self.expression( 3535 exp.Describe( 3536 this=this, 3537 style=style, 3538 kind=kind, 3539 expressions=expressions, 3540 partition=partition, 3541 format=format, 3542 as_json=self._match_text_seq("AS", "JSON"), 3543 ) 3544 ) 3545 3546 def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts: 3547 kind = self._prev.text.upper() 3548 expressions = [] 3549 3550 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3551 if self._match(TokenType.WHEN): 3552 expression = self._parse_disjunction() 3553 self._match(TokenType.THEN) 3554 else: 3555 expression = None 3556 3557 else_ = self._match(TokenType.ELSE) 3558 3559 if not self._match(TokenType.INTO): 3560 return None 3561 3562 return self.expression( 3563 exp.ConditionalInsert( 3564 this=self.expression( 3565 exp.Insert( 3566 this=self._parse_table(schema=True), 3567 expression=self._parse_derived_table_values(), 3568 ) 3569 ), 3570 expression=expression, 3571 else_=else_, 3572 ) 3573 ) 3574 3575 expression = parse_conditional_insert() 3576 while expression is not None: 3577 expressions.append(expression) 3578 expression = parse_conditional_insert() 3579 3580 return self.expression( 3581 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3582 comments=comments, 3583 ) 3584 3585 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3586 comments: list[str] = [] 3587 hint = self._parse_hint() 3588 overwrite = self._match(TokenType.OVERWRITE) 3589 ignore = self._match(TokenType.IGNORE) 3590 local = self._match_text_seq("LOCAL") 3591 alternative = None 3592 is_function = None 3593 3594 if self._match_text_seq("DIRECTORY"): 3595 this: exp.Expr | None = self.expression( 3596 exp.Directory( 3597 this=self._parse_var_or_string(), 3598 local=local, 3599 row_format=self._parse_row_format(match_row=True), 3600 ) 3601 ) 3602 else: 3603 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3604 comments += ensure_list(self._prev_comments) 3605 return self._parse_multitable_inserts(comments) 3606 3607 if self._match(TokenType.OR): 3608 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3609 3610 self._match(TokenType.INTO) 3611 comments += ensure_list(self._prev_comments) 3612 self._match(TokenType.TABLE) 3613 is_function = self._match(TokenType.FUNCTION) 3614 3615 this = self._parse_function() if is_function else self._parse_insert_table() 3616 3617 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3618 set_values = None 3619 if self._match(TokenType.SET): 3620 columns = [] 3621 values = [] 3622 3623 def _parse_set_assignment() -> exp.Expr | None: 3624 target = self._parse_column() 3625 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3626 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3627 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3628 else: 3629 value = self._parse_disjunction() 3630 3631 if value: 3632 columns.append(target.this) 3633 values.append(value) 3634 return value 3635 3636 self.raise_error("Expected column assignment in INSERT ... SET") 3637 return None 3638 3639 self._parse_csv(_parse_set_assignment) 3640 3641 this = self.expression(exp.Schema(this=this, expressions=columns)) 3642 set_values = self.expression( 3643 exp.Values( 3644 expressions=[exp.Tuple(expressions=values)], 3645 alias=self._parse_table_alias(), 3646 ) 3647 ) 3648 3649 returning = self._parse_returning() # TSQL allows RETURNING before source 3650 3651 stored = self._match_text_seq("STORED") and self._parse_stored() 3652 by_name = self._match_text_seq("BY", "NAME") 3653 exists = self._parse_exists() 3654 replace_where = None 3655 replace_using = None 3656 3657 if self._match(TokenType.REPLACE): 3658 if self._match(TokenType.WHERE): 3659 replace_where = self._parse_disjunction() 3660 elif self._match(TokenType.USING): 3661 replace_using = self._parse_using_identifiers() 3662 3663 return self.expression( 3664 exp.Insert( 3665 hint=hint, 3666 is_function=is_function, 3667 this=this, 3668 stored=stored, 3669 by_name=by_name, 3670 exists=exists, 3671 where=replace_where, 3672 using=replace_using, 3673 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3674 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3675 default=self._match_text_seq("DEFAULT", "VALUES"), 3676 expression=set_values 3677 or self._parse_derived_table_values() 3678 or self._parse_ddl_select(), 3679 conflict=self._parse_on_conflict(), 3680 returning=returning or self._parse_returning(), 3681 overwrite=overwrite, 3682 alternative=alternative, 3683 ignore=ignore, 3684 source=self._match(TokenType.TABLE) and self._parse_table(), 3685 ), 3686 comments=comments, 3687 ) 3688 3689 def _parse_insert_table(self) -> exp.Expr | None: 3690 this = self._parse_table(schema=True, parse_partition=True) 3691 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False): 3692 this.set("alias", self._parse_table_alias()) 3693 return this 3694 3695 def _parse_kill(self) -> exp.Kill: 3696 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3697 3698 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3699 3700 def _parse_on_conflict(self) -> exp.OnConflict | None: 3701 conflict = self._match_text_seq("ON", "CONFLICT") 3702 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3703 3704 if not conflict and not duplicate: 3705 return None 3706 3707 conflict_keys = None 3708 constraint = None 3709 3710 if conflict: 3711 if self._match_text_seq("ON", "CONSTRAINT"): 3712 constraint = self._parse_id_var() 3713 elif self._match(TokenType.L_PAREN): 3714 conflict_keys = self._parse_csv(self._parse_indexed_column) 3715 self._match_r_paren() 3716 3717 index_predicate = self._parse_where() 3718 3719 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3720 if self._prev.token_type == TokenType.UPDATE: 3721 self._match(TokenType.SET) 3722 expressions = self._parse_csv(self._parse_equality) 3723 else: 3724 expressions = None 3725 3726 return self.expression( 3727 exp.OnConflict( 3728 duplicate=duplicate, 3729 expressions=expressions, 3730 action=action, 3731 conflict_keys=conflict_keys, 3732 index_predicate=index_predicate, 3733 constraint=constraint, 3734 where=self._parse_where(), 3735 ) 3736 ) 3737 3738 def _parse_returning(self) -> exp.Returning | None: 3739 if not self._match(TokenType.RETURNING): 3740 return None 3741 return self.expression( 3742 exp.Returning( 3743 expressions=self._parse_csv(self._parse_expression), 3744 into=self._match(TokenType.INTO) and self._parse_table_part(), 3745 ) 3746 ) 3747 3748 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3749 if not self._match(TokenType.FORMAT): 3750 return None 3751 return self._parse_row_format() 3752 3753 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3754 index = self._index 3755 with_ = with_ or self._match_text_seq("WITH") 3756 3757 if not self._match(TokenType.SERDE_PROPERTIES): 3758 self._retreat(index) 3759 return None 3760 return self.expression( 3761 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3762 ) 3763 3764 def _parse_row_format( 3765 self, match_row: bool = False 3766 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3767 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3768 return None 3769 3770 if self._match_text_seq("SERDE"): 3771 this = self._parse_string() 3772 3773 serde_properties = self._parse_serde_properties() 3774 3775 return self.expression( 3776 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3777 ) 3778 3779 self._match_text_seq("DELIMITED") 3780 3781 kwargs = {} 3782 3783 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3784 kwargs["fields"] = self._parse_string() 3785 if self._match_text_seq("ESCAPED", "BY"): 3786 kwargs["escaped"] = self._parse_string() 3787 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3788 kwargs["collection_items"] = self._parse_string() 3789 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3790 kwargs["map_keys"] = self._parse_string() 3791 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3792 kwargs["lines"] = self._parse_string() 3793 if self._match_text_seq("NULL", "DEFINED", "AS"): 3794 kwargs["null"] = self._parse_string() 3795 3796 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3797 3798 def _parse_load(self) -> exp.LoadData | exp.Command: 3799 if self._match_text_seq("DATA"): 3800 local = self._match_text_seq("LOCAL") 3801 self._match_text_seq("INPATH") 3802 inpath = self._parse_string() 3803 overwrite = self._match(TokenType.OVERWRITE) 3804 temp: bool | None = None 3805 if self._match(TokenType.INTO): 3806 temp = self._match(TokenType.TEMPORARY) 3807 self._match(TokenType.TABLE) 3808 3809 return self.expression( 3810 exp.LoadData( 3811 this=self._parse_table(schema=True), 3812 local=local, 3813 overwrite=overwrite, 3814 temp=temp, 3815 inpath=inpath, 3816 files=self._match_text_seq("FROM", "FILES") 3817 and exp.Properties(expressions=self._parse_wrapped_properties()), 3818 partition=self._parse_partition(), 3819 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3820 serde=self._match_text_seq("SERDE") and self._parse_string(), 3821 ) 3822 ) 3823 return self._parse_as_command(self._prev) 3824 3825 def _parse_delete(self) -> exp.Delete: 3826 hint = self._parse_hint() 3827 3828 # This handles MySQL's "Multiple-Table Syntax" 3829 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3830 tables = None 3831 if not self._match(TokenType.FROM, advance=False): 3832 tables = self._parse_csv(self._parse_table) or None 3833 3834 returning = self._parse_returning() 3835 3836 return self.expression( 3837 exp.Delete( 3838 hint=hint, 3839 tables=tables, 3840 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3841 using=self._match(TokenType.USING) 3842 and self._parse_csv(lambda: self._parse_table(joins=True)), 3843 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3844 where=self._parse_where(), 3845 returning=returning or self._parse_returning(), 3846 order=self._parse_order(), 3847 limit=self._parse_limit(), 3848 ) 3849 ) 3850 3851 def _parse_update(self) -> exp.Update: 3852 hint = self._parse_hint() 3853 kwargs: dict[str, object] = { 3854 "hint": hint, 3855 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3856 } 3857 while self._curr: 3858 if self._match(TokenType.SET): 3859 kwargs["expressions"] = self._parse_csv(self._parse_equality) 3860 elif self._match(TokenType.RETURNING, advance=False): 3861 kwargs["returning"] = self._parse_returning() 3862 elif self._match(TokenType.FROM, advance=False): 3863 from_ = self._parse_from(joins=True) 3864 table = from_.this if from_ else None 3865 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 3866 table.set("joins", list(self._parse_joins()) or None) 3867 3868 kwargs["from_"] = from_ 3869 elif self._match(TokenType.WHERE, advance=False): 3870 kwargs["where"] = self._parse_where() 3871 elif self._match(TokenType.ORDER_BY, advance=False): 3872 kwargs["order"] = self._parse_order() 3873 elif self._match(TokenType.LIMIT, advance=False): 3874 kwargs["limit"] = self._parse_limit() 3875 else: 3876 break 3877 3878 return self.expression(exp.Update(**kwargs)) 3879 3880 def _parse_use(self) -> exp.Use: 3881 return self.expression( 3882 exp.Use( 3883 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 3884 this=self._parse_table(schema=False), 3885 ) 3886 ) 3887 3888 def _parse_uncache(self) -> exp.Uncache: 3889 if not self._match(TokenType.TABLE): 3890 self.raise_error("Expecting TABLE after UNCACHE") 3891 3892 return self.expression( 3893 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 3894 ) 3895 3896 def _parse_cache(self) -> exp.Cache: 3897 lazy = self._match_text_seq("LAZY") 3898 self._match(TokenType.TABLE) 3899 table = self._parse_table(schema=True) 3900 3901 options = [] 3902 if self._match_text_seq("OPTIONS"): 3903 self._match_l_paren() 3904 k = self._parse_string() 3905 self._match(TokenType.EQ) 3906 v = self._parse_string() 3907 options = [k, v] 3908 self._match_r_paren() 3909 3910 self._match(TokenType.ALIAS) 3911 return self.expression( 3912 exp.Cache( 3913 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 3914 ) 3915 ) 3916 3917 def _parse_partition(self) -> exp.Partition | None: 3918 if not self._match_texts(self.PARTITION_KEYWORDS): 3919 return None 3920 3921 return self.expression( 3922 exp.Partition( 3923 subpartition=self._prev.text.upper() == "SUBPARTITION", 3924 expressions=self._parse_wrapped_csv(self._parse_disjunction), 3925 ) 3926 ) 3927 3928 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 3929 def _parse_value_expression() -> exp.Expr | None: 3930 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3931 return exp.var(self._prev.text.upper()) 3932 return self._parse_expression() 3933 3934 if self._match(TokenType.L_PAREN): 3935 expressions = self._parse_csv(_parse_value_expression) 3936 self._match_r_paren() 3937 return self.expression(exp.Tuple(expressions=expressions)) 3938 3939 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 3940 expression = self._parse_expression() 3941 if expression: 3942 return self.expression(exp.Tuple(expressions=[expression])) 3943 return None 3944 3945 def _parse_projections( 3946 self, 3947 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 3948 return self._parse_expressions(), None 3949 3950 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 3951 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 3952 this: exp.Expr | None = self._parse_simplified_pivot( 3953 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 3954 ) 3955 elif self._match(TokenType.FROM): 3956 from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True) 3957 # Support parentheses for duckdb FROM-first syntax 3958 select = self._parse_select(from_=from_) 3959 if select: 3960 if not select.args.get("from_"): 3961 select.set("from_", from_) 3962 this = select 3963 else: 3964 this = exp.select("*").from_(t.cast(exp.From, from_)) 3965 this = self._parse_query_modifiers(self._parse_set_operations(this)) 3966 else: 3967 this = ( 3968 self._parse_table(consume_pipe=True) 3969 if table 3970 else self._parse_select(nested=True, parse_set_operation=False) 3971 ) 3972 3973 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 3974 # in case a modifier (e.g. join) is following 3975 if table and isinstance(this, exp.Values) and this.alias: 3976 alias = this.args["alias"].pop() 3977 this = exp.Table(this=this, alias=alias) 3978 3979 this = self._parse_query_modifiers(self._parse_set_operations(this)) 3980 3981 return this 3982 3983 def _parse_select( 3984 self, 3985 nested: bool = False, 3986 table: bool = False, 3987 parse_subquery_alias: bool = True, 3988 parse_set_operation: bool = True, 3989 consume_pipe: bool = True, 3990 from_: exp.From | None = None, 3991 ) -> exp.Expr | None: 3992 query = self._parse_select_query( 3993 nested=nested, 3994 table=table, 3995 parse_subquery_alias=parse_subquery_alias, 3996 parse_set_operation=parse_set_operation, 3997 ) 3998 3999 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 4000 if not query and from_: 4001 query = exp.select("*").from_(from_) 4002 if isinstance(query, exp.Query): 4003 query = self._parse_pipe_syntax_query(query) 4004 query = query.subquery(copy=False) if query and table else query 4005 4006 return query 4007 4008 def _parse_select_query( 4009 self, 4010 nested: bool = False, 4011 table: bool = False, 4012 parse_subquery_alias: bool = True, 4013 parse_set_operation: bool = True, 4014 ) -> exp.Expr | None: 4015 cte = self._parse_with() 4016 4017 if cte: 4018 this = self._parse_statement() 4019 4020 if not this: 4021 self.raise_error("Failed to parse any statement following CTE") 4022 return cte 4023 4024 while isinstance(this, exp.Subquery) and this.is_wrapper: 4025 this = this.this 4026 4027 assert this is not None 4028 if "with_" in this.arg_types: 4029 if inner_cte := this.args.get("with_"): 4030 cte.set("expressions", cte.expressions + inner_cte.expressions) 4031 if inner_cte.args.get("recursive"): 4032 cte.set("recursive", True) 4033 this.set("with_", cte) 4034 else: 4035 self.raise_error(f"{this.key} does not support CTE") 4036 this = cte 4037 4038 return this 4039 4040 # duckdb supports leading with FROM x 4041 from_ = ( 4042 self._parse_from(joins=True, consume_pipe=True) 4043 if self._match(TokenType.FROM, advance=False) 4044 else None 4045 ) 4046 4047 if self._match(TokenType.SELECT): 4048 comments = self._prev_comments 4049 4050 hint = self._parse_hint() 4051 4052 if self._next and not self._next.token_type == TokenType.DOT: 4053 all_ = self._match(TokenType.ALL) 4054 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4055 else: 4056 all_, matched_distinct = None, False 4057 4058 kind = ( 4059 self._prev.text.upper() 4060 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4061 else None 4062 ) 4063 4064 distinct: exp.Expr | None = ( 4065 self.expression( 4066 exp.Distinct( 4067 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4068 ) 4069 ) 4070 if matched_distinct 4071 else None 4072 ) 4073 4074 operation_modifiers = [] 4075 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4076 operation_modifiers.append(exp.var(self._prev.text.upper())) 4077 4078 limit = self._parse_limit(top=True) 4079 4080 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4081 if limit and not matched_distinct and not all_: 4082 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4083 if matched_distinct: 4084 distinct = self.expression( 4085 exp.Distinct( 4086 on=self._parse_value(values=False) 4087 if self._match(TokenType.ON) 4088 else None 4089 ) 4090 ) 4091 else: 4092 all_ = self._match(TokenType.ALL) 4093 4094 if all_ and distinct: 4095 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4096 4097 projections, exclude = self._parse_projections() 4098 4099 this = self.expression( 4100 exp.Select( 4101 kind=kind, 4102 hint=hint, 4103 distinct=distinct, 4104 expressions=projections, 4105 limit=limit, 4106 exclude=exclude, 4107 operation_modifiers=operation_modifiers or None, 4108 ) 4109 ) 4110 this.comments = comments 4111 4112 into = self._parse_into() 4113 if into: 4114 this.set("into", into) 4115 4116 if not from_: 4117 from_ = self._parse_from() 4118 4119 if from_: 4120 this.set("from_", from_) 4121 4122 this = self._parse_query_modifiers(this) 4123 elif (table or nested) and self._match(TokenType.L_PAREN): 4124 comments = self._prev_comments 4125 this = self._parse_wrapped_select(table=table) 4126 4127 if this: 4128 this.add_comments(comments, prepend=True) 4129 4130 # We return early here so that the UNION isn't attached to the subquery by the 4131 # following call to _parse_set_operations, but instead becomes the parent node 4132 self._match_r_paren() 4133 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4134 elif self._match(TokenType.VALUES, advance=False): 4135 this = self._parse_derived_table_values() 4136 elif from_: 4137 this = exp.select("*").from_(from_.this, copy=False) 4138 this = self._parse_query_modifiers(this) 4139 elif self._match(TokenType.SUMMARIZE): 4140 table = self._match(TokenType.TABLE) 4141 this = self._parse_select() or self._parse_string() or self._parse_table() 4142 return self.expression(exp.Summarize(this=this, table=table)) 4143 elif self._match(TokenType.DESCRIBE): 4144 this = self._parse_describe() 4145 else: 4146 this = None 4147 4148 return self._parse_set_operations(this) if parse_set_operation else this 4149 4150 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4151 self._match_text_seq("SEARCH") 4152 4153 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4154 4155 if not kind: 4156 return None 4157 4158 self._match_text_seq("FIRST", "BY") 4159 4160 return self.expression( 4161 exp.RecursiveWithSearch( 4162 kind=kind, 4163 this=self._parse_id_var(), 4164 expression=self._match_text_seq("SET") and self._parse_id_var(), 4165 using=self._match_text_seq("USING") and self._parse_id_var(), 4166 ) 4167 ) 4168 4169 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4170 if not skip_with_token and not self._match(TokenType.WITH): 4171 return None 4172 4173 comments = self._prev_comments 4174 recursive = self._match(TokenType.RECURSIVE) 4175 4176 last_comments = None 4177 expressions = [] 4178 udfs = [] 4179 while True: 4180 cte = self._parse_cte() 4181 if cte: 4182 if isinstance(cte, exp.FunctionSpecification): 4183 udfs.append(cte) 4184 else: 4185 expressions.append(cte) 4186 4187 if last_comments: 4188 cte.add_comments(last_comments) 4189 4190 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4191 break 4192 else: 4193 self._match(TokenType.WITH) 4194 recursive = self._match(TokenType.RECURSIVE) or recursive 4195 4196 last_comments = self._prev_comments 4197 4198 return self.expression( 4199 exp.With( 4200 expressions=expressions, 4201 recursive=recursive or None, 4202 search=self._parse_recursive_with_search(), 4203 udfs=udfs or None, 4204 ), 4205 comments=comments, 4206 ) 4207 4208 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4209 index = self._index 4210 4211 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4212 if not alias or not alias.this: 4213 self.raise_error("Expected CTE to have alias") 4214 4215 key_expressions = ( 4216 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4217 ) 4218 4219 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4220 self._retreat(index) 4221 return None 4222 4223 comments = self._prev_comments 4224 4225 if self._match_text_seq("NOT", "MATERIALIZED"): 4226 materialized = False 4227 elif self._match_text_seq("MATERIALIZED"): 4228 materialized = True 4229 else: 4230 materialized = None 4231 4232 cte = self.expression( 4233 exp.CTE( 4234 this=self._parse_wrapped(self._parse_statement), 4235 alias=alias, 4236 materialized=materialized, 4237 key_expressions=key_expressions, 4238 ), 4239 comments=comments, 4240 ) 4241 4242 values = cte.this 4243 if isinstance(values, exp.Values): 4244 cte.set("this", self._values_to_select(values)) 4245 4246 return cte 4247 4248 def _values_to_select(self, values: exp.Values) -> exp.Select: 4249 if values.alias: 4250 return exp.select("*").from_(values) 4251 return exp.select("*").from_(exp.alias_(values, "_values", table=True)) 4252 4253 def _parse_table_alias( 4254 self, alias_tokens: t.Collection[TokenType] | None = None 4255 ) -> exp.TableAlias | None: 4256 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4257 # so this section tries to parse the clause version and if it fails, it treats the token 4258 # as an identifier (alias) 4259 if self._can_parse_limit_or_offset(): 4260 return None 4261 4262 # START is never treated as an implicit alias when followed by WITH, since that 4263 # would swallow the beginning of a START WITH ... CONNECT BY clause 4264 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4265 return None 4266 4267 any_token = self._match(TokenType.ALIAS) 4268 alias = ( 4269 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4270 or self._parse_string_as_identifier() 4271 ) 4272 4273 index = self._index 4274 if self._match(TokenType.L_PAREN): 4275 columns = self._parse_csv(self._parse_function_parameter) 4276 self._match_r_paren() if columns else self._retreat(index) 4277 else: 4278 columns = None 4279 4280 if not alias and not columns: 4281 return None 4282 4283 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4284 4285 # We bubble up comments from the Identifier to the TableAlias 4286 if isinstance(alias, exp.Identifier): 4287 table_alias.add_comments(alias.pop_comments()) 4288 4289 return table_alias 4290 4291 def _parse_subquery( 4292 self, this: exp.Expr | None, parse_alias: bool = True 4293 ) -> exp.Subquery | None: 4294 if not this: 4295 return None 4296 4297 return self.expression( 4298 exp.Subquery( 4299 this=this, 4300 pivots=self._parse_pivots(), 4301 alias=self._parse_table_alias() if parse_alias else None, 4302 sample=self._parse_table_sample(), 4303 ) 4304 ) 4305 4306 def _implicit_unnests_to_explicit(self, this: E) -> E: 4307 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4308 4309 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4310 for i, join in enumerate(this.args.get("joins") or []): 4311 table = join.this 4312 normalized_table = table.copy() 4313 normalized_table.meta["maybe_column"] = True 4314 normalized_table = _norm(normalized_table, dialect=self.dialect) 4315 4316 if isinstance(table, exp.Table) and not join.args.get("on"): 4317 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4318 table_as_column = table.to_column() 4319 unnest = exp.Unnest(expressions=[table_as_column]) 4320 4321 # Table.to_column creates a parent Alias node that we want to convert to 4322 # a TableAlias and attach to the Unnest, so it matches the parser's output 4323 if isinstance(table.args.get("alias"), exp.TableAlias): 4324 table_as_column.replace(table_as_column.this) 4325 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4326 4327 table.replace(unnest) 4328 4329 refs.add(normalized_table.alias_or_name) 4330 4331 return this 4332 4333 @t.overload 4334 def _parse_query_modifiers(self, this: E) -> E: ... 4335 4336 @t.overload 4337 def _parse_query_modifiers(self, this: None) -> None: ... 4338 4339 def _parse_query_modifiers(self, this): 4340 if isinstance(this, self.MODIFIABLES): 4341 for join in self._parse_joins(): 4342 this.append("joins", join) 4343 for lateral in iter(self._parse_lateral, None): 4344 this.append("laterals", lateral) 4345 4346 while True: 4347 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4348 modifier_token = self._curr 4349 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4350 key, expression = parser(self) 4351 4352 if expression: 4353 if this.args.get(key): 4354 self.raise_error( 4355 f"Found multiple '{modifier_token.text.upper()}' clauses", 4356 token=modifier_token, 4357 ) 4358 4359 this.set(key, expression) 4360 if key == "limit": 4361 offset = expression.args.get("offset") 4362 expression.set("offset", None) 4363 4364 if offset: 4365 offset = exp.Offset(expression=offset) 4366 this.set("offset", offset) 4367 4368 limit_by_expressions = expression.expressions 4369 expression.set("expressions", None) 4370 offset.set("expressions", limit_by_expressions) 4371 continue 4372 4373 if self._curr.text.upper() == "START": 4374 modifier_token = self._curr 4375 connect = self._parse_connect() 4376 if connect: 4377 if this.args.get("connect"): 4378 self.raise_error( 4379 "Found multiple 'START WITH' clauses", token=modifier_token 4380 ) 4381 4382 this.set("connect", connect) 4383 continue 4384 break 4385 4386 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4387 this = self._implicit_unnests_to_explicit(this) 4388 4389 return this 4390 4391 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4392 start = self._curr 4393 while self._curr: 4394 self._advance() 4395 4396 end = self._tokens[self._index - 1] 4397 return exp.Hint(expressions=[self._find_sql(start, end)]) 4398 4399 def _parse_hint_function_call(self) -> exp.Expr | None: 4400 return self._parse_function_call() 4401 4402 def _parse_hint_body(self) -> exp.Hint | None: 4403 start_index = self._index 4404 should_fallback_to_string = False 4405 4406 hints = [] 4407 try: 4408 for hint in iter( 4409 lambda: self._parse_csv( 4410 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4411 ), 4412 [], 4413 ): 4414 hints.extend(hint) 4415 except ParseError: 4416 should_fallback_to_string = True 4417 4418 if should_fallback_to_string or self._curr: 4419 self._retreat(start_index) 4420 return self._parse_hint_fallback_to_string() 4421 4422 return self.expression(exp.Hint(expressions=hints)) 4423 4424 def _parse_hint(self) -> exp.Hint | None: 4425 if self._match(TokenType.HINT) and self._prev_comments: 4426 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4427 4428 return None 4429 4430 def _parse_into(self) -> exp.Into | None: 4431 if not self._match(TokenType.INTO): 4432 return None 4433 4434 temp = self._match(TokenType.TEMPORARY) 4435 unlogged = self._match_text_seq("UNLOGGED") 4436 self._match(TokenType.TABLE) 4437 4438 return self.expression( 4439 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4440 ) 4441 4442 def _parse_from( 4443 self, 4444 joins: bool = False, 4445 skip_from_token: bool = False, 4446 consume_pipe: bool = False, 4447 ) -> exp.From | None: 4448 if not skip_from_token and not self._match(TokenType.FROM): 4449 return None 4450 4451 comments = self._prev_comments 4452 return self.expression( 4453 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4454 comments=comments, 4455 ) 4456 4457 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4458 return self.expression( 4459 exp.MatchRecognizeMeasure( 4460 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4461 this=self._parse_expression(), 4462 ) 4463 ) 4464 4465 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4466 if not self._match(TokenType.MATCH_RECOGNIZE): 4467 return None 4468 4469 self._match_l_paren() 4470 4471 partition = self._parse_partition_by() 4472 order = self._parse_order() 4473 4474 measures = ( 4475 self._parse_csv(self._parse_match_recognize_measure) 4476 if self._match_text_seq("MEASURES") 4477 else None 4478 ) 4479 4480 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4481 rows = exp.var("ONE ROW PER MATCH") 4482 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4483 text = "ALL ROWS PER MATCH" 4484 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4485 text += " SHOW EMPTY MATCHES" 4486 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4487 text += " OMIT EMPTY MATCHES" 4488 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4489 text += " WITH UNMATCHED ROWS" 4490 rows = exp.var(text) 4491 else: 4492 rows = None 4493 4494 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4495 text = "AFTER MATCH SKIP" 4496 if self._match_text_seq("PAST", "LAST", "ROW"): 4497 text += " PAST LAST ROW" 4498 elif self._match_text_seq("TO", "NEXT", "ROW"): 4499 text += " TO NEXT ROW" 4500 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4501 direction = self._prev.text.upper() 4502 pattern_var = self._advance_any() 4503 if not pattern_var: 4504 self.raise_error( 4505 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4506 ) 4507 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4508 after = exp.var(text) 4509 else: 4510 after = None 4511 4512 if self._match_text_seq("PATTERN"): 4513 self._match_l_paren() 4514 4515 if not self._curr: 4516 self.raise_error("Expecting )", self._curr) 4517 4518 paren = 1 4519 start = self._curr 4520 4521 while self._curr and paren > 0: 4522 if self._curr.token_type == TokenType.L_PAREN: 4523 paren += 1 4524 if self._curr.token_type == TokenType.R_PAREN: 4525 paren -= 1 4526 4527 end = self._prev 4528 self._advance() 4529 4530 if paren > 0: 4531 self.raise_error("Expecting )", self._curr) 4532 4533 pattern = exp.var(self._find_sql(start, end)) 4534 else: 4535 pattern = None 4536 4537 define = ( 4538 self._parse_csv(self._parse_name_as_expression) 4539 if self._match_text_seq("DEFINE") 4540 else None 4541 ) 4542 4543 self._match_r_paren() 4544 4545 return self.expression( 4546 exp.MatchRecognize( 4547 partition_by=partition, 4548 order=order, 4549 measures=measures, 4550 rows=rows, 4551 after=after, 4552 pattern=pattern, 4553 define=define, 4554 alias=self._parse_table_alias(), 4555 ) 4556 ) 4557 4558 def _parse_lateral(self) -> exp.Lateral | None: 4559 cross_apply: bool | None = None 4560 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4561 cross_apply = True 4562 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4563 cross_apply = False 4564 4565 if cross_apply is not None: 4566 this = self._parse_select(table=True) 4567 view = None 4568 outer = None 4569 elif self._match(TokenType.LATERAL): 4570 this = self._parse_select(table=True) 4571 view = self._match(TokenType.VIEW) 4572 outer = self._match(TokenType.OUTER) 4573 else: 4574 return None 4575 4576 if not this: 4577 this = ( 4578 self._parse_unnest() 4579 or self._parse_function() 4580 or self._parse_id_var(any_token=False) 4581 ) 4582 4583 while self._match(TokenType.DOT): 4584 this = exp.Dot( 4585 this=this, 4586 expression=self._parse_function() or self._parse_id_var(any_token=False), 4587 ) 4588 4589 ordinality: bool | None = None 4590 4591 if view: 4592 table = self._parse_id_var(any_token=False) 4593 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4594 table_alias: exp.TableAlias | None = self.expression( 4595 exp.TableAlias(this=table, columns=columns) 4596 ) 4597 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4598 # We move the alias from the lateral's child node to the lateral itself 4599 table_alias = this.args["alias"].pop() 4600 else: 4601 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4602 table_alias = self._parse_table_alias() 4603 4604 return self.expression( 4605 exp.Lateral( 4606 this=this, 4607 view=view, 4608 outer=outer, 4609 alias=table_alias, 4610 cross_apply=cross_apply, 4611 ordinality=ordinality, 4612 ) 4613 ) 4614 4615 def _parse_stream(self) -> exp.Stream | None: 4616 index = self._index 4617 if self._match(TokenType.STREAM): 4618 if this := self._try_parse(self._parse_table): 4619 return self.expression(exp.Stream(this=this)) 4620 self._retreat(index) 4621 return None 4622 4623 def _parse_join_parts( 4624 self, 4625 ) -> tuple[Token | None, Token | None, Token | None]: 4626 return ( 4627 self._prev if self._match_set(self.JOIN_METHODS) else None, 4628 self._prev if self._match_set(self.JOIN_SIDES) else None, 4629 self._prev if self._match_set(self.JOIN_KINDS) else None, 4630 ) 4631 4632 def _parse_using_identifiers(self) -> list[exp.Expr]: 4633 def _parse_column_as_identifier() -> exp.Expr | None: 4634 this = self._parse_column() 4635 if isinstance(this, exp.Column): 4636 return this.this 4637 return this 4638 4639 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4640 4641 def _parse_join( 4642 self, 4643 skip_join_token: bool = False, 4644 parse_bracket: bool = False, 4645 alias_tokens: t.Collection[TokenType] | None = None, 4646 ) -> exp.Join | None: 4647 if self._match(TokenType.COMMA): 4648 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4649 cross_join = self.expression(exp.Join(this=table)) if table else None 4650 4651 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4652 cross_join.set("kind", "CROSS") 4653 4654 return cross_join 4655 4656 index = self._index 4657 method, side, kind = self._parse_join_parts() 4658 directed = self._match_text_seq("DIRECTED") 4659 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4660 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4661 join_comments = self._prev_comments 4662 4663 if not skip_join_token and not join: 4664 self._retreat(index) 4665 kind = None 4666 method = None 4667 side = None 4668 4669 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4670 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4671 4672 if not skip_join_token and not join and not outer_apply and not cross_apply: 4673 return None 4674 4675 kwargs: dict[str, t.Any] = { 4676 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4677 } 4678 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4679 kwargs["expressions"] = self._parse_csv( 4680 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4681 ) 4682 4683 if method: 4684 kwargs["method"] = method.text.upper() 4685 if side: 4686 kwargs["side"] = side.text.upper() 4687 if kind: 4688 kwargs["kind"] = kind.text.upper() 4689 if hint: 4690 kwargs["hint"] = hint 4691 4692 if self._match(TokenType.MATCH_CONDITION): 4693 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4694 4695 if self._match(TokenType.ON): 4696 kwargs["on"] = self._parse_disjunction() 4697 elif self._match(TokenType.USING): 4698 kwargs["using"] = self._parse_using_identifiers() 4699 elif ( 4700 not method 4701 and not (outer_apply or cross_apply) 4702 and not isinstance(kwargs["this"], exp.Unnest) 4703 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4704 ): 4705 index = self._index 4706 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4707 4708 if joins and self._match(TokenType.ON): 4709 kwargs["on"] = self._parse_disjunction() 4710 elif joins and self._match(TokenType.USING): 4711 kwargs["using"] = self._parse_using_identifiers() 4712 else: 4713 joins = None 4714 self._retreat(index) 4715 4716 kwargs["this"].set("joins", joins if joins else None) 4717 4718 kwargs["pivots"] = self._parse_pivots() 4719 4720 comments = [c for token in (method, side, kind) if token for c in token.comments] 4721 comments = (join_comments or []) + comments 4722 4723 if ( 4724 self.ADD_JOIN_ON_TRUE 4725 and not kwargs.get("on") 4726 and not kwargs.get("using") 4727 and not kwargs.get("method") 4728 and kwargs.get("kind") in (None, "INNER", "OUTER") 4729 ): 4730 kwargs["on"] = exp.true() 4731 4732 if directed: 4733 kwargs["directed"] = directed 4734 4735 return self.expression(exp.Join(**kwargs), comments=comments) 4736 4737 def _parse_opclass(self) -> exp.Expr | None: 4738 this = self._parse_disjunction() 4739 4740 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4741 return this 4742 4743 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4744 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4745 4746 return this 4747 4748 def _parse_index_params(self) -> exp.IndexParameters: 4749 using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None 4750 4751 if self._match(TokenType.L_PAREN, advance=False): 4752 columns = self._parse_wrapped_csv(self._parse_with_operator) 4753 else: 4754 columns = None 4755 4756 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4757 partition_by = self._parse_partition_by() 4758 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4759 tablespace = ( 4760 self._parse_var(any_token=True) 4761 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4762 else None 4763 ) 4764 where = self._parse_where() 4765 4766 on = self._parse_field() if self._match(TokenType.ON) else None 4767 4768 return self.expression( 4769 exp.IndexParameters( 4770 using=using, 4771 columns=columns, 4772 include=include, 4773 partition_by=partition_by, 4774 where=where, 4775 with_storage=with_storage, 4776 tablespace=tablespace, 4777 on=on, 4778 ) 4779 ) 4780 4781 def _parse_index( 4782 self, index: exp.Expr | None = None, anonymous: bool = False 4783 ) -> exp.Index | None: 4784 if index or anonymous: 4785 unique = None 4786 primary = None 4787 amp = None 4788 4789 self._match(TokenType.ON) 4790 self._match(TokenType.TABLE) # hive 4791 table = self._parse_table_parts(schema=True) 4792 else: 4793 unique = self._match(TokenType.UNIQUE) 4794 primary = self._match_text_seq("PRIMARY") 4795 amp = self._match_text_seq("AMP") 4796 4797 if not self._match(TokenType.INDEX): 4798 return None 4799 4800 index = self._parse_id_var() 4801 table = None 4802 4803 params = self._parse_index_params() 4804 4805 return self.expression( 4806 exp.Index( 4807 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4808 ) 4809 ) 4810 4811 def _parse_table_hints(self) -> list[exp.Expr] | None: 4812 hints: list[exp.Expr] = [] 4813 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 4814 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 4815 hints.append( 4816 self.expression( 4817 exp.WithTableHint( 4818 expressions=self._parse_csv( 4819 lambda: self._parse_function() or self._parse_var(any_token=True) 4820 ) 4821 ) 4822 ) 4823 ) 4824 self._match_r_paren() 4825 else: 4826 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 4827 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 4828 hint = exp.IndexTableHint(this=self._prev.text.upper()) 4829 4830 self._match_set((TokenType.INDEX, TokenType.KEY)) 4831 if self._match(TokenType.FOR): 4832 hint.set("target", self._advance_any() and self._prev.text.upper()) 4833 4834 hint.set("expressions", self._parse_wrapped_id_vars()) 4835 hints.append(hint) 4836 4837 return hints or None 4838 4839 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 4840 return ( 4841 (not schema and self._parse_function(optional_parens=False)) 4842 or self._parse_id_var(any_token=False) 4843 or self._parse_string_as_identifier() 4844 or self._parse_placeholder() 4845 ) 4846 4847 def _parse_table_parts_fast(self) -> exp.Table | None: 4848 index = self._index 4849 parts: list[exp.Identifier] | None = None 4850 all_comments: list[str] | None = None 4851 4852 while self._match_set(self.IDENTIFIER_TOKENS): 4853 token = self._prev 4854 comments = self._prev_comments 4855 4856 has_dot = self._match(TokenType.DOT) 4857 curr_tt = self._curr.token_type 4858 4859 if not has_dot: 4860 if curr_tt in self.TABLE_POSTFIX_TOKENS: 4861 self._retreat(index) 4862 return None 4863 elif curr_tt not in self.IDENTIFIER_TOKENS: 4864 self._retreat(index) 4865 return None 4866 4867 if parts is None: 4868 parts = [] 4869 4870 if comments: 4871 if all_comments is None: 4872 all_comments = [] 4873 all_comments.extend(comments) 4874 self._prev_comments = [] 4875 4876 parts.append( 4877 self.expression( 4878 exp.Identifier( 4879 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 4880 ), 4881 token, 4882 ) 4883 ) 4884 4885 if not has_dot: 4886 break 4887 4888 if parts is None: 4889 return None 4890 4891 n = len(parts) 4892 4893 if n == 1: 4894 table: exp.Table = exp.Table(this=parts[0]) 4895 elif n == 2: 4896 table = exp.Table(this=parts[1], db=parts[0]) 4897 elif n >= 3: 4898 this: exp.Identifier | exp.Dot = parts[2] 4899 for i in range(3, n): 4900 this = exp.Dot(this=this, expression=parts[i]) 4901 4902 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 4903 4904 if table is None: 4905 self._retreat(index) 4906 elif all_comments: 4907 table.add_comments(all_comments) 4908 return table 4909 4910 def _parse_table_parts( 4911 self, 4912 schema: bool = False, 4913 is_db_reference: bool = False, 4914 wildcard: bool = False, 4915 fast: bool = False, 4916 ) -> exp.Table | exp.Dot | None: 4917 if fast: 4918 return self._parse_table_parts_fast() 4919 4920 catalog: exp.Expr | str | None = None 4921 db: exp.Expr | str | None = None 4922 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 4923 4924 while self._match(TokenType.DOT): 4925 if catalog: 4926 # This allows nesting the table in arbitrarily many dot expressions if needed 4927 table = self.expression( 4928 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 4929 ) 4930 else: 4931 catalog = db 4932 db = table 4933 # "" used for tsql FROM a..b case 4934 table = self._parse_table_part(schema=schema) or "" 4935 4936 if ( 4937 wildcard 4938 and self._is_connected() 4939 and (isinstance(table, exp.Identifier) or not table) 4940 and self._match(TokenType.STAR) 4941 ): 4942 if isinstance(table, exp.Identifier): 4943 table.args["this"] += "*" 4944 else: 4945 table = exp.Identifier(this="*") 4946 4947 if is_db_reference: 4948 catalog = db 4949 db = table 4950 table = None 4951 4952 if not table and not is_db_reference: 4953 self.raise_error(f"Expected table name but got {self._curr}") 4954 if not db and is_db_reference: 4955 self.raise_error(f"Expected database name but got {self._curr}") 4956 4957 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 4958 4959 # Bubble up comments from identifier parts to the Table 4960 comments = [] 4961 for part in table.parts: 4962 if part_comments := part.pop_comments(): 4963 comments.extend(part_comments) 4964 if comments: 4965 table.add_comments(comments) 4966 4967 changes = self._parse_changes() 4968 if changes: 4969 table.set("changes", changes) 4970 4971 at_before = self._parse_historical_data() 4972 if at_before: 4973 table.set("when", at_before) 4974 4975 pivots = self._parse_pivots() 4976 if pivots: 4977 table.set("pivots", pivots) 4978 4979 return table 4980 4981 def _parse_table( 4982 self, 4983 schema: bool = False, 4984 joins: bool = False, 4985 alias_tokens: t.Collection[TokenType] | None = None, 4986 parse_bracket: bool = False, 4987 is_db_reference: bool = False, 4988 parse_partition: bool = False, 4989 consume_pipe: bool = False, 4990 ) -> exp.Expr | None: 4991 if not schema and not is_db_reference and not consume_pipe and not joins: 4992 index = self._index 4993 table = self._parse_table_parts(fast=True) 4994 4995 if table is not None: 4996 curr_tt = self._curr.token_type 4997 next_tt = self._next.token_type 4998 4999 fast_terminators = self.TABLE_TERMINATORS 5000 5001 # only return the table if we're sure there are no other operators 5002 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5003 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5004 return table 5005 5006 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5007 5008 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5009 if alias := self._parse_table_alias( 5010 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5011 ): 5012 table.set("alias", alias) 5013 5014 if self._curr.token_type in fast_terminators: 5015 return table 5016 5017 self._retreat(index) 5018 5019 if stream := self._parse_stream(): 5020 return stream 5021 5022 if lateral := self._parse_lateral(): 5023 return lateral 5024 5025 if unnest := self._parse_unnest(): 5026 return unnest 5027 5028 if values := self._parse_derived_table_values(): 5029 return values 5030 5031 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5032 if not subquery.args.get("pivots"): 5033 subquery.set("pivots", self._parse_pivots()) 5034 if joins: 5035 for join in self._parse_joins(): 5036 subquery.append("joins", join) 5037 return subquery 5038 5039 bracket = parse_bracket and self._parse_bracket(None) 5040 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5041 5042 rows_from_tables = ( 5043 self._parse_wrapped_csv(self._parse_table) 5044 if self._match_text_seq("ROWS", "FROM") 5045 else None 5046 ) 5047 rows_from = ( 5048 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5049 ) 5050 5051 only = self._match(TokenType.ONLY) 5052 5053 this = t.cast( 5054 exp.Expr, 5055 bracket 5056 or rows_from 5057 or self._parse_bracket( 5058 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5059 ), 5060 ) 5061 5062 if only: 5063 this.set("only", only) 5064 5065 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5066 self._match(TokenType.STAR) 5067 5068 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5069 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5070 this.set("partition", self._parse_partition()) 5071 5072 if schema: 5073 return self._parse_schema(this=this) 5074 5075 if self.dialect.ALIAS_POST_VERSION: 5076 this.set("version", self._parse_version()) 5077 5078 if self.dialect.ALIAS_POST_TABLESAMPLE: 5079 this.set("sample", self._parse_table_sample()) 5080 5081 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5082 if alias: 5083 this.set("alias", alias) 5084 5085 # DuckDB requires the time-travel clause to come after the alias, e.g. 5086 # SELECT * FROM t AS a AT (VERSION => 1) 5087 if isinstance(this, exp.Table) and not this.args.get("when"): 5088 this.set("when", self._parse_historical_data()) 5089 5090 if self._match(TokenType.INDEXED_BY): 5091 this.set("indexed", self._parse_table_parts()) 5092 elif self._match_text_seq("NOT", "INDEXED"): 5093 this.set("indexed", False) 5094 5095 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5096 return self.expression( 5097 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5098 ) 5099 5100 this.set("hints", self._parse_table_hints()) 5101 5102 if not this.args.get("pivots"): 5103 this.set("pivots", self._parse_pivots()) 5104 5105 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5106 this.set("sample", self._parse_table_sample()) 5107 5108 if not self.dialect.ALIAS_POST_VERSION: 5109 this.set("version", self._parse_version()) 5110 5111 if joins: 5112 for join in self._parse_joins(alias_tokens=alias_tokens): 5113 this.append("joins", join) 5114 5115 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5116 this.set("ordinality", True) 5117 this.set("alias", self._parse_table_alias()) 5118 5119 return this 5120 5121 def _parse_version(self) -> exp.Version | None: 5122 for phrase, this in self.VERSION_PHRASES.items(): 5123 if self._match_text_seq(*phrase): 5124 break 5125 else: 5126 return None 5127 5128 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5129 kind = self._prev.text.upper() 5130 start = self._parse_bitwise() 5131 self._match_texts(("TO", "AND")) 5132 end = self._parse_bitwise() 5133 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5134 elif self._match_text_seq("CONTAINED", "IN"): 5135 kind = "CONTAINED IN" 5136 expression = self.expression( 5137 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5138 ) 5139 elif self._match(TokenType.ALL): 5140 kind = "ALL" 5141 expression = None 5142 else: 5143 self._match_text_seq("AS", "OF") 5144 kind = "AS OF" 5145 expression = self._parse_type() 5146 5147 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5148 5149 def _parse_historical_data(self) -> exp.HistoricalData | None: 5150 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5151 index = self._index 5152 historical_data = None 5153 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5154 this = self._prev.text.upper() 5155 kind = ( 5156 self._match(TokenType.L_PAREN) 5157 and self._match_texts(self.HISTORICAL_DATA_KIND) 5158 and self._prev.text.upper() 5159 ) 5160 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5161 5162 if expression: 5163 self._match_r_paren() 5164 historical_data = self.expression( 5165 exp.HistoricalData(this=this, kind=kind, expression=expression) 5166 ) 5167 else: 5168 self._retreat(index) 5169 5170 return historical_data 5171 5172 def _parse_changes(self) -> exp.Changes | None: 5173 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5174 return None 5175 5176 information = self._parse_var(any_token=True) 5177 self._match_r_paren() 5178 5179 return self.expression( 5180 exp.Changes( 5181 information=information, 5182 at_before=self._parse_historical_data(), 5183 end=self._parse_historical_data(), 5184 ) 5185 ) 5186 5187 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5188 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5189 return None 5190 5191 self._advance() 5192 5193 expressions = self._parse_wrapped_csv(self._parse_equality) 5194 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5195 5196 alias = self._parse_table_alias() if with_alias else None 5197 5198 if alias: 5199 if self.dialect.UNNEST_COLUMN_ONLY: 5200 if alias.args.get("columns"): 5201 self.raise_error("Unexpected extra column alias in unnest.") 5202 5203 alias.set("columns", [alias.this]) 5204 alias.set("this", None) 5205 5206 columns = alias.args.get("columns") or [] 5207 if offset and len(expressions) < len(columns): 5208 offset = columns.pop() 5209 5210 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5211 self._match(TokenType.ALIAS) 5212 offset = self._parse_id_var( 5213 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5214 ) or exp.to_identifier("offset") 5215 5216 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5217 5218 def _parse_derived_table_values(self) -> exp.Values | None: 5219 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5220 if not is_derived and not ( 5221 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5222 self._match_text_seq("VALUES") or self._match_text_seq("FORMAT", "VALUES") 5223 ): 5224 return None 5225 5226 expressions = self._parse_csv(self._parse_value) 5227 alias = self._parse_table_alias() 5228 5229 if is_derived: 5230 self._match_r_paren() 5231 5232 return self.expression( 5233 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5234 ) 5235 5236 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5237 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5238 as_modifier and self._match_text_seq("USING", "SAMPLE") 5239 ): 5240 return None 5241 5242 bucket_numerator = None 5243 bucket_denominator = None 5244 bucket_field = None 5245 percent = None 5246 size = None 5247 seed = None 5248 5249 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5250 matched_l_paren = self._match(TokenType.L_PAREN) 5251 5252 if self.TABLESAMPLE_CSV: 5253 num = None 5254 expressions = self._parse_csv(self._parse_primary) 5255 else: 5256 expressions = None 5257 num = ( 5258 self._parse_factor() 5259 if self._match(TokenType.NUMBER, advance=False) 5260 else self._parse_primary() or self._parse_placeholder() 5261 ) 5262 5263 if self._match_text_seq("BUCKET"): 5264 bucket_numerator = self._parse_number() 5265 self._match_text_seq("OUT", "OF") 5266 bucket_denominator = bucket_denominator = self._parse_number() 5267 self._match(TokenType.ON) 5268 bucket_field = self._parse_field() 5269 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5270 percent = num 5271 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5272 size = num 5273 else: 5274 percent = num 5275 5276 if matched_l_paren: 5277 self._match_r_paren() 5278 5279 if self._match(TokenType.L_PAREN): 5280 method = self._parse_var(upper=True) 5281 seed = self._match(TokenType.COMMA) and self._parse_number() 5282 self._match_r_paren() 5283 elif self._match_texts(("SEED", "REPEATABLE")): 5284 seed = self._parse_wrapped(self._parse_number) 5285 5286 if not method and self.DEFAULT_SAMPLING_METHOD: 5287 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5288 5289 return self.expression( 5290 exp.TableSample( 5291 expressions=expressions, 5292 method=method, 5293 bucket_numerator=bucket_numerator, 5294 bucket_denominator=bucket_denominator, 5295 bucket_field=bucket_field, 5296 percent=percent, 5297 size=size, 5298 seed=seed, 5299 ) 5300 ) 5301 5302 def _parse_pivots(self) -> list[exp.Pivot] | None: 5303 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5304 return None 5305 return list(iter(self._parse_pivot, None)) or None 5306 5307 def _parse_joins( 5308 self, alias_tokens: t.Collection[TokenType] | None = None 5309 ) -> t.Iterator[exp.Join]: 5310 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5311 5312 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5313 if not self._match(TokenType.INTO): 5314 return None 5315 5316 return self.expression( 5317 exp.UnpivotColumns( 5318 this=self._match_text_seq("NAME") and self._parse_column(), 5319 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5320 ) 5321 ) 5322 5323 # https://duckdb.org/docs/sql/statements/pivot 5324 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5325 def _parse_on() -> exp.Expr | None: 5326 this = self._parse_bitwise() 5327 5328 if self._match(TokenType.IN): 5329 # PIVOT ... ON col IN (row_val1, row_val2) 5330 return self._parse_in(this) 5331 if self._match(TokenType.ALIAS, advance=False): 5332 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5333 return self._parse_alias(this) 5334 5335 return this 5336 5337 this = self._parse_table() 5338 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5339 into = self._parse_unpivot_columns() 5340 using = self._match(TokenType.USING) and self._parse_csv( 5341 lambda: self._parse_alias(self._parse_column()) 5342 ) 5343 group = self._parse_group() 5344 5345 return self.expression( 5346 exp.Pivot( 5347 this=this, 5348 expressions=expressions, 5349 using=using, 5350 group=group, 5351 unpivot=is_unpivot, 5352 into=into, 5353 ) 5354 ) 5355 5356 def _parse_pivot_in(self) -> exp.In: 5357 def _parse_aliased_expression() -> exp.Expr | None: 5358 this = self._parse_select_or_expression() 5359 5360 self._match(TokenType.ALIAS) 5361 alias = self._parse_bitwise() 5362 if alias: 5363 if isinstance(alias, exp.Column) and not alias.db: 5364 alias = alias.this 5365 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5366 5367 return this 5368 5369 value = self._parse_column() 5370 5371 if not self._match(TokenType.IN): 5372 self.raise_error("Expecting IN") 5373 5374 if self._match(TokenType.L_PAREN): 5375 if self._match(TokenType.ANY): 5376 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5377 else: 5378 exprs = self._parse_csv(_parse_aliased_expression) 5379 self._match_r_paren() 5380 return self.expression(exp.In(this=value, expressions=exprs)) 5381 5382 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5383 5384 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5385 func = self._parse_function() 5386 if not func: 5387 if self._prev.token_type == TokenType.COMMA: 5388 return None 5389 self.raise_error("Expecting an aggregation function in PIVOT") 5390 5391 return self._parse_alias(func) 5392 5393 def _parse_pivot(self) -> exp.Pivot | None: 5394 index = self._index 5395 include_nulls = None 5396 5397 if self._match(TokenType.PIVOT): 5398 unpivot = False 5399 elif self._match(TokenType.UNPIVOT): 5400 unpivot = True 5401 5402 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5403 if self._match_text_seq("INCLUDE", "NULLS"): 5404 include_nulls = True 5405 elif self._match_text_seq("EXCLUDE", "NULLS"): 5406 include_nulls = False 5407 else: 5408 return None 5409 5410 expressions = [] 5411 5412 if not self._match(TokenType.L_PAREN): 5413 self._retreat(index) 5414 return None 5415 5416 if unpivot: 5417 expressions = self._parse_csv(self._parse_column) 5418 else: 5419 expressions = self._parse_csv(self._parse_pivot_aggregation) 5420 5421 if not expressions: 5422 self.raise_error("Failed to parse PIVOT's aggregation list") 5423 5424 if not self._match(TokenType.FOR): 5425 self.raise_error("Expecting FOR") 5426 5427 fields = [] 5428 while True: 5429 field = self._try_parse(self._parse_pivot_in) 5430 if not field: 5431 break 5432 fields.append(field) 5433 5434 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5435 self._parse_bitwise 5436 ) 5437 5438 group = self._parse_group() 5439 5440 self._match_r_paren() 5441 5442 pivot = self.expression( 5443 exp.Pivot( 5444 expressions=expressions, 5445 fields=fields, 5446 unpivot=unpivot, 5447 include_nulls=include_nulls, 5448 default_on_null=default_on_null, 5449 group=group, 5450 ) 5451 ) 5452 5453 if unpivot: 5454 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5455 for pivot_field in pivot.fields: 5456 if isinstance(pivot_field, exp.In): 5457 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5458 5459 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5460 5461 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5462 pivot.set("alias", self._parse_table_alias()) 5463 5464 if not unpivot: 5465 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5466 5467 columns: list[exp.Expr] = [] 5468 all_fields = [] 5469 for pivot_field in pivot.fields: 5470 pivot_field_expressions = pivot_field.expressions 5471 5472 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5473 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5474 continue 5475 5476 all_fields.append( 5477 [ 5478 # An explicit `<field> AS <alias>` names the output column directly, 5479 # so it wins over the dialect's string-identifying convention 5480 fld.sql() 5481 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5482 else fld.alias_or_name 5483 for fld in pivot_field_expressions 5484 ] 5485 ) 5486 5487 if all_fields: 5488 if names: 5489 all_fields.append(names) 5490 5491 # Generate all possible combinations of the pivot columns 5492 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5493 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5494 for fld_parts_tuple in itertools.product(*all_fields): 5495 fld_parts = list(fld_parts_tuple) 5496 5497 if names and self.PREFIXED_PIVOT_COLUMNS: 5498 # Move the "name" to the front of the list 5499 fld_parts.insert(0, fld_parts.pop(-1)) 5500 5501 columns.append(exp.to_identifier("_".join(fld_parts))) 5502 5503 pivot.set("columns", columns) 5504 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5505 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5506 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5507 5508 return pivot 5509 5510 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5511 return [agg.alias for agg in aggregations if agg.alias] 5512 5513 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5514 if not skip_where_token and not self._match(TokenType.PREWHERE): 5515 return None 5516 5517 comments = self._prev_comments 5518 return self.expression( 5519 exp.PreWhere(this=self._parse_disjunction()), 5520 comments=comments, 5521 ) 5522 5523 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5524 if not skip_where_token and not self._match(TokenType.WHERE): 5525 return None 5526 5527 comments = self._prev_comments 5528 return self.expression( 5529 exp.Where(this=self._parse_disjunction()), 5530 comments=comments, 5531 ) 5532 5533 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5534 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5535 return None 5536 comments = self._prev_comments 5537 5538 elements: dict[str, t.Any] = defaultdict(list) 5539 5540 if self._match(TokenType.ALL): 5541 elements["all"] = True 5542 elif self._match(TokenType.DISTINCT): 5543 elements["all"] = False 5544 5545 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5546 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5547 5548 while True: 5549 index = self._index 5550 5551 elements["expressions"].extend( 5552 self._parse_csv( 5553 lambda: ( 5554 None 5555 if self._match_set((TokenType.CUBE, TokenType.ROLLUP), advance=False) 5556 else self._parse_disjunction() 5557 ) 5558 ) 5559 ) 5560 5561 before_with_index = self._index 5562 with_prefix = self._match(TokenType.WITH) 5563 5564 if cube_or_rollup := self._parse_cube_or_rollup(with_prefix=with_prefix): 5565 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5566 elements[key].append(cube_or_rollup) 5567 elif grouping_sets := self._parse_grouping_sets(): 5568 elements["grouping_sets"].append(grouping_sets) 5569 elif self._match_text_seq("TOTALS"): 5570 elements["totals"] = True # type: ignore 5571 5572 if before_with_index <= self._index <= before_with_index + 1: 5573 self._retreat(before_with_index) 5574 break 5575 5576 if index == self._index: 5577 break 5578 5579 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5580 5581 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5582 if self._match(TokenType.CUBE): 5583 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5584 elif self._match(TokenType.ROLLUP): 5585 kind = exp.Rollup 5586 else: 5587 return None 5588 5589 return self.expression( 5590 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5591 ) 5592 5593 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5594 if self._match(TokenType.GROUPING_SETS): 5595 return self.expression( 5596 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5597 ) 5598 return None 5599 5600 def _parse_grouping_set(self) -> exp.Expr | None: 5601 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5602 5603 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5604 if not skip_having_token and not self._match(TokenType.HAVING): 5605 return None 5606 comments = self._prev_comments 5607 return self.expression( 5608 exp.Having(this=self._parse_disjunction()), 5609 comments=comments, 5610 ) 5611 5612 def _parse_qualify(self) -> exp.Qualify | None: 5613 if not self._match(TokenType.QUALIFY): 5614 return None 5615 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5616 5617 def _parse_connect_with_prior(self) -> exp.Expr | None: 5618 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5619 exp.Prior(this=self._parse_bitwise()) 5620 ) 5621 connect = self._parse_disjunction() 5622 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5623 return connect 5624 5625 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5626 if skip_start_token: 5627 start = None 5628 elif self._match_text_seq("START", "WITH"): 5629 start = self._parse_disjunction() 5630 else: 5631 return None 5632 5633 self._match(TokenType.CONNECT_BY) 5634 nocycle = self._match_text_seq("NOCYCLE") 5635 connect = self._parse_connect_with_prior() 5636 5637 if not start and self._match_text_seq("START", "WITH"): 5638 start = self._parse_disjunction() 5639 5640 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5641 5642 def _parse_name_as_expression(self) -> exp.Expr | None: 5643 this = self._parse_id_var(any_token=True) 5644 if self._match(TokenType.ALIAS): 5645 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5646 return this 5647 5648 def _parse_interpolate(self) -> list[exp.Expr] | None: 5649 if self._match_text_seq("INTERPOLATE"): 5650 return self._parse_wrapped_csv(self._parse_name_as_expression) 5651 return None 5652 5653 def _parse_order( 5654 self, this: exp.Expr | None = None, skip_order_token: bool = False 5655 ) -> exp.Expr | None: 5656 siblings = None 5657 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5658 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5659 return this 5660 5661 siblings = True 5662 5663 comments = self._prev_comments 5664 return self.expression( 5665 exp.Order( 5666 this=this, 5667 expressions=self._parse_csv(self._parse_ordered), 5668 siblings=siblings, 5669 ), 5670 comments=comments, 5671 ) 5672 5673 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5674 if not self._match(token): 5675 return None 5676 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5677 5678 def _parse_ordered( 5679 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5680 ) -> exp.Ordered | None: 5681 this = parse_method() if parse_method else self._parse_disjunction() 5682 if not this: 5683 return None 5684 5685 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5686 this = exp.var("ALL") 5687 5688 asc = self._match(TokenType.ASC) 5689 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5690 5691 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5692 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5693 5694 nulls_first = is_nulls_first or False 5695 explicitly_null_ordered = is_nulls_first or is_nulls_last 5696 5697 if ( 5698 not explicitly_null_ordered 5699 and ( 5700 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5701 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5702 ) 5703 and self.dialect.NULL_ORDERING != "nulls_are_last" 5704 ): 5705 nulls_first = True 5706 5707 if self._match_text_seq("WITH", "FILL"): 5708 with_fill = self.expression( 5709 exp.WithFill( 5710 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5711 to=self._match_text_seq("TO") and self._parse_bitwise(), 5712 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5713 interpolate=self._parse_interpolate(), 5714 ) 5715 ) 5716 else: 5717 with_fill = None 5718 5719 return self.expression( 5720 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5721 ) 5722 5723 def _parse_limit_options(self) -> exp.LimitOptions | None: 5724 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5725 rows = self._match_set((TokenType.ROW, TokenType.ROWS)) 5726 self._match_text_seq("ONLY") 5727 with_ties = self._match_text_seq("WITH", "TIES") 5728 5729 if not (percent or rows or with_ties): 5730 return None 5731 5732 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5733 5734 def _parse_limit( 5735 self, 5736 this: exp.Expr | None = None, 5737 top: bool = False, 5738 skip_limit_token: bool = False, 5739 ) -> exp.Expr | None: 5740 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5741 comments = self._prev_comments 5742 if top: 5743 limit_paren = self._match(TokenType.L_PAREN) 5744 expression = ( 5745 self._parse_term() or self._parse_select() 5746 if limit_paren 5747 else self._parse_number() 5748 ) 5749 5750 if limit_paren: 5751 self._match_r_paren() 5752 5753 else: 5754 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5755 return this 5756 5757 # Parsing LIMIT x% (i.e x PERCENT) as a term leads to an error, since 5758 # we try to build an exp.Mod expr. For that matter, we backtrack and instead 5759 # consume the factor plus parse the percentage separately 5760 index = self._index 5761 expression = self._try_parse(self._parse_term) 5762 if isinstance(expression, exp.Mod): 5763 self._retreat(index) 5764 expression = self._parse_factor() 5765 elif not expression: 5766 expression = self._parse_factor() 5767 limit_options = self._parse_limit_options() 5768 5769 if self._match(TokenType.COMMA): 5770 offset = expression 5771 expression = self._parse_term() 5772 else: 5773 offset = None 5774 5775 limit_exp = self.expression( 5776 exp.Limit( 5777 this=this, 5778 expression=expression, 5779 offset=offset, 5780 limit_options=limit_options, 5781 expressions=self._parse_limit_by(), 5782 ), 5783 comments=comments, 5784 ) 5785 5786 return limit_exp 5787 5788 if self._match(TokenType.FETCH): 5789 direction = ( 5790 self._prev.text.upper() 5791 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5792 else "FIRST" 5793 ) 5794 5795 count = self._parse_field(tokens=self.FETCH_TOKENS) 5796 5797 return self.expression( 5798 exp.Fetch( 5799 direction=direction, count=count, limit_options=self._parse_limit_options() 5800 ) 5801 ) 5802 5803 return this 5804 5805 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 5806 if not self._match(TokenType.OFFSET): 5807 return this 5808 5809 count = self._parse_term() 5810 self._match_set((TokenType.ROW, TokenType.ROWS)) 5811 5812 return self.expression( 5813 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 5814 ) 5815 5816 def _can_parse_limit_or_offset(self) -> bool: 5817 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 5818 return False 5819 5820 index = self._index 5821 result = bool( 5822 self._try_parse(self._parse_limit, retreat=True) 5823 or self._try_parse(self._parse_offset, retreat=True) 5824 ) 5825 self._retreat(index) 5826 5827 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 5828 if self._next.token_type == TokenType.MATCH_CONDITION: 5829 result = False 5830 5831 return result 5832 5833 def _can_parse_named_window(self) -> bool: 5834 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 5835 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 5836 if not self._match(TokenType.WINDOW, advance=False): 5837 return False 5838 5839 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 5840 if name is None or name.token_type not in self.ID_VAR_TOKENS: 5841 return False 5842 5843 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 5844 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 5845 return False 5846 5847 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 5848 return body is not None and body.token_type == TokenType.L_PAREN 5849 5850 def _parse_limit_by(self) -> list[exp.Expr] | None: 5851 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 5852 5853 def _parse_locks(self) -> list[exp.Lock]: 5854 locks = [] 5855 while True: 5856 update, key = None, None 5857 if self._match_text_seq("FOR", "UPDATE"): 5858 update = True 5859 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 5860 "LOCK", "IN", "SHARE", "MODE" 5861 ): 5862 update = False 5863 elif self._match_text_seq("FOR", "KEY", "SHARE"): 5864 update, key = False, True 5865 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 5866 update, key = True, True 5867 else: 5868 break 5869 5870 expressions = None 5871 if self._match_text_seq("OF"): 5872 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 5873 5874 wait: bool | exp.Expr | None = None 5875 if self._match_text_seq("NOWAIT"): 5876 wait = True 5877 elif self._match_text_seq("WAIT"): 5878 wait = self._parse_primary() 5879 elif self._match_text_seq("SKIP", "LOCKED"): 5880 wait = False 5881 5882 locks.append( 5883 self.expression( 5884 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 5885 ) 5886 ) 5887 5888 return locks 5889 5890 def parse_set_operation( 5891 self, this: exp.Expr | None, consume_pipe: bool = False 5892 ) -> exp.Expr | None: 5893 start = self._index 5894 _, side_token, kind_token = self._parse_join_parts() 5895 5896 side = side_token.text if side_token else None 5897 kind = kind_token.text if kind_token else None 5898 5899 if not self._match_set(self.SET_OPERATIONS): 5900 self._retreat(start) 5901 return None 5902 5903 token_type = self._prev.token_type 5904 5905 if token_type == TokenType.UNION: 5906 operation: type[exp.SetOperation] = exp.Union 5907 elif token_type == TokenType.EXCEPT: 5908 operation = exp.Except 5909 else: 5910 operation = exp.Intersect 5911 5912 comments = self._prev.comments 5913 5914 if self._match(TokenType.DISTINCT): 5915 distinct: bool | None = True 5916 elif self._match(TokenType.ALL): 5917 distinct = False 5918 else: 5919 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 5920 if distinct is None: 5921 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 5922 5923 by_name = ( 5924 self._match_text_seq("BY", "NAME") 5925 or self._match_text_seq("STRICT", "CORRESPONDING") 5926 or None 5927 ) 5928 if self._match_text_seq("CORRESPONDING"): 5929 by_name = True 5930 if not side and not kind: 5931 kind = "INNER" 5932 5933 on_column_list = None 5934 if by_name and self._match_texts(("ON", "BY")): 5935 on_column_list = self._parse_wrapped_csv(self._parse_column) 5936 5937 expression = self._parse_select( 5938 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 5939 ) 5940 5941 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 5942 # in _parse_cte and so that alias pushdown can reach into set operation branches 5943 if isinstance(this, exp.Values): 5944 this = self._values_to_select(this) 5945 if isinstance(expression, exp.Values): 5946 expression = self._values_to_select(expression) 5947 5948 return self.expression( 5949 operation( 5950 this=this, 5951 distinct=distinct, 5952 by_name=by_name, 5953 expression=expression, 5954 side=side, 5955 kind=kind, 5956 on=on_column_list, 5957 ), 5958 comments=comments, 5959 ) 5960 5961 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 5962 while this: 5963 setop = self.parse_set_operation(this) 5964 if not setop: 5965 break 5966 this = setop 5967 5968 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 5969 expression = this.expression 5970 5971 if expression: 5972 for arg in self.SET_OP_MODIFIERS: 5973 expr = expression.args.get(arg) 5974 if expr: 5975 this.set(arg, expr.pop()) 5976 5977 return this 5978 5979 def _parse_expression(self) -> exp.Expr | None: 5980 return self._parse_alias(self._parse_assignment()) 5981 5982 def _parse_assignment(self) -> exp.Expr | None: 5983 this = self._parse_disjunction() 5984 if not this and self._next.token_type in self.ASSIGNMENT: 5985 # This allows us to parse <non-identifier token> := <expr> 5986 this = exp.column( 5987 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 5988 ) 5989 5990 while self._match_set(self.ASSIGNMENT): 5991 if isinstance(this, exp.Column) and len(this.parts) == 1: 5992 this = this.this 5993 5994 comments = self._prev_comments 5995 this = self.expression( 5996 self.ASSIGNMENT[self._prev.token_type]( 5997 this=this, expression=self._parse_assignment() 5998 ), 5999 comments=comments, 6000 ) 6001 6002 return this 6003 6004 def _parse_disjunction(self) -> exp.Expr | None: 6005 this = self._parse_conjunction() 6006 while self._match_set(self.DISJUNCTION): 6007 comments = self._prev_comments 6008 this = self.expression( 6009 self.DISJUNCTION[self._prev.token_type]( 6010 this=this, expression=self._parse_conjunction() 6011 ), 6012 comments=comments, 6013 ) 6014 return this 6015 6016 def _parse_conjunction(self) -> exp.Expr | None: 6017 this = self._parse_equality() 6018 while self._match_set(self.CONJUNCTION): 6019 comments = self._prev_comments 6020 this = self.expression( 6021 self.CONJUNCTION[self._prev.token_type]( 6022 this=this, expression=self._parse_equality() 6023 ), 6024 comments=comments, 6025 ) 6026 return this 6027 6028 def _parse_equality(self) -> exp.Expr | None: 6029 this = self._parse_comparison() 6030 while self._match_set(self.EQUALITY): 6031 comments = self._prev_comments 6032 this = self.expression( 6033 self.EQUALITY[self._prev.token_type]( 6034 this=this, expression=self._parse_comparison() 6035 ), 6036 comments=comments, 6037 ) 6038 return this 6039 6040 def _parse_comparison(self) -> exp.Expr | None: 6041 this = self._parse_range() 6042 while self._match_set(self.COMPARISON): 6043 comments = self._prev_comments 6044 this = self.expression( 6045 self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()), 6046 comments=comments, 6047 ) 6048 return this 6049 6050 def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6051 this = this or self._parse_bitwise() 6052 6053 while True: 6054 negate = self._match(TokenType.NOT) 6055 if self._match_set(self.RANGE_PARSERS): 6056 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6057 if not expression: 6058 return this 6059 6060 this = expression 6061 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6062 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6063 elif self._match(TokenType.NOTNULL): 6064 # Postgres supports ISNULL and NOTNULL for conditions. 6065 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6066 if self.dialect.NORMALIZE_NOT_NULL: 6067 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6068 this = self.expression(exp.Not(this=this)) 6069 else: 6070 this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True)) 6071 else: 6072 if negate: 6073 self._retreat(self._index - 1) 6074 break 6075 6076 if negate: 6077 this = self._negate_range(this) 6078 if self._curr and ( 6079 self._curr.token_type == TokenType.NOT 6080 or self._curr.token_type in self.RANGE_PARSERS 6081 ): 6082 this = self.expression(exp.Paren(this=this)) 6083 6084 return this 6085 6086 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6087 if not this: 6088 return this 6089 6090 expression = this.this if isinstance(this, exp.Escape) else this 6091 if isinstance(expression, (exp.Like, exp.ILike)): 6092 expression.set("negate", True) 6093 return this 6094 6095 return self.expression(exp.Not(this=this)) 6096 6097 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6098 index = self._index - 1 6099 negate = self._match(TokenType.NOT) 6100 6101 if self._match_text_seq("DISTINCT", "FROM"): 6102 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6103 return self.expression(klass(this=this, expression=self._parse_bitwise())) 6104 6105 if self._match(TokenType.JSON): 6106 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6107 6108 if self._match_text_seq("WITH"): 6109 _with = True 6110 elif self._match_text_seq("WITHOUT"): 6111 _with = False 6112 else: 6113 _with = None 6114 6115 unique = self._match(TokenType.UNIQUE) 6116 self._match_text_seq("KEYS") 6117 expression: exp.Expr | None = self.expression( 6118 exp.JSON(this=kind, with_=_with, unique=unique) 6119 ) 6120 else: 6121 expression = self._parse_null() or self._parse_bitwise() 6122 if not expression: 6123 self._retreat(index) 6124 return None 6125 6126 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6127 this = self.expression(exp.Is(this=this, expression=expression, negate=True)) 6128 else: 6129 this = self.expression(exp.Is(this=this, expression=expression)) 6130 this = self.expression(exp.Not(this=this)) if negate else this 6131 6132 return self._parse_column_ops(this) 6133 6134 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6135 unnest = self._parse_unnest(with_alias=False) 6136 if unnest: 6137 this = self.expression(exp.In(this=this, unnest=unnest)) 6138 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6139 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6140 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6141 6142 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6143 this = self.expression( 6144 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6145 ) 6146 else: 6147 this = self.expression(exp.In(this=this, expressions=expressions)) 6148 6149 if matched_l_paren: 6150 self._match_r_paren(this) 6151 elif not self._match(TokenType.R_BRACKET, expression=this): 6152 self.raise_error("Expecting ]") 6153 else: 6154 this = self.expression(exp.In(this=this, field=self._parse_column())) 6155 6156 return this 6157 6158 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6159 symmetric = None 6160 if self._match_text_seq("SYMMETRIC"): 6161 symmetric = True 6162 elif self._match_text_seq("ASYMMETRIC"): 6163 symmetric = False 6164 6165 low = self._parse_bitwise() 6166 self._match(TokenType.AND) 6167 high = self._parse_bitwise() 6168 6169 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6170 6171 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6172 if not self._match(TokenType.ESCAPE): 6173 return this 6174 return self.expression( 6175 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6176 ) 6177 6178 def _parse_interval_span( 6179 self, this: exp.Expr, parse_function_unit: bool = True 6180 ) -> exp.Interval: 6181 # handle day-time format interval span with omitted units: 6182 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6183 interval_span_units_omitted = None 6184 if ( 6185 this 6186 and this.is_string 6187 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6188 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6189 ): 6190 index = self._index 6191 6192 # Var "TO" Var 6193 first_unit = self._parse_var(any_token=True, upper=True) 6194 second_unit = None 6195 if first_unit and self._match_text_seq("TO"): 6196 second_unit = self._parse_var(any_token=True, upper=True) 6197 6198 interval_span_units_omitted = not (first_unit and second_unit) 6199 6200 self._retreat(index) 6201 6202 unit_index = self._index 6203 if interval_span_units_omitted: 6204 unit = None 6205 else: 6206 # Only attempt to parse a unit if the current token can actually be one, so that a 6207 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6208 is_unit = self._curr is not None and ( 6209 self._curr.token_type == TokenType.VAR 6210 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6211 ) 6212 unit = self._parse_function() if parse_function_unit and is_unit else None 6213 if not unit and is_unit: 6214 unit = self._parse_var(any_token=True, upper=True) 6215 6216 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6217 # each INTERVAL expression into this canonical form so it's easy to transpile 6218 if this and this.is_number: 6219 try: 6220 this = exp.Literal.string(this.to_py()) 6221 except ValueError: 6222 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6223 elif this and this.is_string: 6224 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6225 if parts and unit: 6226 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6227 unit = None 6228 self._retreat(unit_index) 6229 6230 if len(parts) == 1: 6231 this = exp.Literal.string(parts[0][0]) 6232 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6233 6234 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6235 unit = self.expression( 6236 exp.IntervalSpan( 6237 this=unit, 6238 expression=self._parse_function() 6239 or self._parse_var(any_token=True, upper=True), 6240 ) 6241 ) 6242 6243 return self.expression(exp.Interval(this=this, unit=unit)) 6244 6245 def _parse_interval( 6246 self, require_interval: bool = True, parse_function_unit: bool = True 6247 ) -> exp.Add | exp.Interval | None: 6248 index = self._index 6249 6250 if not self._match(TokenType.INTERVAL) and require_interval: 6251 return None 6252 6253 if self._match(TokenType.STRING, advance=False): 6254 this = self._parse_primary() 6255 else: 6256 this = self._parse_term() 6257 6258 if not this or ( 6259 isinstance(this, exp.Column) 6260 and not this.table 6261 and not this.this.quoted 6262 and self._curr 6263 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6264 ): 6265 self._retreat(index) 6266 return None 6267 6268 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6269 6270 index = self._index 6271 self._match(TokenType.PLUS) 6272 6273 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6274 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6275 return self.expression( 6276 exp.Add( 6277 this=interval, 6278 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6279 ) 6280 ) 6281 6282 self._retreat(index) 6283 return interval 6284 6285 def _parse_bitwise(self) -> exp.Expr | None: 6286 this = self._parse_term() 6287 6288 while True: 6289 if self._match_set(self.BITWISE): 6290 this = self.expression( 6291 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6292 ) 6293 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6294 this = self.expression( 6295 exp.DPipe( 6296 this=this, 6297 expression=self._parse_term(), 6298 safe=not self.dialect.STRICT_STRING_CONCAT, 6299 ) 6300 ) 6301 elif self._match(TokenType.DQMARK): 6302 this = self.expression( 6303 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6304 ) 6305 elif self._match_pair(TokenType.LT, TokenType.LT): 6306 this = self.expression( 6307 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6308 ) 6309 elif self._match_pair(TokenType.GT, TokenType.GT): 6310 this = self.expression( 6311 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6312 ) 6313 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6314 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6315 else: 6316 break 6317 6318 return this 6319 6320 def _parse_term(self) -> exp.Expr | None: 6321 this = self._parse_factor() 6322 6323 while self._match_set(self.TERM): 6324 klass = self.TERM[self._prev.token_type] 6325 comments = self._prev_comments 6326 expression = self._parse_factor() 6327 6328 this = self.expression(klass(this=this, expression=expression), comments=comments) 6329 6330 if isinstance(this, exp.Collate): 6331 self._normalize_collate(this) 6332 6333 return this 6334 6335 def _normalize_collate(self, collate: exp.Collate) -> None: 6336 expr = collate.expression 6337 6338 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6339 # fallback to Identifier / Var 6340 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6341 ident = expr.this 6342 if isinstance(ident, exp.Identifier): 6343 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6344 6345 def _parse_factor(self) -> exp.Expr | None: 6346 parse_method = self._parse_factor_operand 6347 this = self._parse_at_time_zone(parse_method()) 6348 6349 while self._match_set(self.FACTOR): 6350 klass = self.FACTOR[self._prev.token_type] 6351 comments = self._prev_comments 6352 expression = parse_method() 6353 6354 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6355 self._retreat(self._index - 1) 6356 return this 6357 6358 this = self.expression(klass(this=this, expression=expression), comments=comments) 6359 6360 if isinstance(this, exp.Div): 6361 this.set("typed", self.dialect.TYPED_DIVISION) 6362 this.set("safe", self.dialect.SAFE_DIVISION) 6363 6364 return this 6365 6366 def _parse_factor_operand(self) -> exp.Expr | None: 6367 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6368 6369 def _parse_exponent(self) -> exp.Expr | None: 6370 this = self._parse_unary() 6371 while self._match_set(self.EXPONENT): 6372 comments = self._prev_comments 6373 this = self.expression( 6374 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6375 comments=comments, 6376 ) 6377 return this 6378 6379 def _parse_unary(self) -> exp.Expr | None: 6380 if self._match_set(self.UNARY_PARSERS): 6381 return self.UNARY_PARSERS[self._prev.token_type](self) 6382 return self._parse_type() 6383 6384 def _parse_type( 6385 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6386 ) -> exp.Expr | None: 6387 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6388 return atom 6389 6390 if interval := parse_interval and self._parse_interval(): 6391 return self._parse_column_ops(interval) 6392 6393 index = self._index 6394 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6395 6396 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6397 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6398 if isinstance(data_type, exp.Cast): 6399 # This constructor can contain ops directly after it, for instance struct unnesting: 6400 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6401 return self._parse_column_ops(data_type) 6402 6403 if data_type: 6404 index2 = self._index 6405 this = self._parse_primary() 6406 6407 if isinstance(this, exp.Literal): 6408 literal = this.name 6409 this = self._parse_column_ops(this) 6410 6411 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6412 if parser: 6413 return parser(self, this, data_type) 6414 6415 if ( 6416 self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR 6417 and data_type.is_type(exp.DType.TIMESTAMP) 6418 and TIME_ZONE_RE.search(literal) 6419 ): 6420 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6421 6422 return self.expression(exp.Cast(this=this, to=data_type)) 6423 6424 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6425 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6426 # 6427 # If the index difference here is greater than 1, that means the parser itself must have 6428 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6429 # 6430 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6431 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6432 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6433 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6434 # 6435 # In these cases, we don't really want to return the converted type, but instead retreat 6436 # and try to parse a Column or Identifier in the section below. 6437 if data_type.expressions and index2 - index > 1: 6438 self._retreat(index2) 6439 return self._parse_column_ops(data_type) 6440 6441 self._retreat(index) 6442 6443 if fallback_to_identifier: 6444 return self._parse_id_var() 6445 6446 return self._parse_column() 6447 6448 def _parse_type_size(self) -> exp.DataTypeParam | None: 6449 this = self._parse_type() 6450 if not this: 6451 return None 6452 6453 if isinstance(this, exp.Column) and not this.table: 6454 this = exp.var(this.name.upper()) 6455 6456 return self.expression( 6457 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6458 ) 6459 6460 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6461 type_name = identifier.name 6462 6463 while self._match(TokenType.DOT): 6464 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6465 6466 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6467 6468 def _parse_types( 6469 self, 6470 check_func: bool = False, 6471 schema: bool = False, 6472 allow_identifiers: bool = True, 6473 with_collation: bool = False, 6474 ) -> exp.Expr | None: 6475 index = self._index 6476 this: exp.Expr | None = None 6477 6478 if self._match_set(self.TYPE_TOKENS): 6479 type_token = self._prev.token_type 6480 else: 6481 type_token = None 6482 identifier = allow_identifiers and self._parse_id_var( 6483 any_token=False, tokens=(TokenType.VAR,) 6484 ) 6485 if isinstance(identifier, exp.Identifier): 6486 try: 6487 tokens = self.dialect.tokenize(identifier.name) 6488 except TokenError: 6489 tokens = None 6490 6491 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6492 if len(tokens) > 1: 6493 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6494 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6495 this = self._parse_user_defined_type(identifier) 6496 else: 6497 self._retreat(self._index - 1) 6498 return None 6499 else: 6500 return None 6501 6502 if type_token == TokenType.PSEUDO_TYPE: 6503 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6504 6505 if type_token == TokenType.OBJECT_IDENTIFIER: 6506 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6507 6508 # https://materialize.com/docs/sql/types/map/ 6509 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6510 key_type = self._parse_types( 6511 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6512 ) 6513 if not self._match(TokenType.FARROW): 6514 self._retreat(index) 6515 return None 6516 6517 value_type = self._parse_types( 6518 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6519 ) 6520 if not self._match(TokenType.R_BRACKET): 6521 self._retreat(index) 6522 return None 6523 6524 return exp.DataType( 6525 this=exp.DType.MAP, 6526 expressions=[key_type, value_type], 6527 nested=True, 6528 ) 6529 6530 nested = type_token in self.NESTED_TYPE_TOKENS 6531 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6532 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6533 expressions = None 6534 maybe_func = False 6535 6536 if self._match(TokenType.L_PAREN): 6537 if is_struct: 6538 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6539 elif nested: 6540 expressions = self._parse_csv( 6541 lambda: self._parse_types( 6542 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6543 ) 6544 ) 6545 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6546 this = expressions[0] 6547 this.set("nullable", True) 6548 self._match_r_paren() 6549 return this 6550 elif type_token in self.ENUM_TYPE_TOKENS: 6551 expressions = self._parse_csv(self._parse_equality) 6552 elif type_token == TokenType.JSON: 6553 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6554 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6555 expressions = self._parse_csv(self._parse_json_type_arg) 6556 elif is_aggregate: 6557 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6558 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6559 ) 6560 if not func_or_ident: 6561 return None 6562 expressions = [func_or_ident] 6563 if self._match(TokenType.COMMA): 6564 expressions.extend( 6565 self._parse_csv( 6566 lambda: self._parse_types( 6567 check_func=check_func, 6568 schema=schema, 6569 allow_identifiers=allow_identifiers, 6570 ) 6571 ) 6572 ) 6573 else: 6574 expressions = self._parse_csv(self._parse_type_size) 6575 6576 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6577 if type_token == TokenType.VECTOR and len(expressions) == 2: 6578 expressions = self._parse_vector_expressions(expressions) 6579 6580 if not self._match(TokenType.R_PAREN): 6581 self._retreat(index) 6582 return None 6583 6584 maybe_func = True 6585 6586 values: list[exp.Expr] | None = None 6587 6588 if nested and self._match(TokenType.LT): 6589 if is_struct: 6590 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6591 else: 6592 expressions = self._parse_csv( 6593 lambda: self._parse_types( 6594 check_func=check_func, 6595 schema=schema, 6596 allow_identifiers=allow_identifiers, 6597 with_collation=True, 6598 ) 6599 ) 6600 6601 if not self._match(TokenType.GT): 6602 self.raise_error("Expecting >") 6603 6604 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6605 values = self._parse_csv(self._parse_disjunction) 6606 if not values and is_struct: 6607 values = None 6608 self._retreat(self._index - 1) 6609 else: 6610 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6611 6612 if type_token in self.TIMESTAMPS: 6613 if self._match_text_seq("WITH", "TIME", "ZONE"): 6614 maybe_func = False 6615 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6616 this = exp.DataType(this=tz_type, expressions=expressions) 6617 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6618 maybe_func = False 6619 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6620 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6621 maybe_func = False 6622 elif type_token == TokenType.INTERVAL: 6623 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6624 unit = self._parse_var(upper=True) 6625 if self._match_text_seq("TO"): 6626 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6627 6628 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6629 else: 6630 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6631 elif type_token == TokenType.VOID: 6632 this = exp.DataType(this=exp.DType.NULL) 6633 6634 if maybe_func and check_func: 6635 index2 = self._index 6636 peek = self._parse_string() 6637 6638 if not peek: 6639 self._retreat(index) 6640 return None 6641 6642 self._retreat(index2) 6643 6644 if not this: 6645 assert type_token is not None 6646 if self._match_text_seq("UNSIGNED"): 6647 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6648 if not unsigned_type_token: 6649 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6650 6651 type_token = unsigned_type_token or type_token 6652 6653 # NULLABLE without parentheses can be a column (Presto/Trino) 6654 if type_token == TokenType.NULLABLE and not expressions: 6655 self._retreat(index) 6656 return None 6657 6658 this = exp.DataType( 6659 this=exp.DType[type_token.name], 6660 expressions=expressions, 6661 nested=nested, 6662 ) 6663 6664 # Empty arrays/structs are allowed 6665 if values is not None: 6666 cls = exp.Struct if is_struct else exp.Array 6667 this = exp.cast(cls(expressions=values), this, copy=False) 6668 6669 elif expressions: 6670 this.set("expressions", expressions) 6671 6672 # https://materialize.com/docs/sql/types/list/#type-name 6673 while self._match(TokenType.LIST): 6674 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 6675 6676 index = self._index 6677 6678 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 6679 matched_array = self._match(TokenType.ARRAY) 6680 6681 while self._curr: 6682 datatype_token = self._prev.token_type 6683 matched_l_bracket = self._match(TokenType.L_BRACKET) 6684 6685 if (not matched_l_bracket and not matched_array) or ( 6686 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 6687 ): 6688 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 6689 # not to be confused with the fixed size array parsing 6690 break 6691 6692 matched_array = False 6693 values = self._parse_csv(self._parse_disjunction) or None 6694 if ( 6695 values 6696 and not schema 6697 and ( 6698 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 6699 or datatype_token == TokenType.ARRAY 6700 or not self._match(TokenType.R_BRACKET, advance=False) 6701 ) 6702 ): 6703 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 6704 # ARRAY[1] should retreat and instead be parsed into exp.Array in contrast to INT[x][y] which denotes a fixed-size array data type 6705 self._retreat(index) 6706 break 6707 6708 this = exp.DataType( 6709 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 6710 ) 6711 self._match(TokenType.R_BRACKET) 6712 6713 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 6714 converter = self.TYPE_CONVERTERS.get(this.this) 6715 if converter: 6716 this = converter(t.cast(exp.DataType, this)) 6717 6718 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 6719 this.set("collate", self._parse_identifier() or self._parse_column()) 6720 6721 return this 6722 6723 def _parse_json_type_arg(self) -> exp.Expr | None: 6724 """Parse a single argument to ClickHouse's JSON type.""" 6725 6726 # SKIP col or SKIP REGEXP 'pattern' 6727 if self._match_text_seq("SKIP"): 6728 regexp = self._match(TokenType.RLIKE) 6729 arg = self._parse_column() 6730 if isinstance(arg, exp.Column): 6731 arg = arg.to_dot() 6732 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 6733 6734 param_or_col = self._parse_column() 6735 if not isinstance(param_or_col, exp.Column): 6736 return None 6737 6738 # Parameter: name=value (e.g., max_dynamic_paths=2) 6739 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 6740 param = param_or_col.name 6741 value = self._parse_primary() 6742 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 6743 6744 # Column type hint: col_name Type 6745 col = param_or_col.to_dot() 6746 kind = self._parse_types(check_func=False, allow_identifiers=False) 6747 return self.expression(exp.ColumnDef(this=col, kind=kind)) 6748 6749 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 6750 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 6751 6752 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 6753 index = self._index 6754 6755 if ( 6756 self._curr 6757 and self._next 6758 and self._curr.token_type in self.TYPE_TOKENS 6759 and self._next.token_type in self.TYPE_TOKENS 6760 ): 6761 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 6762 # type token. Without this, the list will be parsed as a type and we'll eventually crash 6763 this = self._parse_id_var() 6764 else: 6765 this = ( 6766 self._parse_type(parse_interval=False, fallback_to_identifier=True) 6767 or self._parse_id_var() 6768 ) 6769 6770 self._match(TokenType.COLON) 6771 6772 if ( 6773 type_required 6774 and not isinstance(this, exp.DataType) 6775 and not self._match_set(self.TYPE_TOKENS, advance=False) 6776 ): 6777 self._retreat(index) 6778 return self._parse_types() 6779 6780 return self._parse_column_def(this) 6781 6782 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 6783 if not self._match_text_seq("AT", "TIME", "ZONE"): 6784 return this 6785 return self._parse_at_time_zone( 6786 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 6787 ) 6788 6789 def _parse_atom(self) -> exp.Expr | None: 6790 if ( 6791 self._curr.token_type in self.IDENTIFIER_TOKENS 6792 and (column := self._parse_column()) is not None 6793 ): 6794 return column 6795 6796 token = self._curr 6797 token_type = token.token_type 6798 6799 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 6800 return None 6801 6802 next_type = self._next.token_type 6803 6804 if ( 6805 next_type in self.COLUMN_OPERATORS 6806 or next_type in self.COLUMN_POSTFIX_TOKENS 6807 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 6808 ): 6809 return None 6810 6811 self._advance() 6812 return primary_parser(self, token) 6813 6814 def _parse_column(self) -> exp.Expr | None: 6815 column: exp.Expr | None = self._parse_column_parts_fast() 6816 if column is None: 6817 this = self._parse_column_reference() 6818 if not this: 6819 this = self._parse_bracket(this) 6820 column = self._parse_column_ops(this) if this else this 6821 6822 if column: 6823 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 6824 column.set("join_mark", self._match(TokenType.JOIN_MARKER)) 6825 if self.COLON_IS_VARIANT_EXTRACT: 6826 column = self._parse_colon_as_variant_extract(column) 6827 6828 return column 6829 6830 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 6831 """Fast path for simple column and dot references (a, a.b, ...). 6832 6833 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 6834 that nothing complex follows. If it does, retreats and returns None so 6835 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 6836 """ 6837 index = self._index 6838 parts: list[exp.Identifier] | None = None 6839 all_comments: list[str] | None = None 6840 6841 while self._match_set(self.IDENTIFIER_TOKENS): 6842 token = self._prev 6843 comments = self._prev_comments 6844 6845 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 6846 self._retreat(index) 6847 return None 6848 6849 has_dot = self._match(TokenType.DOT) 6850 curr_tt = self._curr.token_type 6851 6852 if not has_dot: 6853 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 6854 self._retreat(index) 6855 return None 6856 elif curr_tt not in self.IDENTIFIER_TOKENS: 6857 self._retreat(index) 6858 return None 6859 6860 if parts is None: 6861 parts = [] 6862 6863 if comments: 6864 if all_comments is None: 6865 all_comments = [] 6866 all_comments.extend(comments) 6867 self._prev_comments = [] 6868 6869 parts.append( 6870 self.expression( 6871 exp.Identifier( 6872 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 6873 ), 6874 token, 6875 ) 6876 ) 6877 6878 if not has_dot: 6879 break 6880 6881 if parts is None: 6882 return None 6883 6884 n = len(parts) 6885 6886 if n == 1: 6887 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 6888 elif n == 2: 6889 column = exp.Column(this=parts[1], table=parts[0]) 6890 elif n == 3: 6891 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 6892 else: 6893 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 6894 6895 for i in range(4, n): 6896 column = exp.Dot(this=column, expression=parts[i]) 6897 6898 if all_comments: 6899 column.add_comments(all_comments) 6900 6901 return column 6902 6903 def _parse_column_reference(self) -> exp.Expr | None: 6904 this = self._parse_field() 6905 if ( 6906 not this 6907 and self._match(TokenType.VALUES, advance=False) 6908 and self.VALUES_FOLLOWED_BY_PAREN 6909 and (not self._next or self._next.token_type != TokenType.L_PAREN) 6910 ): 6911 this = self._parse_id_var() 6912 6913 if isinstance(this, exp.Identifier): 6914 # We bubble up comments from the Identifier to the Column 6915 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 6916 6917 return this 6918 6919 def _build_json_extract( 6920 self, 6921 this: exp.Expr | None, 6922 path_parts: list[exp.JSONPathPart], 6923 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 6924 if len(path_parts) > 1: 6925 this = self.expression( 6926 exp.JSONExtract( 6927 this=this, 6928 expression=exp.JSONPath(expressions=path_parts), 6929 variant_extract=True, 6930 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 6931 ) 6932 ) 6933 path_parts = [exp.JSONPathRoot()] 6934 6935 return this, path_parts 6936 6937 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 6938 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 6939 6940 while self._match(TokenType.COLON): 6941 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 6942 this, path_parts = self._build_json_extract(this, path_parts) 6943 6944 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 6945 6946 if key: 6947 quoted = isinstance(key, exp.Identifier) and key.quoted 6948 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 6949 6950 while True: 6951 if self._match(TokenType.DOT): 6952 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 6953 6954 if next_key: 6955 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 6956 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 6957 elif self._match(TokenType.L_BRACKET): 6958 bracket_expr = self._parse_bracket_key_value() 6959 6960 if not self._match(TokenType.R_BRACKET): 6961 self.raise_error("Expected ]") 6962 6963 if bracket_expr: 6964 if bracket_expr.is_string: 6965 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 6966 elif bracket_expr.is_star: 6967 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 6968 elif bracket_expr.is_number: 6969 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 6970 else: 6971 this, path_parts = self._build_json_extract(this, path_parts) 6972 6973 this = self.expression( 6974 exp.Bracket( 6975 this=this, expressions=[bracket_expr], json_access=True 6976 ), 6977 ) 6978 6979 elif self._match(TokenType.DCOLON): 6980 this, path_parts = self._build_json_extract(this, path_parts) 6981 6982 cast_type = self._parse_types() 6983 if cast_type: 6984 this = self.expression(exp.Cast(this=this, to=cast_type)) 6985 else: 6986 self.raise_error("Expected type after '::'") 6987 else: 6988 break 6989 6990 this, _ = self._build_json_extract(this, path_parts) 6991 6992 return this 6993 6994 def _parse_dcolon(self) -> exp.Expr | None: 6995 return self._parse_types() 6996 6997 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 6998 while self._curr.token_type in self.BRACKETS: 6999 this = self._parse_bracket(this) 7000 7001 column_operators = self.COLUMN_OPERATORS 7002 cast_column_operators = self.CAST_COLUMN_OPERATORS 7003 while self._curr: 7004 op_token = self._curr.token_type 7005 7006 if op_token not in column_operators: 7007 break 7008 op = column_operators[op_token] 7009 self._advance() 7010 7011 if op_token in cast_column_operators: 7012 field = self._parse_dcolon() 7013 if not field: 7014 self.raise_error("Expected type") 7015 elif op and self._curr: 7016 field = self._parse_column_reference() or self._parse_bitwise() 7017 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7018 field = self._parse_column_ops(field) 7019 else: 7020 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7021 field = self._parse_field(any_token=True, anonymous_func=True) 7022 7023 # In t.true, t.null we should produce an Identifier node 7024 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7025 field = self.expression( 7026 exp.Identifier(this=self._prev.text), 7027 comments=field.comments, 7028 ) 7029 7030 # Function calls can be qualified, e.g., x.y.FOO() 7031 # This converts the final AST to a series of Dots leading to the function call 7032 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7033 if isinstance(field, (exp.Func, exp.Window)) and this: 7034 this = this.transform( 7035 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7036 ) 7037 7038 if op: 7039 this = op(self, this, field) 7040 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7041 this = self.expression( 7042 exp.Column( 7043 this=field, 7044 table=this.this, 7045 db=this.args.get("table"), 7046 catalog=this.args.get("db"), 7047 ), 7048 comments=this.comments, 7049 ) 7050 elif isinstance(field, exp.Window): 7051 # Move the exp.Dot's to the window's function 7052 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7053 field.set("this", window_func) 7054 this = field 7055 else: 7056 this = self.expression(exp.Dot(this=this, expression=field)) 7057 7058 if field and field.comments: 7059 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7060 7061 this = self._parse_bracket(this) 7062 7063 return this 7064 7065 def _parse_paren(self) -> exp.Expr | None: 7066 if not self._match(TokenType.L_PAREN): 7067 return None 7068 7069 comments = self._prev_comments 7070 query = self._parse_select() 7071 7072 if query: 7073 expressions = [query] 7074 else: 7075 expressions = self._parse_expressions() 7076 7077 this = seq_get(expressions, 0) 7078 7079 if not this and self._match(TokenType.R_PAREN, advance=False): 7080 this = self.expression(exp.Tuple()) 7081 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7082 this = self.expression(exp.Tuple(expressions=expressions)) 7083 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7084 this = self._parse_subquery(this=this, parse_alias=False) 7085 elif isinstance(this, (exp.Subquery, exp.Values)): 7086 this = self._parse_subquery( 7087 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7088 parse_alias=False, 7089 ) 7090 else: 7091 this = self.expression(exp.Paren(this=this)) 7092 7093 if this: 7094 this.add_comments(comments) 7095 7096 self._match_r_paren(expression=this) 7097 7098 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7099 return self._parse_window(this) 7100 7101 return this 7102 7103 def _parse_primary(self) -> exp.Expr | None: 7104 if self._match_set(self.PRIMARY_PARSERS): 7105 token_type = self._prev.token_type 7106 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7107 7108 if token_type == TokenType.STRING: 7109 expressions = [primary] 7110 while self._match(TokenType.STRING, advance=False): 7111 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7112 self.raise_error( 7113 "Adjacent string literals need to be separated by whitespace or comments" 7114 ) 7115 7116 self._advance() 7117 expressions.append(exp.Literal.string(self._prev.text)) 7118 7119 if len(expressions) > 1: 7120 return self.expression( 7121 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7122 ) 7123 7124 return primary 7125 7126 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7127 return exp.Literal.number(f"0.{self._prev.text}") 7128 7129 return self._parse_paren() 7130 7131 def _parse_field( 7132 self, 7133 any_token: bool = False, 7134 tokens: t.Collection[TokenType] | None = None, 7135 anonymous_func: bool = False, 7136 ) -> exp.Expr | None: 7137 if anonymous_func: 7138 field = ( 7139 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7140 or self._parse_primary() 7141 ) 7142 else: 7143 field = self._parse_primary() or self._parse_function( 7144 anonymous=anonymous_func, any_token=any_token 7145 ) 7146 return field or self._parse_id_var(any_token=any_token, tokens=tokens) 7147 7148 def _parse_function( 7149 self, 7150 functions: dict[str, t.Callable] | None = None, 7151 anonymous: bool = False, 7152 optional_parens: bool = True, 7153 any_token: bool = False, 7154 ) -> exp.Expr | None: 7155 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7156 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7157 fn_syntax = False 7158 if ( 7159 self._match(TokenType.L_BRACE, advance=False) 7160 and self._next 7161 and self._next.text.upper() == "FN" 7162 ): 7163 self._advance(2) 7164 fn_syntax = True 7165 7166 func = self._parse_function_call( 7167 functions=functions, 7168 anonymous=anonymous, 7169 optional_parens=optional_parens, 7170 any_token=any_token, 7171 ) 7172 7173 if fn_syntax: 7174 self._match(TokenType.R_BRACE) 7175 7176 return func 7177 7178 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7179 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7180 7181 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7182 args = self._parse_function_args(alias=False) 7183 if not args: 7184 self.raise_error("Expected at least one argument") 7185 7186 # Wrapped so the connector keeps its precedence in the parent context 7187 return exp.Paren(this=connector(*args, copy=False)) 7188 7189 def _parse_function_call( 7190 self, 7191 functions: dict[str, t.Callable] | None = None, 7192 anonymous: bool = False, 7193 optional_parens: bool = True, 7194 any_token: bool = False, 7195 ) -> exp.Expr | None: 7196 if not self._curr: 7197 return None 7198 7199 comments = self._curr.comments 7200 prev = self._prev 7201 token = self._curr 7202 token_type = self._curr.token_type 7203 this: str | exp.Expr = self._curr.text 7204 upper = self._curr.text.upper() 7205 7206 after_dot = prev.token_type == TokenType.DOT 7207 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7208 if ( 7209 optional_parens 7210 and parser 7211 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7212 and not after_dot 7213 ): 7214 self._advance() 7215 return self._parse_window(parser(self)) 7216 7217 if self._next.token_type != TokenType.L_PAREN: 7218 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7219 self._advance() 7220 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7221 7222 return None 7223 7224 if any_token: 7225 if token_type in self.RESERVED_TOKENS: 7226 return None 7227 elif token_type not in self.FUNC_TOKENS: 7228 return None 7229 7230 self._advance(2) 7231 7232 parser = self.FUNCTION_PARSERS.get(upper) 7233 if parser and not anonymous: 7234 result = parser(self) 7235 else: 7236 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7237 7238 if subquery_predicate: 7239 expr = None 7240 if self._curr.token_type in self.SUBQUERY_TOKENS: 7241 expr = self._parse_select() 7242 self._match_r_paren() 7243 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7244 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7245 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7246 self._advance(-1) 7247 expr = self._parse_bitwise() 7248 7249 if expr: 7250 return self.expression(subquery_predicate(this=expr), comments=comments) 7251 7252 if functions is None: 7253 functions = self.FUNCTIONS 7254 7255 function = functions.get(upper) 7256 known_function = function and not anonymous 7257 7258 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7259 args = self._parse_function_args(alias) 7260 7261 post_func_comments = self._curr.comments if self._curr else None 7262 if known_function and post_func_comments: 7263 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7264 # call we'll construct it as exp.Anonymous, even if it's "known" 7265 if any( 7266 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7267 for comment in post_func_comments 7268 ): 7269 known_function = False 7270 7271 if alias and known_function: 7272 args = self._kv_to_prop_eq(args) 7273 7274 if known_function: 7275 func_builder = t.cast(t.Callable, function) 7276 7277 # mypyc compiled functions don't have __code__, so we use 7278 # try/except to check if func_builder accepts 'dialect'. 7279 try: 7280 func = func_builder(args) 7281 except TypeError: 7282 func = func_builder(args, dialect=self.dialect) 7283 7284 func = self.validate_expression(func, args) 7285 if self.dialect.PRESERVE_ORIGINAL_NAMES: 7286 func.meta["name"] = this 7287 7288 result = func 7289 else: 7290 if token_type == TokenType.IDENTIFIER: 7291 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7292 7293 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7294 7295 result = result.update_positions(token) 7296 7297 if isinstance(result, exp.Expr): 7298 result.add_comments(comments) 7299 7300 if parser: 7301 self._match(TokenType.R_PAREN, expression=result) 7302 else: 7303 self._match_r_paren(result) 7304 return self._parse_window(result) 7305 7306 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7307 return expression 7308 7309 def _kv_to_prop_eq( 7310 self, expressions: list[exp.Expr], parse_map: bool = False 7311 ) -> list[exp.Expr]: 7312 transformed = [] 7313 7314 for index, e in enumerate(expressions): 7315 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7316 if isinstance(e, exp.Alias): 7317 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7318 7319 if not isinstance(e, exp.PropertyEQ): 7320 e = self.expression( 7321 exp.PropertyEQ( 7322 this=e.this if parse_map else exp.to_identifier(e.this.name), 7323 expression=e.expression, 7324 ) 7325 ) 7326 7327 if isinstance(e.this, exp.Column): 7328 e.this.replace(e.this.this) 7329 else: 7330 e = self._to_prop_eq(e, index) 7331 7332 transformed.append(e) 7333 7334 return transformed 7335 7336 def _parse_function_properties(self) -> exp.Properties | None: 7337 # Skip the generic `key = value` fallback in _parse_property since this 7338 # runs post-AS where a function body like `name = expr` can be misread 7339 # as a property. 7340 properties = [] 7341 while True: 7342 if self._match_texts(self.PROPERTY_PARSERS): 7343 keyword = self._prev.text.upper() 7344 prop = self.PROPERTY_PARSERS[keyword](self) 7345 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7346 keyword = self._prev.text.upper() 7347 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7348 else: 7349 break 7350 if not prop: 7351 self.raise_error(f"Failed to parse property '{keyword}'") 7352 break 7353 for p in ensure_list(prop): 7354 properties.append(p) 7355 7356 return self.expression(exp.Properties(expressions=properties)) if properties else None 7357 7358 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7359 return self._parse_statement() 7360 7361 def _parse_function_parameter(self) -> exp.Expr | None: 7362 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7363 7364 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7365 this = self._parse_table_parts(schema=True) 7366 7367 if not self._match(TokenType.L_PAREN): 7368 return this 7369 7370 expressions = self._parse_csv(self._parse_function_parameter) 7371 self._match_r_paren() 7372 return self.expression( 7373 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7374 ) 7375 7376 def _parse_macro_overloads( 7377 self, 7378 this: exp.UserDefinedFunction, 7379 first_body: exp.Expr, 7380 first_is_table: bool = False, 7381 ) -> exp.MacroOverloads: 7382 overloads = [ 7383 self.expression( 7384 exp.MacroOverload( 7385 this=first_body, 7386 expressions=this.expressions or None, 7387 is_table=first_is_table, 7388 ) 7389 ) 7390 ] 7391 this.set("expressions", None) 7392 this.set("wrapped", False) 7393 7394 while self._match(TokenType.COMMA): 7395 if not self._match(TokenType.L_PAREN): 7396 break 7397 7398 params = self._parse_csv(self._parse_function_parameter) 7399 self._match_r_paren() 7400 7401 if not self._match(TokenType.ALIAS): 7402 break 7403 7404 is_table = self._match(TokenType.TABLE) 7405 body = self._parse_expression() 7406 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7407 overloads.append(self.expression(macro)) 7408 7409 return self.expression(exp.MacroOverloads(expressions=overloads)) 7410 7411 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7412 literal = self._parse_primary() 7413 if literal: 7414 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7415 7416 return self._identifier_expression(token) 7417 7418 def _parse_session_parameter(self) -> exp.SessionParameter: 7419 kind = None 7420 this = self._parse_id_var() or self._parse_primary() 7421 7422 if this and self._match(TokenType.DOT): 7423 kind = this.name 7424 this = self._parse_var() or self._parse_primary() 7425 7426 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7427 7428 def _parse_lambda_arg(self) -> exp.Expr | None: 7429 return self._parse_id_var() 7430 7431 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7432 next_token_type = self._next.token_type 7433 7434 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7435 if ( 7436 next_token_type in self.LAMBDA_ARG_TERMINATORS 7437 and (atom := self._parse_atom()) is not None 7438 ): 7439 return atom 7440 7441 index = self._index 7442 7443 if self._match(TokenType.L_PAREN): 7444 expressions = t.cast( 7445 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7446 ) 7447 7448 if not self._match(TokenType.R_PAREN): 7449 self._retreat(index) 7450 elif self._match_set(self.LAMBDAS): 7451 return self.LAMBDAS[self._prev.token_type](self, expressions) 7452 else: 7453 self._retreat(index) 7454 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7455 expressions = [self._parse_lambda_arg()] 7456 7457 if self._match_set(self.LAMBDAS): 7458 return self.LAMBDAS[self._prev.token_type](self, expressions) 7459 7460 self._retreat(index) 7461 7462 this: exp.Expr | None 7463 7464 if self._match(TokenType.DISTINCT): 7465 this = self.expression( 7466 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7467 ) 7468 else: 7469 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7470 this = self._parse_select_or_expression(alias=alias) 7471 7472 return self._parse_limit( 7473 self._parse_respect_or_ignore_nulls( 7474 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7475 ) 7476 ) 7477 7478 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7479 index = self._index 7480 if not self._match(TokenType.L_PAREN): 7481 return this 7482 7483 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7484 # expr can be of both types 7485 if self._match_set(self.SELECT_START_TOKENS): 7486 self._retreat(index) 7487 return this 7488 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7489 self._match_r_paren() 7490 return self.expression(exp.Schema(this=this, expressions=args)) 7491 7492 def _parse_field_def(self) -> exp.Expr | None: 7493 return self._parse_column_def(self._parse_field(any_token=True)) 7494 7495 def _parse_column_def( 7496 self, this: exp.Expr | None, computed_column: bool = True 7497 ) -> exp.Expr | None: 7498 # column defs are not really columns, they're identifiers 7499 if isinstance(this, exp.Column): 7500 this = this.this 7501 7502 if not computed_column: 7503 self._match(TokenType.ALIAS) 7504 7505 kind = self._parse_types(schema=True) 7506 7507 if self._match_text_seq("FOR", "ORDINALITY"): 7508 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7509 7510 constraints: list[exp.Expr] = [] 7511 7512 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7513 ("ALIAS", "MATERIALIZED") 7514 ): 7515 # Match storage before _parse_types so STORED is not treated as a data type 7516 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7517 persisted = self._prev.text.upper() == "MATERIALIZED" 7518 expression = self._parse_disjunction() 7519 if not persisted: 7520 if self._match_text_seq("PERSISTED"): 7521 persisted = True 7522 elif self._match_texts(("STORED", "VIRTUAL")): 7523 persisted = self._prev.text.upper() == "STORED" 7524 constraint_kind = exp.ComputedColumnConstraint( 7525 this=expression, 7526 persisted=persisted, 7527 data_type=exp.Var(this="AUTO") 7528 if self._match_text_seq("AUTO") 7529 else self._parse_types(), 7530 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7531 ) 7532 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7533 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7534 in_out_constraint = self.expression( 7535 exp.InOutColumnConstraint( 7536 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7537 ) 7538 ) 7539 constraints.append(in_out_constraint) 7540 kind = self._parse_types() 7541 elif ( 7542 kind 7543 and self._match(TokenType.ALIAS, advance=False) 7544 and ( 7545 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7546 or self._next.token_type == TokenType.L_PAREN 7547 ) 7548 ): 7549 self._advance() 7550 constraints.append( 7551 self.expression( 7552 exp.ColumnConstraint( 7553 kind=exp.ComputedColumnConstraint( 7554 this=self._parse_disjunction(), 7555 persisted=self._match_texts(("STORED", "VIRTUAL")) 7556 and self._prev.text.upper() == "STORED", 7557 ) 7558 ) 7559 ) 7560 ) 7561 7562 while True: 7563 constraint = self._parse_column_constraint() 7564 if not constraint: 7565 break 7566 constraints.append(constraint) 7567 7568 if not kind and not constraints: 7569 return this 7570 7571 position = None 7572 if self._match_texts(("FIRST", "AFTER")): 7573 pos = self._prev.text 7574 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7575 7576 return self.expression( 7577 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7578 ) 7579 7580 def _parse_auto_increment( 7581 self, 7582 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7583 start = None 7584 increment = None 7585 order = None 7586 7587 if self._match(TokenType.L_PAREN, advance=False): 7588 args = self._parse_wrapped_csv(self._parse_bitwise) 7589 start = seq_get(args, 0) 7590 increment = seq_get(args, 1) 7591 7592 # The remaining parts form an unordered bag and any of them can be omitted, in which 7593 # case the engine falls back to its own default, so they're parsed independently. 7594 while True: 7595 if self._match_text_seq("START"): 7596 start = self._parse_bitwise() 7597 elif self._match_text_seq("INCREMENT"): 7598 increment = self._parse_bitwise() 7599 elif self._match_text_seq("ORDER"): 7600 order = True 7601 elif self._match_text_seq("NOORDER"): 7602 order = False 7603 else: 7604 break 7605 7606 if start or increment or order is not None: 7607 return exp.GeneratedAsIdentityColumnConstraint( 7608 start=start, increment=increment, this=False, order=order 7609 ) 7610 7611 return exp.AutoIncrementColumnConstraint() 7612 7613 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7614 if not self._match(TokenType.L_PAREN, advance=False): 7615 return None 7616 7617 return self.expression( 7618 exp.CheckColumnConstraint( 7619 this=self._parse_wrapped(self._parse_assignment), 7620 enforced=self._match_text_seq("ENFORCED"), 7621 ) 7622 ) 7623 7624 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7625 if not self._match_text_seq("REFRESH"): 7626 self._retreat(self._index - 1) 7627 return None 7628 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7629 7630 def _parse_compress(self) -> exp.CompressColumnConstraint: 7631 if self._match(TokenType.L_PAREN, advance=False): 7632 return self.expression( 7633 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7634 ) 7635 7636 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7637 7638 def _parse_generated_as_identity( 7639 self, 7640 ) -> ( 7641 exp.GeneratedAsIdentityColumnConstraint 7642 | exp.ComputedColumnConstraint 7643 | exp.GeneratedAsRowColumnConstraint 7644 ): 7645 if self._match_text_seq("BY", "DEFAULT"): 7646 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7647 this = self.expression( 7648 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7649 ) 7650 else: 7651 self._match_text_seq("ALWAYS") 7652 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7653 7654 self._match(TokenType.ALIAS) 7655 7656 if self._match_text_seq("ROW"): 7657 start = self._match_text_seq("START") 7658 if not start: 7659 self._match(TokenType.END) 7660 hidden = self._match_text_seq("HIDDEN") 7661 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 7662 7663 identity = self._match_text_seq("IDENTITY") 7664 7665 if self._match(TokenType.L_PAREN): 7666 if self._match_text_seq("START", "WITH"): 7667 this.set("start", self._parse_bitwise()) 7668 if self._match_text_seq("INCREMENT", "BY"): 7669 this.set("increment", self._parse_bitwise()) 7670 if self._match_text_seq("MINVALUE"): 7671 this.set("minvalue", self._parse_bitwise()) 7672 if self._match_text_seq("MAXVALUE"): 7673 this.set("maxvalue", self._parse_bitwise()) 7674 7675 if self._match_text_seq("CYCLE"): 7676 this.set("cycle", True) 7677 elif self._match_text_seq("NO", "CYCLE"): 7678 this.set("cycle", False) 7679 7680 if not identity: 7681 this.set("expression", self._parse_range()) 7682 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 7683 args = self._parse_csv(self._parse_bitwise) 7684 this.set("start", seq_get(args, 0)) 7685 this.set("increment", seq_get(args, 1)) 7686 7687 self._match_r_paren() 7688 7689 return this 7690 7691 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 7692 self._match_text_seq("LENGTH") 7693 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 7694 7695 def _parse_not_constraint(self) -> exp.Expr | None: 7696 if self._match_text_seq("NULL"): 7697 return self.expression(exp.NotNullColumnConstraint()) 7698 if self._match_text_seq("CASESPECIFIC"): 7699 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 7700 if self._match_text_seq("FOR", "REPLICATION"): 7701 return self.expression(exp.NotForReplicationColumnConstraint()) 7702 7703 # Unconsume the `NOT` token 7704 self._retreat(self._index - 1) 7705 return None 7706 7707 def _parse_column_constraint(self) -> exp.Expr | None: 7708 this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None 7709 7710 procedure_option_follows = ( 7711 self._match(TokenType.WITH, advance=False) 7712 and self._next 7713 and self._next.text.upper() in self.PROCEDURE_OPTIONS 7714 ) 7715 7716 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 7717 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 7718 if not constraint: 7719 self._retreat(self._index - 1) 7720 return None 7721 7722 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 7723 7724 if self._match_text_seq("CHARACTER", "SET"): 7725 return self.expression( 7726 exp.ColumnConstraint( 7727 this=this, 7728 kind=self.expression( 7729 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 7730 ), 7731 ) 7732 ) 7733 7734 return this 7735 7736 def _parse_constraint(self) -> exp.Expr | None: 7737 if not self._match(TokenType.CONSTRAINT): 7738 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 7739 7740 return self.expression( 7741 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 7742 ) 7743 7744 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 7745 constraints = [] 7746 while True: 7747 constraint = self._parse_unnamed_constraint() or self._parse_function() 7748 if not constraint: 7749 break 7750 constraints.append(constraint) 7751 7752 return constraints 7753 7754 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 7755 index = self._index 7756 7757 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 7758 constraints or self.CONSTRAINT_PARSERS 7759 ): 7760 return None 7761 7762 constraint_key = self._prev.text.upper() 7763 if constraint_key not in self.CONSTRAINT_PARSERS: 7764 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 7765 7766 result = self.CONSTRAINT_PARSERS[constraint_key](self) 7767 if not result: 7768 self._retreat(index) 7769 7770 return result 7771 7772 def _parse_unique_key(self) -> exp.Expr | None: 7773 if ( 7774 self._curr 7775 and self._curr.token_type != TokenType.IDENTIFIER 7776 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 7777 ): 7778 return None 7779 return self._parse_id_var(any_token=False) 7780 7781 def _parse_unique(self) -> exp.UniqueColumnConstraint: 7782 self._match_texts(("KEY", "INDEX")) 7783 return self.expression( 7784 exp.UniqueColumnConstraint( 7785 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 7786 this=self._parse_schema(self._parse_unique_key()), 7787 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 7788 on_conflict=self._parse_on_conflict(), 7789 options=self._parse_key_constraint_options(), 7790 ) 7791 ) 7792 7793 def _parse_key_constraint_options(self) -> list[str]: 7794 options = [] 7795 while True: 7796 if not self._curr: 7797 break 7798 7799 if self._match(TokenType.ON): 7800 action = None 7801 on = self._advance_any() and self._prev.text 7802 7803 if self._match_text_seq("NO", "ACTION"): 7804 action = "NO ACTION" 7805 elif self._match_text_seq("CASCADE"): 7806 action = "CASCADE" 7807 elif self._match_text_seq("RESTRICT"): 7808 action = "RESTRICT" 7809 elif self._match_pair(TokenType.SET, TokenType.NULL): 7810 action = "SET NULL" 7811 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 7812 action = "SET DEFAULT" 7813 else: 7814 self.raise_error("Invalid key constraint") 7815 7816 options.append(f"ON {on} {action}") 7817 else: 7818 var = self._parse_var_from_options( 7819 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 7820 ) 7821 if not var: 7822 break 7823 options.append(var.name) 7824 7825 return options 7826 7827 def _parse_references(self, match: bool = True) -> exp.Reference | None: 7828 if match and not self._match(TokenType.REFERENCES): 7829 return None 7830 7831 expressions: list | None = None 7832 this = self._parse_table(schema=True) 7833 options = self._parse_key_constraint_options() 7834 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 7835 7836 def _parse_foreign_key(self) -> exp.ForeignKey: 7837 expressions = ( 7838 self._parse_wrapped_id_vars() 7839 if not self._match(TokenType.REFERENCES, advance=False) 7840 else None 7841 ) 7842 reference = self._parse_references() 7843 on_options = {} 7844 7845 while self._match(TokenType.ON): 7846 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 7847 self.raise_error("Expected DELETE or UPDATE") 7848 7849 kind = self._prev.text.lower() 7850 7851 if self._match_text_seq("NO", "ACTION"): 7852 action = "NO ACTION" 7853 elif self._match(TokenType.SET): 7854 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 7855 action = "SET " + self._prev.text.upper() 7856 else: 7857 self._advance() 7858 action = self._prev.text.upper() 7859 7860 on_options[kind] = action 7861 7862 return self.expression( 7863 exp.ForeignKey( 7864 expressions=expressions, 7865 reference=reference, 7866 options=self._parse_key_constraint_options(), 7867 **on_options, 7868 ) 7869 ) 7870 7871 def _parse_primary_key_part(self) -> exp.Expr | None: 7872 return self._parse_field() 7873 7874 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 7875 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 7876 self._retreat(self._index - 1) 7877 return None 7878 7879 id_vars = self._parse_wrapped_id_vars() 7880 return self.expression( 7881 exp.PeriodForSystemTimeConstraint( 7882 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 7883 ) 7884 ) 7885 7886 def _parse_primary_key( 7887 self, 7888 wrapped_optional: bool = False, 7889 in_props: bool = False, 7890 named_primary_key: bool = False, 7891 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 7892 desc = ( 7893 self._prev.token_type == TokenType.DESC 7894 if self._match_set((TokenType.ASC, TokenType.DESC)) 7895 else None 7896 ) 7897 7898 this = None 7899 if ( 7900 named_primary_key 7901 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 7902 and self._next 7903 and self._next.token_type == TokenType.L_PAREN 7904 ): 7905 this = self._parse_id_var() 7906 7907 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 7908 return self.expression( 7909 exp.PrimaryKeyColumnConstraint( 7910 desc=desc, options=self._parse_key_constraint_options() 7911 ) 7912 ) 7913 7914 expressions = self._parse_wrapped_csv( 7915 self._parse_primary_key_part, optional=wrapped_optional 7916 ) 7917 7918 return self.expression( 7919 exp.PrimaryKey( 7920 this=this, 7921 expressions=expressions, 7922 include=self._parse_index_params(), 7923 options=self._parse_key_constraint_options(), 7924 ) 7925 ) 7926 7927 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 7928 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 7929 7930 def _parse_odbc_datetime_literal(self) -> exp.Expr: 7931 """ 7932 Parses a datetime column in ODBC format. We parse the column into the corresponding 7933 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 7934 same as we did for `DATE('yyyy-mm-dd')`. 7935 7936 Reference: 7937 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 7938 """ 7939 self._match(TokenType.VAR) 7940 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 7941 expression = self.expression(exp_class(this=self._parse_string())) 7942 if not self._match(TokenType.R_BRACE): 7943 self.raise_error("Expected }") 7944 return expression 7945 7946 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 7947 if not self._match_set(self.BRACKETS): 7948 return this 7949 7950 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 7951 map_token = seq_get(self._tokens, self._index - 2) 7952 parse_map = map_token is not None and map_token.text.upper() == "MAP" 7953 else: 7954 parse_map = False 7955 7956 bracket_kind = self._prev.token_type 7957 if ( 7958 bracket_kind == TokenType.L_BRACE 7959 and self._curr 7960 and self._curr.token_type == TokenType.VAR 7961 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 7962 ): 7963 return self._parse_odbc_datetime_literal() 7964 7965 expressions = self._parse_csv( 7966 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 7967 ) 7968 7969 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 7970 self.raise_error("Expected ]") 7971 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 7972 self.raise_error("Expected }") 7973 7974 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 7975 if bracket_kind == TokenType.L_BRACE: 7976 this = self.expression( 7977 exp.Struct( 7978 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 7979 ) 7980 ) 7981 elif not this: 7982 this = build_array_constructor( 7983 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 7984 ) 7985 else: 7986 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 7987 if constructor_type: 7988 return build_array_constructor( 7989 constructor_type, 7990 args=expressions, 7991 bracket_kind=bracket_kind, 7992 dialect=self.dialect, 7993 ) 7994 7995 expressions = apply_index_offset( 7996 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 7997 ) 7998 this = self.expression( 7999 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 8000 ) 8001 8002 self._add_comments(this) 8003 return self._parse_bracket(this) 8004 8005 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8006 if not self._match(TokenType.COLON): 8007 return this 8008 8009 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8010 self._advance() 8011 end: exp.Expr | None = -exp.Literal.number("1") 8012 else: 8013 end = self._parse_assignment() 8014 step = self._parse_unary() if self._match(TokenType.COLON) else None 8015 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8016 8017 def _parse_case(self) -> exp.Expr | None: 8018 if self._match(TokenType.DOT, advance=False): 8019 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8020 self._retreat(self._index - 1) 8021 return None 8022 8023 ifs = [] 8024 default = None 8025 8026 comments = self._prev_comments 8027 expression = self._parse_disjunction() 8028 8029 while self._match(TokenType.WHEN): 8030 this = self._parse_disjunction() 8031 self._match(TokenType.THEN) 8032 then = self._parse_disjunction() 8033 ifs.append(self.expression(exp.If(this=this, true=then))) 8034 8035 if self._match(TokenType.ELSE): 8036 default = self._parse_disjunction() 8037 8038 if not self._match(TokenType.END): 8039 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8040 default = exp.column("interval") 8041 else: 8042 self.raise_error("Expected END after CASE", self._prev) 8043 8044 return self.expression( 8045 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8046 ) 8047 8048 def _parse_if(self) -> exp.Expr | None: 8049 if self._match(TokenType.L_PAREN): 8050 args = self._parse_csv( 8051 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8052 ) 8053 this = self.validate_expression(exp.If.from_arg_list(args), args) 8054 self._match_r_paren() 8055 else: 8056 index = self._index - 1 8057 8058 if self.NO_PAREN_IF_COMMANDS and index == 0: 8059 return self._parse_as_command(self._prev) 8060 8061 condition = self._parse_disjunction() 8062 8063 if not condition: 8064 self._retreat(index) 8065 return None 8066 8067 self._match(TokenType.THEN) 8068 true = self._parse_disjunction() 8069 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8070 self._match(TokenType.END) 8071 this = self.expression(exp.If(this=condition, true=true, false=false)) 8072 8073 return this 8074 8075 def _parse_next_value_for(self) -> exp.Expr | None: 8076 if not self._match_text_seq("VALUE", "FOR"): 8077 self._retreat(self._index - 1) 8078 return None 8079 8080 return self.expression( 8081 exp.NextValueFor( 8082 this=self._parse_column(), 8083 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8084 ) 8085 ) 8086 8087 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8088 this = self._parse_function() or self._parse_var_or_string(upper=True) 8089 8090 if self._match(TokenType.FROM): 8091 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8092 8093 if not self._match(TokenType.COMMA): 8094 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8095 8096 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8097 8098 def _parse_gap_fill(self) -> exp.GapFill: 8099 self._match(TokenType.TABLE) 8100 this = self._parse_table() 8101 8102 self._match(TokenType.COMMA) 8103 args = [this, *self._parse_csv(self._parse_lambda)] 8104 8105 gap_fill = exp.GapFill.from_arg_list(args) 8106 return self.validate_expression(gap_fill, args) 8107 8108 def _parse_char(self) -> exp.Chr: 8109 return self.expression( 8110 exp.Chr( 8111 expressions=self._parse_csv(self._parse_assignment), 8112 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8113 ) 8114 ) 8115 8116 def _parse_charset_name(self) -> exp.Expr | None: 8117 """ 8118 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8119 for specific name shapes override this. 8120 """ 8121 return self._parse_var( 8122 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8123 ) 8124 8125 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8126 this = self._parse_assignment() 8127 8128 if not self._match(TokenType.ALIAS): 8129 if self._match(TokenType.COMMA): 8130 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8131 8132 self.raise_error("Expected AS after CAST") 8133 8134 fmt = None 8135 to = self._parse_types(with_collation=True) 8136 8137 default = None 8138 if self._match(TokenType.DEFAULT): 8139 default = self._parse_bitwise() 8140 self._match_text_seq("ON", "CONVERSION", "ERROR") 8141 8142 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8143 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8144 fmt = self._parse_at_time_zone(fmt_string) 8145 8146 if not to: 8147 to = exp.DType.UNKNOWN.into_expr() 8148 if to.this in exp.DataType.TEMPORAL_TYPES: 8149 this = self.expression( 8150 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8151 this=this, 8152 format=exp.Literal.string( 8153 format_time( 8154 fmt_string.this if fmt_string else "", 8155 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8156 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8157 ) 8158 ), 8159 safe=safe, 8160 ) 8161 ) 8162 8163 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8164 this.set("zone", fmt.args["zone"]) 8165 return this 8166 elif not to: 8167 self.raise_error("Expected TYPE after CAST") 8168 elif isinstance(to, exp.Identifier): 8169 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8170 elif to.this == exp.DType.CHAR and ( 8171 self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET") 8172 ): 8173 to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string()) 8174 8175 return self.build_cast( 8176 strict=strict, 8177 this=this, 8178 to=to, 8179 format=fmt, 8180 safe=safe, 8181 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8182 default=default, 8183 ) 8184 8185 def _parse_string_agg(self) -> exp.GroupConcat: 8186 if self._match(TokenType.DISTINCT): 8187 args: list[exp.Expr | None] = [ 8188 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8189 ] 8190 if self._match(TokenType.COMMA): 8191 args.extend(self._parse_csv(self._parse_disjunction)) 8192 else: 8193 args = self._parse_csv(self._parse_disjunction) # type: ignore 8194 8195 if self._match_text_seq("ON", "OVERFLOW"): 8196 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8197 if self._match_text_seq("ERROR"): 8198 on_overflow: exp.Expr | None = exp.var("ERROR") 8199 else: 8200 self._match_text_seq("TRUNCATE") 8201 on_overflow = self.expression( 8202 exp.OverflowTruncateBehavior( 8203 this=self._parse_string(), 8204 with_count=( 8205 self._match_text_seq("WITH", "COUNT") 8206 or not self._match_text_seq("WITHOUT", "COUNT") 8207 ), 8208 ) 8209 ) 8210 else: 8211 on_overflow = None 8212 8213 index = self._index 8214 if not self._match(TokenType.R_PAREN) and args: 8215 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8216 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8217 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8218 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8219 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8220 8221 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8222 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8223 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8224 if not self._match_text_seq("WITHIN", "GROUP"): 8225 self._retreat(index) 8226 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8227 8228 # The corresponding match_r_paren will be called in parse_function (caller) 8229 self._match_l_paren() 8230 8231 return self.expression( 8232 exp.GroupConcat( 8233 this=self._parse_order(this=seq_get(args, 0)), 8234 separator=seq_get(args, 1), 8235 on_overflow=on_overflow, 8236 ) 8237 ) 8238 8239 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8240 this = self._parse_bitwise() 8241 8242 if self._match(TokenType.USING): 8243 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8244 elif self._match(TokenType.COMMA): 8245 to = self._parse_types() 8246 else: 8247 to = None 8248 8249 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8250 8251 def _parse_xml_element(self) -> exp.XMLElement: 8252 if self._match_text_seq("EVALNAME"): 8253 evalname = True 8254 this = self._parse_bitwise() 8255 else: 8256 evalname = None 8257 self._match_text_seq("NAME") 8258 this = self._parse_id_var() 8259 8260 return self.expression( 8261 exp.XMLElement( 8262 this=this, 8263 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8264 evalname=evalname, 8265 ) 8266 ) 8267 8268 def _parse_xml_table(self) -> exp.XMLTable: 8269 namespaces = None 8270 passing = None 8271 columns = None 8272 8273 if self._match_text_seq("XMLNAMESPACES", "("): 8274 namespaces = self._parse_xml_namespace() 8275 self._match_text_seq(")", ",") 8276 8277 this = self._parse_string() 8278 8279 if self._match_text_seq("PASSING"): 8280 # The BY VALUE keywords are optional and are provided for semantic clarity 8281 self._match_text_seq("BY", "VALUE") 8282 passing = self._parse_csv(self._parse_column) 8283 8284 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8285 8286 if self._match_text_seq("COLUMNS"): 8287 columns = self._parse_csv(self._parse_field_def) 8288 8289 return self.expression( 8290 exp.XMLTable( 8291 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8292 ) 8293 ) 8294 8295 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8296 namespaces = [] 8297 8298 while True: 8299 if self._match(TokenType.DEFAULT): 8300 uri = self._parse_string() 8301 else: 8302 uri = self._parse_alias(self._parse_string()) 8303 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8304 if not self._match(TokenType.COMMA): 8305 break 8306 8307 return namespaces 8308 8309 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8310 args = self._parse_csv(self._parse_disjunction) 8311 8312 if len(args) < 3: 8313 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8314 8315 return self.expression(exp.DecodeCase(expressions=args)) 8316 8317 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8318 self._match_text_seq("KEY") 8319 key = self._parse_column() 8320 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8321 self._match_text_seq("VALUE") 8322 value = self._parse_bitwise() 8323 8324 if not key and not value: 8325 return None 8326 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8327 8328 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8329 if not this or not self._match_text_seq("FORMAT", "JSON"): 8330 return this 8331 8332 return self.expression(exp.FormatJson(this=this)) 8333 8334 def _parse_on_condition(self) -> exp.OnCondition | None: 8335 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8336 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8337 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8338 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8339 else: 8340 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8341 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8342 8343 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8344 8345 if not empty and not error and not null: 8346 return None 8347 8348 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8349 8350 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8351 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8352 for value in values: 8353 if self._match_text_seq(value, "ON", on): 8354 return f"{value} ON {on}" 8355 8356 index = self._index 8357 if self._match(TokenType.DEFAULT): 8358 default_value = self._parse_bitwise() 8359 if self._match_text_seq("ON", on): 8360 return default_value 8361 8362 self._retreat(index) 8363 8364 return None 8365 8366 @t.overload 8367 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8368 8369 @t.overload 8370 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8371 8372 def _parse_json_object(self, agg=False): 8373 star = self._parse_star() 8374 expressions = ( 8375 [star] 8376 if star 8377 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8378 ) 8379 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8380 8381 unique_keys = None 8382 if self._match_text_seq("WITH", "UNIQUE"): 8383 unique_keys = True 8384 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8385 unique_keys = False 8386 8387 self._match_text_seq("KEYS") 8388 8389 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8390 self._parse_type() 8391 ) 8392 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8393 8394 return self.expression( 8395 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8396 expressions=expressions, 8397 null_handling=null_handling, 8398 unique_keys=unique_keys, 8399 return_type=return_type, 8400 encoding=encoding, 8401 ) 8402 ) 8403 8404 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8405 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8406 if not self._match_text_seq("NESTED"): 8407 this = self._parse_id_var() 8408 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8409 kind = self._parse_types(allow_identifiers=False) 8410 nested = None 8411 else: 8412 this = None 8413 ordinality = None 8414 kind = None 8415 nested = True 8416 8417 format_json = self._match_text_seq("FORMAT", "JSON") 8418 path = self._match_text_seq("PATH") and self._parse_string() 8419 nested_schema = nested and self._parse_json_schema() 8420 8421 return self.expression( 8422 exp.JSONColumnDef( 8423 this=this, 8424 kind=kind, 8425 path=path, 8426 nested_schema=nested_schema, 8427 ordinality=ordinality, 8428 format_json=format_json, 8429 ) 8430 ) 8431 8432 def _parse_json_schema(self) -> exp.JSONSchema: 8433 self._match_text_seq("COLUMNS") 8434 return self.expression( 8435 exp.JSONSchema( 8436 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8437 ) 8438 ) 8439 8440 def _parse_json_table(self) -> exp.JSONTable: 8441 this = self._parse_format_json(self._parse_bitwise()) 8442 path = self._match(TokenType.COMMA) and self._parse_string() 8443 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8444 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8445 schema = self._parse_json_schema() 8446 8447 return exp.JSONTable( 8448 this=this, 8449 schema=schema, 8450 path=path, 8451 error_handling=error_handling, 8452 empty_handling=empty_handling, 8453 ) 8454 8455 def _parse_match_against(self) -> exp.MatchAgainst: 8456 if self._match_text_seq("TABLE"): 8457 # parse SingleStore MATCH(TABLE ...) syntax 8458 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8459 expressions = [] 8460 table = self._parse_table() 8461 if table: 8462 expressions = [table] 8463 else: 8464 expressions = self._parse_csv(self._parse_column) 8465 8466 self._match_text_seq(")", "AGAINST", "(") 8467 8468 this = self._parse_string() 8469 8470 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8471 modifier = "IN NATURAL LANGUAGE MODE" 8472 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8473 modifier = f"{modifier} WITH QUERY EXPANSION" 8474 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8475 modifier = "IN BOOLEAN MODE" 8476 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8477 modifier = "WITH QUERY EXPANSION" 8478 else: 8479 modifier = None 8480 8481 return self.expression( 8482 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8483 ) 8484 8485 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8486 def _parse_open_json(self) -> exp.OpenJSON: 8487 this = self._parse_bitwise() 8488 path = self._match(TokenType.COMMA) and self._parse_string() 8489 8490 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8491 this = self._parse_field(any_token=True) 8492 kind = self._parse_types() 8493 path = self._parse_string() 8494 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8495 8496 return self.expression( 8497 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8498 ) 8499 8500 expressions = None 8501 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8502 self._match_l_paren() 8503 expressions = self._parse_csv(_parse_open_json_column_def) 8504 8505 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8506 8507 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8508 args = self._parse_csv(self._parse_bitwise) 8509 8510 if self._match(TokenType.IN): 8511 return self.expression( 8512 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8513 ) 8514 8515 if haystack_first: 8516 haystack = seq_get(args, 0) 8517 needle = seq_get(args, 1) 8518 else: 8519 haystack = seq_get(args, 1) 8520 needle = seq_get(args, 0) 8521 8522 return self.expression( 8523 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8524 ) 8525 8526 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8527 args = self._parse_csv(self._parse_table) 8528 return exp.JoinHint(this=func_name.upper(), expressions=args) 8529 8530 def _parse_substring(self) -> exp.Substring: 8531 # Postgres supports the form: substring(string [from int] [for int]) 8532 # (despite being undocumented, the reverse order also works) 8533 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8534 8535 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8536 8537 start, length = None, None 8538 8539 while self._curr: 8540 if self._match(TokenType.FROM): 8541 start = self._parse_bitwise() 8542 elif self._match(TokenType.FOR): 8543 if not start: 8544 start = exp.Literal.number(1) 8545 length = self._parse_bitwise() 8546 else: 8547 break 8548 8549 if start: 8550 args.append(start) 8551 if length: 8552 args.append(length) 8553 8554 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8555 8556 def _parse_trim(self) -> exp.Trim: 8557 # https://www.w3resource.com/sql/character-functions/trim.php 8558 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8559 8560 position = None 8561 collation = None 8562 expression = None 8563 8564 if self._match_texts(self.TRIM_TYPES): 8565 position = self._prev.text.upper() 8566 8567 this = self._parse_bitwise() 8568 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8569 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8570 expression = self._parse_bitwise() 8571 8572 if invert_order: 8573 this, expression = expression, this 8574 8575 if self._match(TokenType.COLLATE): 8576 collation = self._parse_bitwise() 8577 8578 return self.expression( 8579 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8580 ) 8581 8582 def _parse_window_clause(self) -> list[exp.Expr] | None: 8583 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8584 8585 def _parse_named_window(self) -> exp.Expr | None: 8586 return self._parse_window(self._parse_id_var(), alias=True) 8587 8588 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8589 if self._curr.token_type == TokenType.VAR: 8590 if self._match_text_seq("IGNORE", "NULLS"): 8591 return self.expression(exp.IgnoreNulls(this=this)) 8592 if self._match_text_seq("RESPECT", "NULLS"): 8593 return self.expression(exp.RespectNulls(this=this)) 8594 return this 8595 8596 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8597 if self._match(TokenType.HAVING): 8598 self._match_texts(("MAX", "MIN")) 8599 max = self._prev.text.upper() != "MIN" 8600 return self.expression( 8601 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8602 ) 8603 8604 return this 8605 8606 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8607 func = this 8608 comments = func.comments if isinstance(func, exp.Expr) else None 8609 8610 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 8611 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 8612 if self._match_text_seq("WITHIN", "GROUP"): 8613 order = self._parse_wrapped(self._parse_order) 8614 this = self.expression(exp.WithinGroup(this=this, expression=order)) 8615 8616 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 8617 self._match(TokenType.WHERE) 8618 this = self.expression( 8619 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 8620 ) 8621 self._match_r_paren() 8622 8623 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 8624 # Some dialects choose to implement and some do not. 8625 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 8626 8627 # There is some code above in _parse_lambda that handles 8628 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 8629 8630 # The below changes handle 8631 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 8632 8633 # Oracle allows both formats 8634 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 8635 # and Snowflake chose to do the same for familiarity 8636 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 8637 if isinstance(this, exp.AggFunc): 8638 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 8639 8640 if ignore_respect and ignore_respect is not this: 8641 ignore_respect.replace(ignore_respect.this) 8642 this = self.expression(ignore_respect.__class__(this=this)) 8643 8644 this = self._parse_respect_or_ignore_nulls(this) 8645 8646 # bigquery select from window x AS (partition by ...) 8647 if alias: 8648 over = None 8649 self._match(TokenType.ALIAS) 8650 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 8651 return this 8652 else: 8653 over = self._prev.text.upper() 8654 8655 if comments and isinstance(func, exp.Expr): 8656 func.pop_comments() 8657 8658 if not self._match(TokenType.L_PAREN): 8659 return self.expression( 8660 exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments 8661 ) 8662 8663 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 8664 8665 first: bool | None = True if self._match(TokenType.FIRST) else None 8666 if self._match_text_seq("LAST"): 8667 first = False 8668 8669 partition, order = self._parse_partition_and_order() 8670 kind = ( 8671 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 8672 ) and self._prev.text 8673 8674 if kind: 8675 self._match(TokenType.BETWEEN) 8676 start = self._parse_window_spec() 8677 8678 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 8679 exclude = ( 8680 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 8681 if self._match_text_seq("EXCLUDE") 8682 else None 8683 ) 8684 8685 spec = self.expression( 8686 exp.WindowSpec( 8687 kind=kind, 8688 start=start["value"], 8689 start_side=start["side"], 8690 end=end.get("value"), 8691 end_side=end.get("side"), 8692 exclude=exclude, 8693 ) 8694 ) 8695 else: 8696 spec = None 8697 8698 self._match_r_paren() 8699 8700 window = self.expression( 8701 exp.Window( 8702 this=this, 8703 partition_by=partition, 8704 order=order, 8705 spec=spec, 8706 alias=window_alias, 8707 over=over, 8708 first=first, 8709 ), 8710 comments=comments, 8711 ) 8712 8713 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 8714 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 8715 return self._parse_window(window, alias=alias) 8716 8717 return window 8718 8719 def _parse_partition_and_order( 8720 self, 8721 ) -> tuple[list[exp.Expr], exp.Expr | None]: 8722 return self._parse_partition_by(), self._parse_order() 8723 8724 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 8725 self._match(TokenType.BETWEEN) 8726 8727 return { 8728 "value": ( 8729 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 8730 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 8731 or self._parse_bitwise() 8732 ), 8733 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 8734 } 8735 8736 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 8737 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 8738 # so this section tries to parse the clause version and if it fails, it treats the token 8739 # as an identifier (alias) 8740 if self._can_parse_limit_or_offset(): 8741 return this 8742 8743 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 8744 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 8745 if self._can_parse_named_window(): 8746 return this 8747 8748 any_token = self._match(TokenType.ALIAS) 8749 comments = self._prev_comments 8750 8751 if explicit and not any_token: 8752 return this 8753 8754 if self._match(TokenType.L_PAREN): 8755 aliases = self.expression( 8756 exp.Aliases( 8757 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 8758 ), 8759 comments=comments, 8760 ) 8761 self._match_r_paren(aliases) 8762 return aliases 8763 8764 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 8765 self.STRING_ALIASES and self._parse_string_as_identifier() 8766 ) 8767 8768 if alias: 8769 comments.extend(alias.pop_comments()) 8770 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 8771 column = this.this 8772 8773 # Moves the comment next to the alias in `expr /* comment */ AS alias` 8774 if not this.comments and column and column.comments: 8775 this.comments = column.pop_comments() 8776 8777 return this 8778 8779 def _parse_id_var( 8780 self, 8781 any_token: bool = True, 8782 tokens: t.Collection[TokenType] | None = None, 8783 ) -> exp.Expr | None: 8784 expression = self._parse_identifier() 8785 if not expression and ( 8786 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 8787 ): 8788 quoted = self._prev.token_type == TokenType.STRING 8789 expression = self._identifier_expression(quoted=quoted) 8790 8791 return expression 8792 8793 def _parse_string(self) -> exp.Expr | None: 8794 if self._match_set(self.STRING_PARSERS): 8795 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 8796 return self._parse_placeholder() 8797 8798 def _parse_string_as_identifier(self) -> exp.Identifier | None: 8799 if not self._match(TokenType.STRING): 8800 return None 8801 output = exp.to_identifier(self._prev.text, quoted=True) 8802 output.update_positions(self._prev) 8803 return output 8804 8805 def _parse_number(self) -> exp.Expr | None: 8806 if self._match_set(self.NUMERIC_PARSERS): 8807 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 8808 return self._parse_placeholder() 8809 8810 def _parse_identifier(self) -> exp.Expr | None: 8811 if self._match(TokenType.IDENTIFIER): 8812 return self._identifier_expression(quoted=True) 8813 return self._parse_placeholder() 8814 8815 def _parse_var( 8816 self, 8817 any_token: bool = False, 8818 tokens: t.Collection[TokenType] | None = None, 8819 upper: bool = False, 8820 ) -> exp.Expr | None: 8821 if ( 8822 (any_token and self._advance_any()) 8823 or self._match(TokenType.VAR) 8824 or (self._match_set(tokens) if tokens else False) 8825 ): 8826 return self.expression( 8827 exp.Var(this=self._prev.text.upper() if upper else self._prev.text) 8828 ) 8829 return self._parse_placeholder() 8830 8831 def _advance_any(self, ignore_reserved: bool = False) -> Token | None: 8832 if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS): 8833 self._advance() 8834 return self._prev 8835 return None 8836 8837 def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None: 8838 return self._parse_string() or self._parse_var(any_token=True, upper=upper) 8839 8840 def _parse_primary_or_var(self) -> exp.Expr | None: 8841 return self._parse_primary() or self._parse_var(any_token=True) 8842 8843 def _parse_null(self) -> exp.Expr | None: 8844 if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): 8845 return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) 8846 return self._parse_placeholder() 8847 8848 def _parse_boolean(self) -> exp.Expr | None: 8849 if self._match(TokenType.TRUE): 8850 return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) 8851 if self._match(TokenType.FALSE): 8852 return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) 8853 return self._parse_placeholder() 8854 8855 def _parse_star(self) -> exp.Expr | None: 8856 if self._match(TokenType.STAR): 8857 return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) 8858 return self._parse_placeholder() 8859 8860 def _parse_parameter(self) -> exp.Parameter: 8861 this = self._parse_identifier() or self._parse_primary_or_var() 8862 return self.expression(exp.Parameter(this=this)) 8863 8864 def _parse_placeholder(self) -> exp.Expr | None: 8865 if self._match_set(self.PLACEHOLDER_PARSERS): 8866 placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) 8867 if placeholder: 8868 return placeholder 8869 self._advance(-1) 8870 return None 8871 8872 def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None: 8873 if not self._match_texts(keywords): 8874 return None 8875 if self._match(TokenType.L_PAREN, advance=False): 8876 return self._parse_wrapped_csv(self._parse_expression) 8877 8878 expression = self._parse_alias(self._parse_disjunction(), explicit=True) 8879 return [expression] if expression else None 8880 8881 def _parse_csv( 8882 self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA 8883 ) -> list[T]: 8884 parse_result = parse_method() 8885 items = [parse_result] if parse_result is not None else [] 8886 8887 while self._match(sep): 8888 if isinstance(parse_result, exp.Expr): 8889 self._add_comments(parse_result) 8890 parse_result = parse_method() 8891 if parse_result is not None: 8892 items.append(parse_result) 8893 8894 return items 8895 8896 def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]: 8897 return self._parse_wrapped_csv(self._parse_id_var, optional=optional) 8898 8899 def _parse_wrapped_csv( 8900 self, 8901 parse_method: t.Callable[[], T | None], 8902 sep: TokenType = TokenType.COMMA, 8903 optional: bool = False, 8904 ) -> list[T]: 8905 return self._parse_wrapped( 8906 lambda: self._parse_csv(parse_method, sep=sep), optional=optional 8907 ) 8908 8909 def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T: 8910 wrapped = self._match(TokenType.L_PAREN) 8911 if not wrapped and not optional: 8912 self.raise_error("Expecting (") 8913 parse_result = parse_method() 8914 if wrapped: 8915 self._match_r_paren() 8916 return parse_result 8917 8918 def _parse_expressions(self) -> list[exp.Expr]: 8919 return self._parse_csv(self._parse_expression) 8920 8921 def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None: 8922 return ( 8923 self._parse_set_operations( 8924 self._parse_alias(self._parse_assignment(), explicit=True) 8925 if alias 8926 else self._parse_assignment() 8927 ) 8928 or self._parse_select() 8929 ) 8930 8931 def _parse_ddl_select(self) -> exp.Expr | None: 8932 return self._parse_query_modifiers( 8933 self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False)) 8934 ) 8935 8936 def _parse_transaction(self) -> exp.Transaction | exp.Command: 8937 this = None 8938 if self._match_texts(self.TRANSACTION_KIND): 8939 this = self._prev.text 8940 8941 self._match_texts(("TRANSACTION", "WORK")) 8942 8943 modes = [] 8944 while True: 8945 mode = [] 8946 while self._match(TokenType.VAR) or self._match(TokenType.NOT): 8947 mode.append(self._prev.text) 8948 8949 if mode: 8950 modes.append(" ".join(mode)) 8951 if not self._match(TokenType.COMMA): 8952 break 8953 8954 return self.expression(exp.Transaction(this=this, modes=modes)) 8955 8956 def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: 8957 chain = None 8958 savepoint = None 8959 is_rollback = self._prev.token_type == TokenType.ROLLBACK 8960 8961 self._match_texts(("TRANSACTION", "WORK")) 8962 8963 if self._match_text_seq("TO"): 8964 self._match_text_seq("SAVEPOINT") 8965 savepoint = self._parse_id_var() 8966 8967 if self._match(TokenType.AND): 8968 chain = not self._match_text_seq("NO") 8969 self._match_text_seq("CHAIN") 8970 8971 if is_rollback: 8972 return self.expression(exp.Rollback(savepoint=savepoint)) 8973 8974 return self.expression(exp.Commit(chain=chain)) 8975 8976 def _parse_refresh(self) -> exp.Refresh | exp.Command: 8977 if self._match_text_seq("EXTERNAL", "TABLE"): 8978 kind = "EXTERNAL TABLE" 8979 elif self._match(TokenType.TABLE): 8980 kind = "TABLE" 8981 elif self._match_text_seq("MATERIALIZED", "VIEW"): 8982 kind = "MATERIALIZED VIEW" 8983 else: 8984 kind = "" 8985 8986 this = self._parse_string() or self._parse_table() 8987 if not kind and not isinstance(this, exp.Literal): 8988 return self._parse_as_command(self._prev) 8989 8990 return self.expression(exp.Refresh(this=this, kind=kind)) 8991 8992 def _parse_column_def_with_exists(self): 8993 start = self._index 8994 self._match(TokenType.COLUMN) 8995 8996 exists_column = self._parse_exists(not_=True) 8997 expression = self._parse_field_def() 8998 8999 if not isinstance(expression, exp.ColumnDef): 9000 self._retreat(start) 9001 return None 9002 9003 expression.set("exists", exists_column) 9004 9005 return expression 9006 9007 def _parse_add_column(self) -> exp.ColumnDef | None: 9008 if not self._prev.text.upper() == "ADD": 9009 return None 9010 9011 return self._parse_column_def_with_exists() 9012 9013 def _parse_drop_column(self) -> exp.Drop | exp.Command | None: 9014 drop = self._parse_drop() if self._match(TokenType.DROP) else None 9015 if drop and not isinstance(drop, exp.Command): 9016 drop.set("kind", drop.args.get("kind", "COLUMN")) 9017 return drop 9018 9019 def _parse_alter_drop_action(self) -> exp.Expr | None: 9020 return self._parse_drop_column() 9021 9022 # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html 9023 def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition: 9024 return self.expression( 9025 exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists) 9026 ) 9027 9028 def _parse_alter_table_add(self) -> list[exp.Expr]: 9029 def _parse_add_alteration() -> exp.Expr | None: 9030 self._match_text_seq("ADD") 9031 if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False): 9032 return self.expression( 9033 exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint)) 9034 ) 9035 9036 column_def = self._parse_add_column() 9037 if isinstance(column_def, exp.ColumnDef): 9038 return column_def 9039 9040 exists = self._parse_exists(not_=True) 9041 if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): 9042 return self.expression( 9043 exp.AddPartition( 9044 exists=exists, 9045 this=self._parse_field(any_token=True), 9046 location=self._match_text_seq("LOCATION", advance=False) 9047 and self._parse_property(), 9048 ) 9049 ) 9050 9051 return None 9052 9053 if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( 9054 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 9055 or self._match_text_seq("COLUMNS") 9056 ): 9057 schema = self._parse_schema() 9058 9059 return ( 9060 ensure_list(schema) 9061 if schema 9062 else self._parse_csv(self._parse_column_def_with_exists) 9063 ) 9064 9065 return self._parse_csv(_parse_add_alteration) 9066 9067 def _parse_alter_table_alter(self) -> exp.Expr | None: 9068 if self._match_texts(self.ALTER_ALTER_PARSERS): 9069 return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) 9070 9071 # Many dialects support the ALTER [COLUMN] syntax, so if there is no 9072 # keyword after ALTER we default to parsing this statement 9073 self._match(TokenType.COLUMN) 9074 exists = self._parse_exists() 9075 column = self._parse_field(any_token=True) 9076 9077 if self._match_pair(TokenType.DROP, TokenType.DEFAULT): 9078 return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None)) 9079 if self._match_pair(TokenType.SET, TokenType.DEFAULT): 9080 return self.expression( 9081 exp.AlterColumn( 9082 this=column, default=self._parse_disjunction(), exists=exists or None 9083 ) 9084 ) 9085 if self._match(TokenType.COMMENT): 9086 return self.expression( 9087 exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None) 9088 ) 9089 if self._match_text_seq("DROP", "NOT", "NULL"): 9090 return self.expression( 9091 exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None) 9092 ) 9093 if self._match_text_seq("SET", "NOT", "NULL"): 9094 return self.expression( 9095 exp.AlterColumn(this=column, allow_null=False, exists=exists or None) 9096 ) 9097 9098 if self._match_text_seq("SET", "VISIBLE"): 9099 return self.expression( 9100 exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None) 9101 ) 9102 if self._match_text_seq("SET", "INVISIBLE"): 9103 return self.expression( 9104 exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None) 9105 ) 9106 9107 self._match_text_seq("SET", "DATA") 9108 self._match_text_seq("TYPE") 9109 return self.expression( 9110 exp.AlterColumn( 9111 this=column, 9112 dtype=self._parse_types(), 9113 collate=self._match(TokenType.COLLATE) and self._parse_term(), 9114 using=self._match(TokenType.USING) and self._parse_disjunction(), 9115 exists=exists or None, 9116 ) 9117 ) 9118 9119 def _parse_alter_diststyle(self) -> exp.AlterDistStyle: 9120 if self._match_texts(("ALL", "EVEN", "AUTO")): 9121 return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper()))) 9122 9123 self._match_text_seq("KEY", "DISTKEY") 9124 return self.expression(exp.AlterDistStyle(this=self._parse_column())) 9125 9126 def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey: 9127 if compound: 9128 self._match_text_seq("SORTKEY") 9129 9130 if self._match(TokenType.L_PAREN, advance=False): 9131 return self.expression( 9132 exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound) 9133 ) 9134 9135 self._match_texts(("AUTO", "NONE")) 9136 return self.expression( 9137 exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound) 9138 ) 9139 9140 def _parse_alter_table_drop(self) -> list[exp.Expr]: 9141 index = self._index - 1 9142 9143 partition_exists = self._parse_exists() 9144 if self._match(TokenType.PARTITION, advance=False): 9145 return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists)) 9146 9147 self._retreat(index) 9148 return self._parse_csv(self._parse_alter_drop_action) 9149 9150 def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None: 9151 if self._match(TokenType.COLUMN) or ( 9152 not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False) 9153 ): 9154 exists = self._parse_exists() 9155 old_column = self._parse_column() 9156 to = self._match_text_seq("TO") 9157 new_column = self._parse_column() 9158 9159 if old_column is None or not to or new_column is None: 9160 return None 9161 9162 return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists)) 9163 9164 self._match_text_seq("TO") 9165 return self.expression(exp.AlterRename(this=self._parse_table(schema=True))) 9166 9167 def _parse_alter_table_set(self) -> exp.AlterSet: 9168 alter_set = self.expression(exp.AlterSet()) 9169 9170 if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( 9171 "TABLE", "PROPERTIES" 9172 ): 9173 alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment)) 9174 elif self._match_text_seq("FILESTREAM_ON", advance=False): 9175 alter_set.set("expressions", [self._parse_assignment()]) 9176 elif self._match_texts(("LOGGED", "UNLOGGED")): 9177 alter_set.set("option", exp.var(self._prev.text.upper())) 9178 elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): 9179 alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) 9180 elif self._match_text_seq("LOCATION"): 9181 alter_set.set("location", self._parse_field()) 9182 elif self._match_text_seq("ACCESS", "METHOD"): 9183 alter_set.set("access_method", self._parse_field()) 9184 elif self._match_text_seq("TABLESPACE"): 9185 alter_set.set("tablespace", self._parse_field()) 9186 elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"): 9187 alter_set.set("file_format", [self._parse_field()]) 9188 elif self._match_text_seq("STAGE_FILE_FORMAT"): 9189 alter_set.set("file_format", self._parse_wrapped_options()) 9190 elif self._match_text_seq("STAGE_COPY_OPTIONS"): 9191 alter_set.set("copy_options", self._parse_wrapped_options()) 9192 elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): 9193 alter_set.set("tag", self._parse_csv(self._parse_assignment)) 9194 else: 9195 if self._match_text_seq("SERDE"): 9196 alter_set.set("serde", self._parse_field()) 9197 9198 properties = self._parse_wrapped(self._parse_properties, optional=True) 9199 alter_set.set("expressions", [properties]) 9200 9201 return alter_set 9202 9203 def _parse_alter_session(self) -> exp.AlterSession: 9204 """Parse ALTER SESSION SET/UNSET statements.""" 9205 if self._match(TokenType.SET): 9206 expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) 9207 return self.expression(exp.AlterSession(expressions=expressions, unset=False)) 9208 9209 self._match_text_seq("UNSET") 9210 expressions = self._parse_csv( 9211 lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True))) 9212 ) 9213 return self.expression(exp.AlterSession(expressions=expressions, unset=True)) 9214 9215 def _parse_alter(self) -> exp.Alter | exp.Command: 9216 start = self._prev 9217 9218 iceberg = self._match_text_seq("ICEBERG") 9219 9220 alter_token = self._match_set(self.ALTERABLES) and self._prev 9221 if not alter_token: 9222 return self._parse_as_command(start) 9223 if iceberg and alter_token.token_type != TokenType.TABLE: 9224 return self._parse_as_command(start) 9225 9226 exists = self._parse_exists() 9227 only = self._match_text_seq("ONLY") 9228 9229 if alter_token.token_type == TokenType.SESSION: 9230 this = None 9231 check = None 9232 cluster = None 9233 else: 9234 this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS) 9235 check = self._match_text_seq("WITH", "CHECK") 9236 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9237 9238 if self._next: 9239 self._advance() 9240 9241 parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None 9242 if parser: 9243 actions = ensure_list(parser(self)) 9244 not_valid = self._match_text_seq("NOT", "VALID") 9245 options = self._parse_csv(self._parse_property) 9246 cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE") 9247 9248 if not self._curr and actions: 9249 return self.expression( 9250 exp.Alter( 9251 this=this, 9252 kind=alter_token.text.upper(), 9253 exists=exists, 9254 actions=actions, 9255 only=only, 9256 options=options, 9257 cluster=cluster, 9258 not_valid=not_valid, 9259 check=check, 9260 cascade=cascade, 9261 iceberg=iceberg, 9262 ) 9263 ) 9264 9265 return self._parse_as_command(start) 9266 9267 def _parse_analyze(self) -> exp.Analyze | exp.Command: 9268 start = self._prev 9269 # https://duckdb.org/docs/sql/statements/analyze 9270 if not self._curr: 9271 return self.expression(exp.Analyze()) 9272 9273 options = [] 9274 while self._match_texts(self.ANALYZE_STYLES): 9275 if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": 9276 options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") 9277 else: 9278 options.append(self._prev.text.upper()) 9279 9280 this: exp.Expr | None = None 9281 inner_expression: exp.Expr | None = None 9282 9283 kind = self._curr.text.upper() if self._curr else None 9284 9285 if self._match(TokenType.TABLE) or self._match(TokenType.INDEX): 9286 this = self._parse_table_parts() 9287 elif self._match_text_seq("TABLES"): 9288 if self._match_set((TokenType.FROM, TokenType.IN)): 9289 kind = f"{kind} {self._prev.text.upper()}" 9290 this = self._parse_table(schema=True, is_db_reference=True) 9291 elif self._match_text_seq("DATABASE"): 9292 this = self._parse_table(schema=True, is_db_reference=True) 9293 elif self._match_text_seq("CLUSTER"): 9294 this = self._parse_table() 9295 # Try matching inner expr keywords before fallback to parse table. 9296 elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9297 kind = None 9298 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9299 else: 9300 # Empty kind https://prestodb.io/docs/current/sql/analyze.html 9301 kind = None 9302 this = self._parse_table_parts() 9303 9304 partition = self._try_parse(self._parse_partition) 9305 if not partition and self._match_texts(self.PARTITION_KEYWORDS): 9306 return self._parse_as_command(start) 9307 9308 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9309 if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( 9310 "WITH", "ASYNC", "MODE" 9311 ): 9312 mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" 9313 else: 9314 mode = None 9315 9316 if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9317 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9318 9319 properties = self._parse_properties() 9320 return self.expression( 9321 exp.Analyze( 9322 kind=kind, 9323 this=this, 9324 mode=mode, 9325 partition=partition, 9326 properties=properties, 9327 expression=inner_expression, 9328 options=options, 9329 ) 9330 ) 9331 9332 # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html 9333 def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: 9334 this = None 9335 kind = self._prev.text.upper() 9336 option = self._prev.text.upper() if self._match_text_seq("DELTA") else None 9337 expressions = [] 9338 9339 if not self._match_text_seq("STATISTICS"): 9340 self.raise_error("Expecting token STATISTICS") 9341 9342 if self._match_text_seq("NOSCAN"): 9343 this = "NOSCAN" 9344 elif self._match(TokenType.FOR): 9345 if self._match_text_seq("ALL", "COLUMNS"): 9346 this = "FOR ALL COLUMNS" 9347 if self._match_text_seq("COLUMNS"): 9348 this = "FOR COLUMNS" 9349 expressions = self._parse_csv(self._parse_column_reference) 9350 elif self._match_text_seq("SAMPLE"): 9351 sample = self._parse_number() 9352 expressions = [ 9353 self.expression( 9354 exp.AnalyzeSample( 9355 sample=sample, 9356 kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None, 9357 ) 9358 ) 9359 ] 9360 9361 return self.expression( 9362 exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions) 9363 ) 9364 9365 # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html 9366 def _parse_analyze_validate(self) -> exp.AnalyzeValidate: 9367 kind = None 9368 this = None 9369 expression: exp.Expr | None = None 9370 if self._match_text_seq("REF", "UPDATE"): 9371 kind = "REF" 9372 this = "UPDATE" 9373 if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): 9374 this = "UPDATE SET DANGLING TO NULL" 9375 elif self._match_text_seq("STRUCTURE"): 9376 kind = "STRUCTURE" 9377 if self._match_text_seq("CASCADE", "FAST"): 9378 this = "CASCADE FAST" 9379 elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( 9380 ("ONLINE", "OFFLINE") 9381 ): 9382 this = f"CASCADE COMPLETE {self._prev.text.upper()}" 9383 expression = self._parse_into() 9384 9385 return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression)) 9386 9387 def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None: 9388 this = self._prev.text.upper() 9389 if self._match_text_seq("COLUMNS"): 9390 return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}")) 9391 return None 9392 9393 def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None: 9394 kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None 9395 if self._match_text_seq("STATISTICS"): 9396 return self.expression(exp.AnalyzeDelete(kind=kind)) 9397 return None 9398 9399 def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None: 9400 if self._match_text_seq("CHAINED", "ROWS"): 9401 return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into())) 9402 return None 9403 9404 # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html 9405 def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: 9406 this = self._prev.text.upper() 9407 expression: exp.Expr | None = None 9408 expressions = [] 9409 update_options = None 9410 9411 if self._match_text_seq("HISTOGRAM", "ON"): 9412 expressions = self._parse_csv(self._parse_column_reference) 9413 with_expressions = [] 9414 while self._match(TokenType.WITH): 9415 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9416 if self._match_texts(("SYNC", "ASYNC")): 9417 if self._match_text_seq("MODE", advance=False): 9418 with_expressions.append(f"{self._prev.text.upper()} MODE") 9419 self._advance() 9420 else: 9421 buckets = self._parse_number() 9422 if self._match_text_seq("BUCKETS"): 9423 with_expressions.append(f"{buckets} BUCKETS") 9424 if with_expressions: 9425 expression = self.expression(exp.AnalyzeWith(expressions=with_expressions)) 9426 9427 if self._match_texts(("MANUAL", "AUTO")) and self._match( 9428 TokenType.UPDATE, advance=False 9429 ): 9430 update_options = self._prev.text.upper() 9431 self._advance() 9432 elif self._match_text_seq("USING", "DATA"): 9433 expression = self.expression(exp.UsingData(this=self._parse_string())) 9434 9435 return self.expression( 9436 exp.AnalyzeHistogram( 9437 this=this, 9438 expressions=expressions, 9439 expression=expression, 9440 update_options=update_options, 9441 ) 9442 ) 9443 9444 def _parse_merge(self) -> exp.Merge: 9445 self._match(TokenType.INTO) 9446 target = self._parse_table() 9447 9448 if target and self._match(TokenType.ALIAS, advance=False): 9449 target.set("alias", self._parse_table_alias()) 9450 9451 self._match(TokenType.USING) 9452 using = self._parse_table() 9453 9454 return self.expression( 9455 exp.Merge( 9456 this=target, 9457 using=using, 9458 on=self._match(TokenType.ON) and self._parse_disjunction(), 9459 using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), 9460 whens=self._parse_when_matched(), 9461 returning=self._parse_returning(), 9462 ) 9463 ) 9464 9465 def _parse_when_matched(self) -> exp.Whens: 9466 whens = [] 9467 9468 while self._match(TokenType.WHEN): 9469 matched = not self._match(TokenType.NOT) 9470 self._match_text_seq("MATCHED") 9471 source = ( 9472 False 9473 if self._match_text_seq("BY", "TARGET") 9474 else self._match_text_seq("BY", "SOURCE") 9475 ) 9476 condition = self._parse_disjunction() if self._match(TokenType.AND) else None 9477 9478 self._match(TokenType.THEN) 9479 9480 if self._match(TokenType.INSERT): 9481 this = self._parse_star() 9482 if this: 9483 then: exp.Expr | None = self.expression(exp.Insert(this=this)) 9484 else: 9485 then = self.expression( 9486 exp.Insert( 9487 this=exp.var("ROW") 9488 if self._match_text_seq("ROW") 9489 else self._parse_value(values=False), 9490 expression=self._match_text_seq("VALUES") and self._parse_value(), 9491 where=self._parse_where(), 9492 ) 9493 ) 9494 elif self._match(TokenType.UPDATE): 9495 expressions = self._parse_star() 9496 if expressions: 9497 then = self.expression(exp.Update(expressions=expressions)) 9498 else: 9499 then = self.expression( 9500 exp.Update( 9501 expressions=self._match(TokenType.SET) 9502 and self._parse_csv(self._parse_equality), 9503 where=self._parse_where(), 9504 ) 9505 ) 9506 elif self._match(TokenType.DELETE): 9507 then = self.expression(exp.Var(this=self._prev.text)) 9508 else: 9509 then = self._parse_var_from_options(self.CONFLICT_ACTIONS) 9510 9511 whens.append( 9512 self.expression( 9513 exp.When(matched=matched, source=source, condition=condition, then=then) 9514 ) 9515 ) 9516 return self.expression(exp.Whens(expressions=whens)) 9517 9518 def _parse_show(self) -> exp.Expr | None: 9519 parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) 9520 if parser: 9521 return parser(self) 9522 return self._parse_as_command(self._prev) 9523 9524 def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None: 9525 index = self._index 9526 9527 if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): 9528 return self._parse_set_transaction(global_=kind == "GLOBAL") 9529 9530 left = self._parse_primary() or self._parse_column() 9531 assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) 9532 9533 if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter): 9534 self._retreat(index) 9535 return None 9536 9537 right = self._parse_statement() or self._parse_id_var() 9538 if isinstance(right, (exp.Column, exp.Identifier)): 9539 right = exp.var(right.name) 9540 9541 this = self.expression(exp.EQ(this=left, expression=right)) 9542 return self.expression(exp.SetItem(this=this, kind=kind)) 9543 9544 def _parse_set_transaction(self, global_: bool = False) -> exp.Expr: 9545 self._match_text_seq("TRANSACTION") 9546 characteristics = self._parse_csv( 9547 lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) 9548 ) 9549 return self.expression( 9550 exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_) 9551 ) 9552 9553 def _parse_set_item(self) -> exp.Expr | None: 9554 parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) 9555 return parser(self) if parser else self._parse_set_item_assignment(kind=None) 9556 9557 def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command: 9558 index = self._index 9559 set_ = self.expression( 9560 exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag) 9561 ) 9562 9563 if self._curr: 9564 self._retreat(index) 9565 return self._parse_as_command(self._prev) 9566 9567 return set_ 9568 9569 def _parse_var_from_options( 9570 self, options: OPTIONS_TYPE, raise_unmatched: bool = True 9571 ) -> exp.Var | None: 9572 start = self._curr 9573 if not start: 9574 return None 9575 9576 option = start.text.upper() 9577 continuations = ( 9578 None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option) 9579 ) 9580 9581 index = self._index 9582 self._advance() 9583 for keywords in continuations or []: 9584 if isinstance(keywords, str): 9585 keywords = (keywords,) 9586 9587 if self._match_text_seq(*keywords): 9588 option = f"{option} {' '.join(keywords)}" 9589 break 9590 else: 9591 if continuations or continuations is None: 9592 if raise_unmatched: 9593 self.raise_error(f"Unknown option {option}") 9594 9595 self._retreat(index) 9596 return None 9597 9598 return exp.var(option) 9599 9600 def _parse_as_command(self, start: Token) -> exp.Command: 9601 while self._curr: 9602 self._advance() 9603 text = self._find_sql(start, self._prev) 9604 size = len(start.text) 9605 self._warn_unsupported() 9606 return exp.Command(this=text[:size], expression=text[size:]) 9607 9608 def _parse_dict_property(self, this: str) -> exp.DictProperty: 9609 settings = [] 9610 9611 self._match_l_paren() 9612 kind = self._parse_id_var() 9613 9614 if self._match(TokenType.L_PAREN): 9615 while True: 9616 key = self._parse_id_var() 9617 value = self._parse_function() or self._parse_primary_or_var() 9618 if not key and value is None: 9619 break 9620 settings.append(self.expression(exp.DictSubProperty(this=key, value=value))) 9621 self._match(TokenType.R_PAREN) 9622 9623 self._match_r_paren() 9624 9625 return self.expression( 9626 exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings) 9627 ) 9628 9629 def _parse_dict_range(self, this: str) -> exp.DictRange: 9630 self._match_l_paren() 9631 has_min = self._match_text_seq("MIN") 9632 if has_min: 9633 min = self._parse_var() or self._parse_primary() 9634 self._match_text_seq("MAX") 9635 max = self._parse_var() or self._parse_primary() 9636 else: 9637 max = self._parse_var() or self._parse_primary() 9638 min = exp.Literal.number(0) 9639 self._match_r_paren() 9640 return self.expression(exp.DictRange(this=this, min=min, max=max)) 9641 9642 def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None: 9643 index = self._index 9644 expression = self._parse_column() 9645 position = self._match(TokenType.COMMA) and self._parse_column() 9646 9647 if not self._match(TokenType.IN): 9648 self._retreat(index - 1) 9649 return None 9650 iterator = self._parse_column() 9651 condition = self._parse_disjunction() if self._match_text_seq("IF") else None 9652 return self.expression( 9653 exp.Comprehension( 9654 this=this, 9655 expression=expression, 9656 position=position, 9657 iterator=iterator, 9658 condition=condition, 9659 ) 9660 ) 9661 9662 def _parse_heredoc(self) -> exp.Heredoc | None: 9663 if self._match(TokenType.HEREDOC_STRING): 9664 return self.expression(exp.Heredoc(this=self._prev.text)) 9665 9666 if not self._match_text_seq("$"): 9667 return None 9668 9669 tags = ["$"] 9670 tag_text = None 9671 9672 if self._is_connected(): 9673 self._advance() 9674 tags.append(self._prev.text.upper()) 9675 else: 9676 self.raise_error("No closing $ found") 9677 9678 if tags[-1] != "$": 9679 if self._is_connected() and self._match_text_seq("$"): 9680 tag_text = tags[-1] 9681 tags.append("$") 9682 else: 9683 self.raise_error("No closing $ found") 9684 9685 heredoc_start = self._curr 9686 9687 while self._curr: 9688 if self._match_text_seq(*tags, advance=False): 9689 this = self._find_sql(heredoc_start, self._prev) 9690 self._advance(len(tags)) 9691 return self.expression(exp.Heredoc(this=this, tag=tag_text)) 9692 9693 self._advance() 9694 9695 self.raise_error(f"No closing {''.join(tags)} found") 9696 return None 9697 9698 def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None: 9699 if not self._curr: 9700 return None 9701 9702 index = self._index 9703 this = [] 9704 while True: 9705 # The current token might be multiple words 9706 curr = self._curr.text.upper() 9707 key = curr.split(" ") 9708 this.append(curr) 9709 9710 self._advance() 9711 result, trie = in_trie(trie, key) 9712 if result == TrieResult.FAILED: 9713 break 9714 9715 if result == TrieResult.EXISTS: 9716 subparser = parsers[" ".join(this)] 9717 return subparser 9718 9719 self._retreat(index) 9720 return None 9721 9722 def _match_l_paren(self, expression: exp.Expr | None = None) -> None: 9723 if not self._match(TokenType.L_PAREN, expression=expression): 9724 self.raise_error("Expecting (") 9725 9726 def _match_r_paren(self, expression: exp.Expr | None = None) -> None: 9727 if not self._match(TokenType.R_PAREN, expression=expression): 9728 self.raise_error("Expecting )") 9729 9730 def _replace_lambda( 9731 self, node: exp.Expr | None, expressions: list[exp.Expr] 9732 ) -> exp.Expr | None: 9733 if not node: 9734 return node 9735 9736 lambda_types = {e.name: e.args.get("to") or False for e in expressions} 9737 9738 for column in node.find_all(exp.Column): 9739 typ = lambda_types.get(column.parts[0].name) 9740 if typ is not None: 9741 dot_or_id = column.to_dot() if column.table else column.this 9742 9743 if typ: 9744 dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ)) 9745 9746 parent = column.parent 9747 9748 while isinstance(parent, exp.Dot): 9749 if not isinstance(parent.parent, exp.Dot): 9750 parent.replace(dot_or_id) 9751 break 9752 parent = parent.parent 9753 else: 9754 if column is node: 9755 node = dot_or_id 9756 else: 9757 column.replace(dot_or_id) 9758 return node 9759 9760 def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr: 9761 start = self._prev 9762 9763 # Not to be confused with TRUNCATE(number, decimals) function call 9764 if self._match(TokenType.L_PAREN): 9765 self._retreat(self._index - 2) 9766 return self._parse_function() 9767 9768 # Clickhouse supports TRUNCATE DATABASE as well 9769 is_database = self._match(TokenType.DATABASE) 9770 9771 self._match(TokenType.TABLE) 9772 9773 exists = self._parse_exists(not_=False) 9774 9775 expressions = self._parse_csv( 9776 lambda: self._parse_table(schema=True, is_db_reference=is_database) 9777 ) 9778 9779 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9780 9781 if self._match_text_seq("RESTART", "IDENTITY"): 9782 identity = "RESTART" 9783 elif self._match_text_seq("CONTINUE", "IDENTITY"): 9784 identity = "CONTINUE" 9785 else: 9786 identity = None 9787 9788 if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): 9789 option = self._prev.text 9790 else: 9791 option = None 9792 9793 partition = self._parse_partition() 9794 9795 # Fallback case 9796 if self._curr: 9797 return self._parse_as_command(start) 9798 9799 return self.expression( 9800 exp.TruncateTable( 9801 expressions=expressions, 9802 is_database=is_database, 9803 exists=exists, 9804 cluster=cluster, 9805 identity=identity, 9806 option=option, 9807 partition=partition, 9808 ) 9809 ) 9810 9811 def _parse_indexed_column(self) -> exp.Expr | None: 9812 return self._parse_ordered(self._parse_opclass) 9813 9814 def _parse_with_operator(self) -> exp.Expr | None: 9815 this = self._parse_indexed_column() 9816 9817 if not self._match(TokenType.WITH): 9818 return this 9819 9820 op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS) 9821 9822 return self.expression(exp.WithOperator(this=this, op=op)) 9823 9824 def _parse_wrapped_options(self) -> list[exp.Expr]: 9825 self._match(TokenType.EQ) 9826 self._match(TokenType.L_PAREN) 9827 9828 opts: list[exp.Expr] = [] 9829 option: exp.Expr | list[exp.Expr] | None 9830 while self._curr and not self._match(TokenType.R_PAREN): 9831 if self._match_text_seq("FORMAT_NAME", "="): 9832 # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL 9833 option = self._parse_format_name() 9834 else: 9835 option = self._parse_property() 9836 9837 if option is None: 9838 self.raise_error("Unable to parse option") 9839 break 9840 9841 opts.extend(ensure_list(option)) 9842 9843 return opts 9844 9845 def _parse_copy_parameters(self) -> list[exp.CopyParameter]: 9846 sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None 9847 9848 options = [] 9849 while self._curr and not self._match(TokenType.R_PAREN, advance=False): 9850 option = self._parse_var(any_token=True) 9851 prev = self._prev.text.upper() 9852 9853 # Different dialects might separate options and values by white space, "=" and "AS" 9854 self._match(TokenType.EQ) 9855 self._match(TokenType.ALIAS) 9856 9857 param = self.expression(exp.CopyParameter(this=option)) 9858 9859 if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( 9860 TokenType.L_PAREN, advance=False 9861 ): 9862 # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options 9863 param.set("expressions", self._parse_wrapped_options()) 9864 elif prev == "FILE_FORMAT": 9865 # T-SQL's external file format case 9866 param.set("expression", self._parse_field()) 9867 elif ( 9868 prev == "FORMAT" 9869 and self._prev.token_type == TokenType.ALIAS 9870 and self._match_texts(("AVRO", "JSON")) 9871 ): 9872 param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) 9873 param.set("expression", self._parse_field()) 9874 else: 9875 param.set("expression", self._parse_unquoted_field() or self._parse_bracket()) 9876 9877 options.append(param) 9878 9879 if sep: 9880 self._match(sep) 9881 9882 return options 9883 9884 def _parse_credentials(self) -> exp.Credentials | None: 9885 expr = self.expression(exp.Credentials()) 9886 9887 if self._match_text_seq("STORAGE_INTEGRATION", "="): 9888 expr.set("storage", self._parse_field()) 9889 if self._match_text_seq("CREDENTIALS"): 9890 # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string> 9891 creds = ( 9892 self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field() 9893 ) 9894 expr.set("credentials", creds) 9895 if self._match_text_seq("ENCRYPTION"): 9896 expr.set("encryption", self._parse_wrapped_options()) 9897 if self._match_text_seq("IAM_ROLE"): 9898 expr.set( 9899 "iam_role", 9900 exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(), 9901 ) 9902 if self._match_text_seq("REGION"): 9903 expr.set("region", self._parse_field()) 9904 9905 return expr 9906 9907 def _parse_file_location(self) -> exp.Expr | None: 9908 return self._parse_field() 9909 9910 def _parse_copy(self) -> exp.Copy | exp.Command: 9911 start = self._prev 9912 9913 self._match(TokenType.INTO) 9914 9915 this = ( 9916 self._parse_select(nested=True, parse_subquery_alias=False) 9917 if self._match(TokenType.L_PAREN, advance=False) 9918 else self._parse_table(schema=True) 9919 ) 9920 9921 kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") 9922 9923 files = self._parse_csv(self._parse_file_location) 9924 if self._match(TokenType.EQ, advance=False): 9925 # Backtrack one token since we've consumed the lhs of a parameter assignment here. 9926 # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter 9927 # list via `_parse_wrapped(..)` below. 9928 self._advance(-1) 9929 files = [] 9930 9931 credentials = self._parse_credentials() 9932 9933 self._match_text_seq("WITH") 9934 9935 params = self._parse_wrapped(self._parse_copy_parameters, optional=True) 9936 9937 # Fallback case 9938 if self._curr: 9939 return self._parse_as_command(start) 9940 9941 return self.expression( 9942 exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params) 9943 ) 9944 9945 def _parse_normalize(self) -> exp.Normalize: 9946 return self.expression( 9947 exp.Normalize( 9948 this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var() 9949 ) 9950 ) 9951 9952 def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor: 9953 args = self._parse_csv(lambda: self._parse_lambda()) 9954 9955 this = seq_get(args, 0) 9956 decimals = seq_get(args, 1) 9957 9958 return expr_type( 9959 this=this, 9960 decimals=decimals, 9961 to=self._parse_var() if self._match_text_seq("TO") else None, 9962 ) 9963 9964 def _parse_star_ops(self) -> exp.Expr | None: 9965 star_token = self._prev 9966 9967 if self._match_text_seq("COLUMNS", "(", advance=False): 9968 this = self._parse_function() 9969 if isinstance(this, exp.Columns): 9970 this.set("unpack", True) 9971 return this 9972 9973 index = self._index 9974 ilike = self._parse_string() if self._match(TokenType.ILIKE) else None 9975 if not ilike: 9976 # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)` 9977 self._retreat(index) 9978 9979 return self.expression( 9980 exp.Star( 9981 ilike=ilike, 9982 except_=self._parse_star_op("EXCEPT", "EXCLUDE"), 9983 replace=self._parse_star_op("REPLACE"), 9984 rename=self._parse_star_op("RENAME"), 9985 ) 9986 ).update_positions(star_token) 9987 9988 def _parse_grant_privilege(self) -> exp.GrantPrivilege | None: 9989 privilege_parts = [] 9990 9991 # Keep consuming consecutive keywords until comma (end of this privilege) or ON 9992 # (end of privilege list) or L_PAREN (start of column list) are met 9993 while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False): 9994 privilege_parts.append(self._curr.text.upper()) 9995 self._advance() 9996 9997 this = exp.var(" ".join(privilege_parts)) 9998 expressions = ( 9999 self._parse_wrapped_csv(self._parse_column) 10000 if self._match(TokenType.L_PAREN, advance=False) 10001 else None 10002 ) 10003 10004 return self.expression(exp.GrantPrivilege(this=this, expressions=expressions)) 10005 10006 def _parse_grant_principal(self) -> exp.GrantPrincipal | None: 10007 kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() 10008 principal = self._parse_id_var() 10009 10010 if not principal: 10011 return None 10012 10013 return self.expression(exp.GrantPrincipal(this=principal, kind=kind)) 10014 10015 def _parse_grant_revoke_common( 10016 self, 10017 ) -> tuple[list | None, str | None, exp.Expr | None]: 10018 privileges = self._parse_csv(self._parse_grant_privilege) 10019 10020 self._match(TokenType.ON) 10021 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 10022 10023 # Attempt to parse the securable e.g. MySQL allows names 10024 # such as "foo.*", "*.*" which are not easily parseable yet 10025 securable = self._try_parse(self._parse_table_parts) 10026 10027 return privileges, kind, securable 10028 10029 def _parse_grant(self) -> exp.Grant | exp.Command: 10030 start = self._prev 10031 10032 privileges, kind, securable = self._parse_grant_revoke_common() 10033 10034 if not securable or not self._match_text_seq("TO"): 10035 return self._parse_as_command(start) 10036 10037 principals = self._parse_csv(self._parse_grant_principal) 10038 10039 grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") 10040 10041 if self._curr: 10042 return self._parse_as_command(start) 10043 10044 return self.expression( 10045 exp.Grant( 10046 privileges=privileges, 10047 kind=kind, 10048 securable=securable, 10049 principals=principals, 10050 grant_option=grant_option, 10051 ) 10052 ) 10053 10054 def _parse_revoke(self) -> exp.Revoke | exp.Command: 10055 start = self._prev 10056 10057 grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") 10058 10059 privileges, kind, securable = self._parse_grant_revoke_common() 10060 10061 if not securable or not self._match_text_seq("FROM"): 10062 return self._parse_as_command(start) 10063 10064 principals = self._parse_csv(self._parse_grant_principal) 10065 10066 cascade = None 10067 if self._match_texts(("CASCADE", "RESTRICT")): 10068 cascade = self._prev.text.upper() 10069 10070 if self._curr: 10071 return self._parse_as_command(start) 10072 10073 return self.expression( 10074 exp.Revoke( 10075 privileges=privileges, 10076 kind=kind, 10077 securable=securable, 10078 principals=principals, 10079 grant_option=grant_option, 10080 cascade=cascade, 10081 ) 10082 ) 10083 10084 def _parse_overlay(self) -> exp.Overlay: 10085 def _parse_overlay_arg(text: str) -> exp.Expr | None: 10086 return ( 10087 self._parse_bitwise() 10088 if self._match(TokenType.COMMA) or self._match_text_seq(text) 10089 else None 10090 ) 10091 10092 return self.expression( 10093 exp.Overlay( 10094 this=self._parse_bitwise(), 10095 expression=_parse_overlay_arg("PLACING"), 10096 from_=_parse_overlay_arg("FROM"), 10097 for_=_parse_overlay_arg("FOR"), 10098 ) 10099 ) 10100 10101 def _parse_format_name(self) -> exp.Property: 10102 # Note: Although not specified in the docs, Snowflake does accept a string/identifier 10103 # for FILE_FORMAT = <format_name> 10104 return self.expression( 10105 exp.Property( 10106 this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts() 10107 ) 10108 ) 10109 10110 def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F: 10111 is_distinct = self._match(TokenType.DISTINCT) 10112 if not is_distinct: 10113 self._match(TokenType.ALL) 10114 10115 args = [self._parse_lambda()] 10116 if self._match(TokenType.COMMA): 10117 args.extend(self._parse_function_args()) 10118 10119 target = seq_get(args, distinct_index) 10120 if is_distinct and target: 10121 args[distinct_index] = self.expression(exp.Distinct(expressions=[target])) 10122 10123 return func.from_arg_list(args) 10124 10125 def _identifier_expression( 10126 self, token: Token | None = None, quoted: bool | None = None 10127 ) -> exp.Identifier: 10128 token = token or self._prev 10129 return self.expression(exp.Identifier(this=token.text, quoted=quoted), token) 10130 10131 def _build_pipe_cte( 10132 self, 10133 query: exp.Query, 10134 expressions: list[exp.Expr], 10135 alias_cte: exp.TableAlias | None = None, 10136 ) -> exp.Select: 10137 new_cte: str | exp.TableAlias | None 10138 if alias_cte: 10139 new_cte = alias_cte 10140 else: 10141 self._pipe_cte_counter += 1 10142 new_cte = f"__tmp{self._pipe_cte_counter}" 10143 10144 with_ = query.args.get("with_") 10145 ctes = with_.pop() if with_ else None 10146 10147 new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) 10148 if ctes: 10149 new_select.set("with_", ctes) 10150 10151 return new_select.with_(new_cte, as_=query, copy=False) 10152 10153 def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: 10154 select = self._parse_select(consume_pipe=False) 10155 if not select: 10156 return query 10157 10158 return self._build_pipe_cte( 10159 query=query.select(*select.expressions, append=False), expressions=[exp.Star()] 10160 ) 10161 10162 def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: 10163 limit = self._parse_limit() 10164 offset = self._parse_offset() 10165 if limit: 10166 curr_limit = query.args.get("limit", limit) 10167 if curr_limit.expression.to_py() >= limit.expression.to_py(): 10168 query.limit(limit, copy=False) 10169 if offset: 10170 curr_offset = query.args.get("offset") 10171 curr_offset = curr_offset.expression.to_py() if curr_offset else 0 10172 query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False) 10173 10174 return query 10175 10176 def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None: 10177 this = self._parse_disjunction() 10178 if self._match_text_seq("GROUP", "AND", advance=False): 10179 return this 10180 10181 this = self._parse_alias(this) 10182 10183 if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): 10184 return self._parse_ordered(lambda: this) 10185 10186 return this 10187 10188 def _parse_pipe_syntax_aggregate_group_order_by( 10189 self, query: exp.Select, group_by_exists: bool = True 10190 ) -> exp.Select: 10191 expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) 10192 aggregates_or_groups, orders = [], [] 10193 for element in expr: 10194 if isinstance(element, exp.Ordered): 10195 this = element.this 10196 if isinstance(this, exp.Alias): 10197 element.set("this", this.args["alias"]) 10198 orders.append(element) 10199 else: 10200 this = element 10201 aggregates_or_groups.append(this) 10202 10203 if group_by_exists: 10204 query.select( 10205 *aggregates_or_groups, *query.expressions, append=False, copy=False 10206 ).group_by( 10207 *[projection.args.get("alias", projection) for projection in aggregates_or_groups], 10208 copy=False, 10209 ) 10210 else: 10211 query.select(*aggregates_or_groups, append=False, copy=False) 10212 10213 if orders: 10214 return query.order_by(*orders, append=False, copy=False) 10215 10216 return query 10217 10218 def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: 10219 self._match_text_seq("AGGREGATE") 10220 query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False) 10221 10222 if self._match(TokenType.GROUP_BY) or ( 10223 self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) 10224 ): 10225 query = self._parse_pipe_syntax_aggregate_group_order_by(query) 10226 10227 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10228 10229 def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None: 10230 first_setop = self.parse_set_operation(this=query) 10231 if not first_setop: 10232 return None 10233 10234 def _parse_and_unwrap_query() -> exp.Expr | None: 10235 expr = self._parse_paren() 10236 return expr.assert_is(exp.Subquery).unnest() if expr else None 10237 10238 first_setop.this.pop() 10239 10240 setops = [ 10241 first_setop.expression.pop().assert_is(exp.Subquery).unnest(), 10242 *self._parse_csv(_parse_and_unwrap_query), 10243 ] 10244 10245 query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10246 with_ = query.args.get("with_") 10247 ctes = with_.pop() if with_ else None 10248 10249 if isinstance(first_setop, exp.Union): 10250 query = query.union(*setops, copy=False, **first_setop.args) 10251 elif isinstance(first_setop, exp.Except): 10252 query = query.except_(*setops, copy=False, **first_setop.args) 10253 else: 10254 query = query.intersect(*setops, copy=False, **first_setop.args) 10255 10256 query.set("with_", ctes) 10257 10258 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10259 10260 def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None: 10261 join = self._parse_join() 10262 if not join: 10263 return None 10264 10265 if isinstance(query, exp.Select): 10266 return query.join(join, copy=False) 10267 10268 return query 10269 10270 def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: 10271 pivots = self._parse_pivots() 10272 if not pivots: 10273 return query 10274 10275 from_ = query.args.get("from_") 10276 if from_: 10277 from_.this.set("pivots", pivots) 10278 10279 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10280 10281 def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: 10282 self._match_text_seq("EXTEND") 10283 query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False) 10284 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10285 10286 def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: 10287 sample = self._parse_table_sample() 10288 10289 with_ = query.args.get("with_") 10290 if with_: 10291 with_.expressions[-1].this.set("sample", sample) 10292 else: 10293 query.set("sample", sample) 10294 10295 return query 10296 10297 def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None: 10298 if isinstance(query, exp.Subquery): 10299 query = exp.select("*").from_(query, copy=False) 10300 10301 if not query.args.get("from_"): 10302 query = exp.select("*").from_(query.subquery(copy=False), copy=False) 10303 10304 while self._match(TokenType.PIPE_GT): 10305 start_index = self._index 10306 start_text = self._curr.text.upper() 10307 parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text) 10308 if not parser: 10309 # The set operators (UNION, etc) and the JOIN operator have a few common starting 10310 # keywords, making it tricky to disambiguate them without lookahead. The approach 10311 # here is to try and parse a set operation and if that fails, then try to parse a 10312 # join operator. If that fails as well, then the operator is not supported. 10313 parsed_query = self._parse_pipe_syntax_set_operator(query) 10314 parsed_query = parsed_query or self._parse_pipe_syntax_join(query) 10315 if not parsed_query: 10316 self._retreat(start_index) 10317 self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.") 10318 break 10319 query = parsed_query 10320 else: 10321 query = parser(self, query) 10322 10323 return query 10324 10325 def _parse_declareitem(self) -> exp.DeclareItem | None: 10326 self._match_texts(("VAR", "VARIABLE")) 10327 10328 vars = self._parse_csv(self._parse_id_var) 10329 if not vars: 10330 return None 10331 10332 self._match(TokenType.ALIAS) 10333 kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types() 10334 default = ( 10335 self._match(TokenType.DEFAULT) or self._match(TokenType.EQ) 10336 ) and self._parse_bitwise() 10337 10338 return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default)) 10339 10340 def _parse_declare(self) -> exp.Declare | exp.Command: 10341 start = self._prev 10342 replace = self._match_text_seq("OR", "REPLACE") 10343 expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) 10344 10345 if not expressions or self._curr: 10346 return self._parse_as_command(start) 10347 10348 return self.expression(exp.Declare(expressions=expressions, replace=replace)) 10349 10350 def build_cast(self, strict: bool, **kwargs) -> exp.Expr: 10351 exp_class = exp.Cast if strict else exp.TryCast 10352 10353 if exp_class == exp.TryCast: 10354 kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING 10355 10356 return self.expression(exp_class(**kwargs)) 10357 10358 def _parse_json_value(self) -> exp.JSONValue: 10359 this = self._parse_bitwise() 10360 self._match(TokenType.COMMA) 10361 path = self._parse_bitwise() 10362 10363 returning = self._match(TokenType.RETURNING) and self._parse_type() 10364 10365 return self.expression( 10366 exp.JSONValue( 10367 this=this, 10368 path=self.dialect.to_json_path(path), 10369 returning=returning, 10370 on_condition=self._parse_on_condition(), 10371 ) 10372 ) 10373 10374 def _parse_group_concat(self) -> exp.Expr | None: 10375 def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr: 10376 if isinstance(node, exp.Distinct) and len(node.expressions) > 1: 10377 concat_exprs = [ 10378 self.expression( 10379 exp.Concat( 10380 expressions=node.expressions, 10381 safe=True, 10382 coalesce=self.dialect.CONCAT_COALESCE, 10383 ) 10384 ) 10385 ] 10386 node.set("expressions", concat_exprs) 10387 return node 10388 if len(exprs) == 1: 10389 return exprs[0] 10390 return self.expression( 10391 exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE) 10392 ) 10393 10394 args = self._parse_csv(self._parse_lambda) 10395 10396 if args: 10397 order = args[-1] if isinstance(args[-1], exp.Order) else None 10398 10399 if order: 10400 # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, 10401 # remove 'expr' from exp.Order and add it back to args 10402 args[-1] = order.this 10403 order.set("this", concat_exprs(order.this, args)) 10404 10405 this = order or concat_exprs(args[0], args) 10406 else: 10407 this = None 10408 10409 separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None 10410 10411 return self.expression(exp.GroupConcat(this=this, separator=separator)) 10412 10413 def _parse_initcap(self) -> exp.Initcap: 10414 expr = exp.Initcap.from_arg_list(self._parse_function_args()) 10415 10416 # attach dialect's default delimiters 10417 if expr.args.get("expression") is None: 10418 expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)) 10419 10420 return expr 10421 10422 def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None: 10423 while True: 10424 if not self._match(TokenType.L_PAREN): 10425 break 10426 10427 op = "" 10428 while self._curr and not self._match(TokenType.R_PAREN): 10429 op += self._curr.text 10430 self._advance() 10431 10432 comments = self._prev_comments 10433 this = self.expression( 10434 exp.Operator(this=this, operator=op, expression=self._parse_bitwise()), 10435 comments=comments, 10436 ) 10437 10438 if not self._match(TokenType.OPERATOR): 10439 break 10440 10441 return this
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: 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
- max_nodes: Maximum number of AST nodes to prevent memory exhaustion. Set to -1 (default) to disable the check.
1941 def __init__( 1942 self, 1943 error_level: ErrorLevel | None = None, 1944 error_message_context: int = 100, 1945 max_errors: int = 3, 1946 max_nodes: int = -1, 1947 dialect: DialectType = None, 1948 ): 1949 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 1950 self.error_message_context: int = error_message_context 1951 self.max_errors: int = max_errors 1952 self.max_nodes: int = max_nodes 1953 self.dialect: t.Any = _resolve_dialect(dialect) 1954 self.sql: str = "" 1955 self.errors: list[ParseError] = [] 1956 self._tokens: list[Token] = [] 1957 self._tokens_size: i64 = 0 1958 self._index: i64 = 0 1959 self._curr: Token = SENTINEL_NONE 1960 self._next: Token = SENTINEL_NONE 1961 self._prev: Token = SENTINEL_NONE 1962 self._prev_comments: list[str] = [] 1963 self._pipe_cte_counter: int = 0 1964 self._chunks: list[list[Token]] = [] 1965 self._chunk_index: i64 = 0 1966 self._node_count: int = 0
1968 def reset(self) -> None: 1969 self.sql = "" 1970 self.errors = [] 1971 self._tokens = [] 1972 self._tokens_size = 0 1973 self._index = 0 1974 self._curr = SENTINEL_NONE 1975 self._next = SENTINEL_NONE 1976 self._prev = SENTINEL_NONE 1977 self._prev_comments = [] 1978 self._pipe_cte_counter = 0 1979 self._chunks = [] 1980 self._chunk_index = 0 1981 self._node_count = 0
2074 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2075 token = token or self._curr or self._prev or Token.string("") 2076 formatted_sql, start_context, highlight, end_context = highlight_sql( 2077 sql=self.sql, 2078 positions=[(token.start, token.end)], 2079 context_length=self.error_message_context, 2080 ) 2081 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2082 2083 error = ParseError.new( 2084 formatted_message, 2085 description=message, 2086 line=token.line, 2087 col=token.col, 2088 start_context=start_context, 2089 highlight=highlight, 2090 end_context=end_context, 2091 ) 2092 2093 if self.error_level == ErrorLevel.IMMEDIATE: 2094 raise error 2095 2096 self.errors.append(error)
2098 def validate_expression(self, expression: E, args: list | None = None) -> E: 2099 if self.max_nodes > -1: 2100 self._node_count += 1 2101 if self._node_count > self.max_nodes: 2102 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2103 if self.error_level != ErrorLevel.IGNORE: 2104 for error_message in expression.error_messages(args): 2105 self.raise_error(error_message) 2106 return expression
2125 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2126 """ 2127 Parses a list of tokens and returns a list of syntax trees, one tree 2128 per parsed SQL statement. 2129 2130 Args: 2131 raw_tokens: The list of tokens. 2132 sql: The original SQL string. 2133 2134 Returns: 2135 The list of the produced syntax trees. 2136 """ 2137 return self._parse( 2138 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2139 )
Parses a list of tokens and returns a list of syntax trees, one tree per parsed SQL statement.
Arguments:
- raw_tokens: The list of tokens.
- sql: The original SQL string.
Returns:
The list of the produced syntax trees.
2141 def parse_into( 2142 self, 2143 expression_types: exp.IntoType, 2144 raw_tokens: list[Token], 2145 sql: str | None = None, 2146 ) -> list[exp.Expr | None]: 2147 """ 2148 Parses a list of tokens into a given Expr type. If a collection of Expr 2149 types is given instead, this method will try to parse the token list into each one 2150 of them, stopping at the first for which the parsing succeeds. 2151 2152 Args: 2153 expression_types: The expression type(s) to try and parse the token list into. 2154 raw_tokens: The list of tokens. 2155 sql: The original SQL string, used to produce helpful debug messages. 2156 2157 Returns: 2158 The target Expr. 2159 """ 2160 errors = [] 2161 for expression_type in ensure_list(expression_types): 2162 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2163 if not parser: 2164 raise TypeError(f"No parser registered for {expression_type}") 2165 2166 try: 2167 return self._parse(parser, raw_tokens, sql) 2168 except ParseError as e: 2169 e.errors[0]["into_expression"] = expression_type 2170 errors.append(e) 2171 2172 raise ParseError( 2173 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2174 errors=merge_errors(errors), 2175 ) from errors[-1]
Parses a list of tokens into a given Expr type. If a collection of Expr types is given instead, this method will try to parse the token list into each one of them, stopping at the first for which the parsing succeeds.
Arguments:
- expression_types: The expression type(s) to try and parse the token list into.
- raw_tokens: The list of tokens.
- sql: The original SQL string, used to produce helpful debug messages.
Returns:
The target Expr.
2177 def check_errors(self) -> None: 2178 """Logs or raises any found errors, depending on the chosen error level setting.""" 2179 if self.error_level == ErrorLevel.WARN: 2180 for error in self.errors: 2181 logger.error(str(error)) 2182 elif self.error_level == ErrorLevel.RAISE and self.errors: 2183 raise ParseError( 2184 concat_messages(self.errors, self.max_errors), 2185 errors=merge_errors(self.errors), 2186 )
Logs or raises any found errors, depending on the chosen error level setting.
2188 def expression( 2189 self, 2190 instance: E, 2191 token: Token | None = None, 2192 comments: list[str] | None = None, 2193 ) -> E: 2194 if token: 2195 instance.update_positions(token) 2196 instance.add_comments(comments) if comments else self._add_comments(instance) 2197 if not instance.is_primitive: 2198 instance = self.validate_expression(instance) 2199 return instance
5890 def parse_set_operation( 5891 self, this: exp.Expr | None, consume_pipe: bool = False 5892 ) -> exp.Expr | None: 5893 start = self._index 5894 _, side_token, kind_token = self._parse_join_parts() 5895 5896 side = side_token.text if side_token else None 5897 kind = kind_token.text if kind_token else None 5898 5899 if not self._match_set(self.SET_OPERATIONS): 5900 self._retreat(start) 5901 return None 5902 5903 token_type = self._prev.token_type 5904 5905 if token_type == TokenType.UNION: 5906 operation: type[exp.SetOperation] = exp.Union 5907 elif token_type == TokenType.EXCEPT: 5908 operation = exp.Except 5909 else: 5910 operation = exp.Intersect 5911 5912 comments = self._prev.comments 5913 5914 if self._match(TokenType.DISTINCT): 5915 distinct: bool | None = True 5916 elif self._match(TokenType.ALL): 5917 distinct = False 5918 else: 5919 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 5920 if distinct is None: 5921 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 5922 5923 by_name = ( 5924 self._match_text_seq("BY", "NAME") 5925 or self._match_text_seq("STRICT", "CORRESPONDING") 5926 or None 5927 ) 5928 if self._match_text_seq("CORRESPONDING"): 5929 by_name = True 5930 if not side and not kind: 5931 kind = "INNER" 5932 5933 on_column_list = None 5934 if by_name and self._match_texts(("ON", "BY")): 5935 on_column_list = self._parse_wrapped_csv(self._parse_column) 5936 5937 expression = self._parse_select( 5938 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 5939 ) 5940 5941 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 5942 # in _parse_cte and so that alias pushdown can reach into set operation branches 5943 if isinstance(this, exp.Values): 5944 this = self._values_to_select(this) 5945 if isinstance(expression, exp.Values): 5946 expression = self._values_to_select(expression) 5947 5948 return self.expression( 5949 operation( 5950 this=this, 5951 distinct=distinct, 5952 by_name=by_name, 5953 expression=expression, 5954 side=side, 5955 kind=kind, 5956 on=on_column_list, 5957 ), 5958 comments=comments, 5959 )